C++中要用类A时,什么时候#include "A.h",什么时候用class A申明?
7 个回答
通常来说,你都不需要主动去写class A这种前置声明。应该仅当出现了头文件循环依赖导致编译失败的时候,才去考虑去写前置声明!
头文件循环依赖,就是说两个头文件互相include了对方,这样编译会出问题。举个例子。
有a.h(里面用了类型B的指针,所以include了b.h):
#pragma once
#include "b.h"
class A {
public:
A():_b(nullptr) {}
~A() {}
void set_b(B* b) {
_b = b;
}
B* get_b() {
return _b;
}
private:
B* _b;
};
有b.h:
#pragma once
#include "a.h"
class B {
public:
B(int i):_i(i) {}
~B() {}
int i() {
return _i;
}
void foo(A& a) {
B* b = a.get_b();
b->_i += _i;
}
private:
int _i;
};
有main.cpp (包含main函数)
#include "a.h"
#include "b.h"
#include <iostream>
using namespace std;
int main() {
A a;
B b(3);
a.set_b(&b);
B b2(7);
b2.foo(a);
cout << a.get_b()->i() << endl;
return 0;
}
编译main.cpp失败,报错:
./b.h:13:14: error: unknown type name 'A'
void foo(A& a) {
^
1 error generated.
修改方法,因为a.h中只出现了类型B的指针,而未调用其成员函数或成员变量,故可以修改a.h删除include "b.h",增加类型B的前置声明。
#pragma once
class B; // 前置声明!
class A {
public:
A():_b(nullptr) {}
~A() {}
void set_b(B* b) {
_b = b;
}
B* get_b() {
return _b;
}
private:
B* _b;
};
编译main.cpp通过。
当前前置声明也不是万能的解药,请注意前面的加粗黑字:
因为a.h中只出现了类型B的指针,而未调用其成员函数或成员变量,故……
换言之,如果a.h中使用了类型B的成员函数,则无法通过更改为前置声明的方式,来让编译通过。
比如:
#pragma once
#include <iostream>
class B;
class A {
public:
A():_b(nullptr) {}
~A() {}
void set_b(B* b) {
std::cout<< b->i() << std::endl; // !使用了B的成员函数
_b = b;
}
B* get_b() {
return _b;
}
private:
B* _b;
};
编译报错:
./a.h:10:22: error: member access into incomplete type 'B'
std::cout<< b->i() << std::endl;
^
./a.h:3:7: note: forward declaration of 'B'
class B;
这时候只能老老实实地改代码,重新梳理并设计类A和类B的关系!
看起来有点乱,记不住?其实不难理解,因为对C++而言,不管是什么指针,它的大小都是确定的。所以只要a.h中只是出现B的指针(或引用)而没有调用其具体的成员函数,C++编译器是可以不去在此时理解B的具体定义的(故只添加class B的声明即可),一旦a.h中用到了B的成员函数,则不然。
在一些情况下必须要用include的形式,常见的情况比如当前文件中某个类的类声明使用了class A
的value(即非指针或引用),再比如这个类继承自class A
... 总之在依赖于class A
的完整声明的情况下必须要用include,如果用了前置声明编译器会提示incomplete type
还有一些情况下必须要用前置声明:有一些情况下只使用include会产生头文件的循环引用,这时如果加了#ifndef
保护header会产生一些乍一看莫名其妙的编译错误,比如a.h
(定义了class A
)和b.h
(定义了class B
)相互引用,在一个cc文件中引用a.h
的话编译器会提示在class B
中找不到class A
的定义。在这种情况下必须使用前置声明(以及从类的设计上考虑这种循环引用是否是必要的)
当然还有更多的情况两种形式都可以,这时使用前置声明的主要优势是可以减少修改header时重新编译所需要的时间:比如说在很多编译系统下,如果b.h
inlcude了a.h
,c.h
inlcude了b.h
,那么当a.h
发生变动的时候会触发所有include了c.h
的文件的重新编译,尽管这些文件可能并没有真正的用到class A
,而最坏情况下整个项目都会被重新编译,这在某些中大型项目中可能是灾难性的(从编译时间的角度讲)。而使用前置声明的主要劣势在于一定程度上降低了代码的可读性(include时很容易找到声明的位置)以及可能增加潜在的维护成本(我个人认为在一定程度上是可控的),知乎上已经有回答对此说明了很清楚了:
各个大公司的code style中有的倾向于前置声明,有的倾向于使用include,这个问题并没有一个明确的答案,在团队中能够达成一致就好