В C ++ возможно ли переслать объявление класса как наследующего от другого класса? - PullRequest
59 голосов
/ 29 января 2010

Я знаю, что могу сделать:

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, что может значительно ускорить компиляцию.

Ответы [ 4 ]

39 голосов
/ 29 января 2010

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

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

39 голосов
/ 29 января 2010

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

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

18 голосов
/ 13 апреля 2012

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

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

#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 * и выполняю статическое приведение возвращаемого значения.

0 голосов
/ 02 февраля 2010

Не думаю, что это полезно. Обратите внимание: вы определили класс, 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...