Можно ли в C++ объявить класс как наследующий от другого класса?

StackOverflow https://stackoverflow.com/questions/2159390

Вопрос

Я знаю, что могу:

class Foo;

но могу ли я объявить класс как наследующий от другого, например:

class Bar {};

class Foo: public Bar;

Примером использования могут быть ковариантные ссылочные возвращаемые типы.

// somewhere.h
class RA {}
class RB : public RA {}

...а затем в другом заголовке, который не включает где-то.h

// other.h
class RA;

class A {
 public:
  virtual RA* Foo();  // this only needs the forward deceleration
}

class RB : public RA; // invalid but...

class B {
 public:
  virtual RB* Foo();  // 
}

Единственная информация, которую компилятор должен необходимо оформить декларацию RB* B:Foo() в том, что RB имеет RA как общедоступный базовый класс.Теперь очевидно, что вам понадобится где-то.h, если вы собираетесь выполнить какое-либо разыменование возвращаемых значений из Foo.Однако, если некоторые клиенты никогда не звонят Foo, то им нет смысла включать где-то.h, что могло бы значительно ускорить компиляцию.

Это было полезно?

Решение

Предварительное объявление действительно полезно только для того, чтобы сообщить компилятору, что класс с таким именем действительно существует и будет объявлен и определен где-то еще.Вы не можете использовать его в любом случае, когда компилятору нужна контекстная информация о классе, и для компилятора бесполезно сообщать ему лишь немного о классе.(Как правило, вы можете использовать предварительное объявление только при ссылке на этот класс без другого контекста, напримерв качестве параметра или возвращаемого значения.)

Таким образом, вы не можете пересылать объявление Bar в любом сценарии, где вы затем используете его для объявления Foo, и совершенно не имеет смысла иметь предварительное объявление, включающее базовый класс - о чем это вам говорит, кроме того? ничего?

Другие советы

Форвардные декларации — это декларации, а не определения.Таким образом, для всего, что требует объявления класса (например, указателей на этот класс), требуется только предварительное объявление.Однако все, что потребует определения – т.е.нужно будет знать фактическую структуру класса - не будет работать только с предварительным объявлением.

Производным классам обязательно нужно знать структуру своего родителя, а не только то, что родитель существует, поэтому предварительного объявления будет недостаточно.

Нет, невозможно объявить наследование, даже если вы имеете дело только с указателями.Имея дело с преобразованиями между указателями, иногда компилятору необходимо знать детали класса, чтобы правильно выполнить преобразование.Это касается множественного наследования.(Вы можете выделить некоторые части иерархии, которые используют только одиночное наследование, но это не является частью языка.)

Рассмотрим следующий тривиальный случай:

#include <stdio.h>
class A { int x; };
class B { int y; };
class C: public A, public B { int z; };
void main()
{ 
    C c; A *pa = &c; B *pb = &c; C *pc = &c; 
    printf("A: %p, B: %p, C: %p\n", pa, pb, pc);
}

Результат, который я получил (с использованием 32-битной Visual Studio 2010):

A: 0018F748, B: 0018F74C, C: 0018F748

Таким образом, для множественного наследования при преобразовании между связанными указателями компилятор должен вставить некоторую арифметику указателей, чтобы преобразования были правильными.

Вот почему, даже если вы имеете дело только с указателями, вы не можете объявить наследование.

Что касается того, почему это было бы полезно, это улучшит время компиляции, если вы действительно хотите использовать ковариантные типы возвращаемых значений вместо использования приведения.Например, это не скомпилируется:

class RA;
class A             { public: virtual RA *fooRet(); };
class RB;
class B : public A  { public: virtual RB *fooRet(); };

Но это будет:

class RA;
class A             { public: virtual RA *fooRet(); };
class RA { int x; };
class RB : public RA{ int y; };
class B : public A  { public: virtual RB *fooRet(); };

Это полезно, когда у вас есть объекты типа B (не указатели или ссылки).В этом случае компилятор достаточно умен, чтобы использовать прямой вызов функции, и вы можете использовать возвращаемый тип RB* напрямую, без приведения.В этом случае обычно я делаю тип возвращаемого значения RA * и выполняю статическое приведение возвращаемого значения.

Я не думаю, что это полезно.Учитывать:вы определили класс Bar:

class Bar {
public:
    void frob();
};

Теперь вы объявляете класс Foo:

class Foo;

Все, что вы можете сделать с Foo, — это создать на него указатель.Теперь предположим, что вы добавляете информацию, которая Foo получено из Bar:

class Foo: public Bar;

Что вы можете сделать сейчас, чего не могли сделать раньше?Я думаю, что все, что вы можете сделать, это принять указатель на Foo и привести его к указателю на Bar, затем используйте этот указатель.

void frob(Foo* f) {
    Bar *b = (Bar)f;
    b->frob();
}

Однако вы должны были сгенерировать указатель в другом месте, поэтому вы могли бы просто принять указатель на Bar вместо.

void frob(Bar* b) {
    b->frob();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top