您现在的位置是:首页 >技术杂谈 >C++的类声明、前置声明、定义及各自优势、使用场景网站首页技术杂谈
C++的类声明、前置声明、定义及各自优势、使用场景
当一个class的成员变量为另外一个类的指针类型的时候,此时,我们可以不在头文件中来包含这个类的头文件,即不使用include“.h”,而是采用前置声明的形式,对这个类进行一个类型的前置声明。
类的前置声明形式上:只有class关键字+类名,后面不带任何符号,即{}
白话解释:哎,b是个类,我要用它,你别管,它在某个地方是个真实的存在(也可能不存在,我就tm忽悠你的), 总之你不要管,我要用它”。这样跟编译器打过招呼之后,就能保证代码编译通过。
如下代码段
//文件a.h
class B;
class A
{
public:
B *b;
};
//文件b.h
class B
{
public:
void func1();
};
a.h和b.h这两个文件中定义了两个类分别是A和B。其中A类有一个成员b是Class B的指针,这个时候可使用类前置声明
前置声明省去了#include的处理、降低文件之间的编译依赖,从而避免不必要的编译时间浪费。
利用前置声明的利弊
优点:避免#include, 避免修改一个被包含的头文件,导致整个文件重新编译。
缺点:(摘自Google)
如果前置声明关系到模板,typedefs, 默认参数和 using 声明,就很难决定它的具体样子了。
很难判断什么时候该用前置声明,什么时候该用 #include, 特别是涉及隐式转换运算符的时候。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义。
前置声明函数或模板有时会害得头文件开发者难以轻易变动其 API. 就像扩大形参类型,加个自带默认参数的模板形参等等。
前置声明来自命名空间 std:: 的 symbol 时,其行为未定义。
仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员),后者会变慢且复杂起来。
还没有实践证实前置声明的优越性。
前置声明作用:
告诉编译器,这个类在其它地方定义,真正使用类的定义的时候才进行编译。
Note:类A在编译的时候不需要拿到类B的定义是因为这里面定义的是指针,而对于指针是不需要定义就可以进行内存布局的,在编译A的类的声明的时候,在进行内存布局的时候是不需要拿到B的定义的
但是在A的成员函数中如果使用这个指针b,则就需要在A对应的.cpp文件中引入B的定义。也就是在.cpp中需要include b.h
这里面对于前置声明,还需要注意一个事情,就是如果另外有一个类,这个类使用了类A,并且通过类A访问使用了A的成员变量b,比如下边这种情况
//文件c.h
#include "a.h"
class C
{
public:
void print(){
a.b->func1();
}
A a;
};
编译时,讲会报错:不允许使用不完全(incomplete)类型A。
Because:C中访问了a成员b的成员函数。所以,此时也需要类B的完整定义,即include
比较正规的做法就是在C的头文件中也引入B的定义,这样编译器就能找到B的定义,从而编译通过
如法1
法1
//文件c.h
#include "a.h"
#include "b.h"
class C
{
...
}
法2
//文件a.h
#include "b.h"
//class B;
class A
{
public:
B *b;
};
法2在类A的头文件中引入b.h头文件
这种写法,就是放弃了前置声明,直接在A中引入B的头文件,一次性将B的全部声明引入到A的头文件中。
这样也可以编译通过,但是会带来一个问题。就是以后所有引用A的头文件的其他文件,在编译的时候都需要编译一遍B,而在这些文件中可能压根就没有访问成员变量b。也就是说这些文件其实根本就不需要B的定义。这样会导致编译这些文件的时候,还需要编译一遍不相关的类B。这会导致编译缓慢
所以,这里面正确的做法是采用前一种方法。也就是说,头文件中能尽量少的引入头文件就应该尽量少的引用收文件,能用前置声明的就应该尽量使用前置声明
几种可以使用前置声明的 地方
**
以指针或引用的形式来引用类型
**
class A;
class B
{
A *pa;//ok
A &ra;//ok
A * f(const A * pa);//ok
A & f(const A & pa);//ok
};
友元
class A;
class B
{
friend A; //ok
friend class A;//ok
};
作为返回值或者参数
class A;
class B
{
void f(A a);//ok
A g();//ok
};
**
不可以使用前置声明的类型
**
使用完整的类引用
class A;
class B
{
//A a; //error
};
被当作父类
class E;
class F: public E //error VC6.0--'E': base class undefined Qt5.6-invalid use of incomplete of 'class C'
{};