Форвардная декларация против Include
Рассмотрим следующие два сценария (Отредактировано только для того, чтобы закончить весь вопрос и сделать его более понятным)
Случай 1: (не компилируется, как указано ниже)
//B.h
#ifndef B_H
#define B_H
#include "B.h"
class A;
class B {
A obj;
public:
void printA_thruB();
};
#endif
//B.cpp
#include "B.h"
#include <iostream>
void B::printA_thruB(){
obj.printA();
}
//A.h;
#ifndef A_H
#define A_H
#include "A.h"
class A {
int a;
public:
A();
void printA();
};
#endif
//A.cpp
#include "A.h"
#include <iostream>
A::A(){
a=10;
}
void A::printA()
{
std::cout<<"A:"<<a<<std::endl;
}
//main.cpp
#include "B.h"
#include<iostream>
using namespace std;
int main()
{
B obj;
obj.printA_thruB();
}
Случай 2: (единственные изменения... работают без ошибки компиляции)
//B.h
#include "A.h" //Add this line
//class A; //comment out this line
Предположим, что оба A.cpp и B.cpp выполнены вместе. Имеют ли два вышеуказанных сценария какие-либо различия? Есть ли причина предпочесть один метод над другим?
Изменить:
Итак, как мне сделать сценарий 1?
Ответы
Ответ 1
Случай 1 приведет к ошибке "неполного типа" при компиляции B.cpp. Поскольку класс B содержит объект класса A, определение (и, в частности, размер) класса A требуется для завершения перед определением класса B.
В качестве альтернативы вы могли бы сделать some_variable указателем или ссылкой на класс A, и в этом случае ваше прямое объявление будет достаточным в B.h. Вам все равно нужно полное определение A в B.cpp(если вы действительно использовали функции/данные A-члена).
Ответ 2
Вперед объявление не является заменой включения заголовка заголовка.
Как следует из самого имени, прямое объявление - это просто Declaration
, а не определение.
Итак, вы объявите компилятору, что это класс, и я просто объявляю его здесь и предоставит вам определение, когда я его буду использовать. Итак, обычно вы forward declare
в файле заголовка и #include
в .cpp файле, где вы используете членов объявленного вперед класса.
Таким образом, то, что вы делаете, везде, где вы включаете заголовочный файл, будет просто объявление для класса вместо всего содержимого #included
...
Но, сказав, что, когда компилятор требует определения класса, он должен быть #included
..
Итак, в вашем случае A obj;
требуется определение class A
и, следовательно, вы должны #include
..
Я сам задал аналогичный вопрос здесь и другой похожий вопрос, который также имеет приятный ответ...
Надеюсь, что это поможет.
Ответ 3
Вам нужно использовать форвардные объявления в случаях, когда у вас есть классы, которые ссылаются друг на друга.
//A.h
class B;
class A {
B* someVar;
}
//B.h
#include <A.h>
class B {
A* someVar;
}
Но нет никакой пользы для этого в случае, если вы выложили.
Ответ 4
Подумайте, как компилятор. Чтобы создать A
внутри B
, компилятор должен знать, как построить A
, и единственный способ сделать это - получить полное определение. В переднем объявлении компилятор сообщает, что класс A
существует без описания того, как он выглядит; это достаточно для определения указателя или ссылки. Когда придет время использовать этот указатель или ссылку, потребуется полное определение класса.
Ответ 5
Если вы хотите изобразить some_variable в качестве указателя, то часто рекомендуемая практика заключается в том, чтобы использовать форвардные объявления, когда это возможно, чтобы избежать накладных расходов включений и более длительных сроков компиляции.
Я использую лучшие практики, но мне очень нравится использовать IDE, которые имеют приятные функции навигации и пересылки кода, вызывают проблемы там, по крайней мере, с Netbeans. Всякий раз, когда я пытаюсь перейти к объявлению типа, я всегда оказываюсь в файле forward, а не в файле .h, содержащем фактическое объявление. Я готов принять дополнительное время компиляции для удобства навигации. Возможно, это просто проблема с Netbeans:)
.. о да.. Если вы посмотрите на связанные вопросы справа от вашего вопроса, вы найдете много дополнительной информации о форвардных декларациях.
Ответ 6
В случае 1 компилятор будет жаловаться на "неполный тип" для класса B, поскольку класс B содержит объект класса A, и вы не указали B какой-либо детали класса A, поэтому компилятор не может решить размер объекта B.
В вашем случае вы можете использовать A& obj
или A* obj
вместо A obj
, так как размер ссылки/указателя const (4/8 для 32-битного/64-битного процессора).