在计算机编程中,一个前置声明是指在程序员尚未给出完整定义之前对一个标示符(一个类型、一个变量或者一个函数)的声明。一个很简单的例子就是我们在函数A中使用了函数B,但是函数B的声明在函数A之后,这个时候,就需要对函数B进行前置声明,实际上就是在函数A之前提供一个函数B的原型(prototype)。这种现象其实在C语言编程中我们已经习以为常了,在C++中亦是如此,只不过在编写较大规模程序的时候,由于定义了较多的类,而这些类之间有可能是互相依赖的,换言之,类与类之间会互相引用,包括对成员函数的引用等等。笔者也经常在编写复杂程序的过程中遇到类似的问题,以下从一个简单例子出发,对此问题加以简要阐述,以为未来参考之需。
示例代码:
test1.h: #ifndef test1_INCLUDED #define test1_INCLUDED class C1; #include "test2.h" class C1{ ....... void func1(C2* c2){ // c2->func2(); } }; #endif
test2.h: #ifndef test2_INCLUDED #define test2_INCLUDED class C2{ ....... void func1(C1* c1){ // c1->func2(); } }; #endif
test.cxx: #include "test1.h" int main(){ C1 inst_c1(...); C2 inst_c2(...); ...... return 0; }
两个头文件中的类存在互相引用,编译器在预处理过程中将头文件的内容include到源文件中,而头文件是按序include的,test.cxx包含头文件test1.h,而在包含test1.h之前,首先将test2.h包含进来,因此在最终的与处理之后的源文件中,类C2定义在C1之前,而C2用到了未被声明的类C1。因此在包含test2.h之前先对类C1进行前置声明,以避免编译时出现变量在作用域内未声明的错误。反之如果在源文件中包含的是test2.h,也需要在test2.h中包含test1.h,同时在包含头文件之前前置声明类C2。前置声明时还需注意一些小细节,例如声明的是一个类模板的话,需要将类模板的完整原型加上。
前置声明的目的是告诉编译器这个标示符(包括变量或者函数等)虽然现在还没有定义,但是后面会有的;未定义全也就表示标示符的size是未知的,所以一般在前置声明之后具体定义之前,使用的都是该标示符的指针,如类要定义成指针类型,因为类大小虽然不确定,但是类指针的大小和所有指针一样,基本上都是四个字节,大小是确定的。另一方面,这样的用法这也就意味着前置声明实际上是有限制条件的,如果在当前代码中加入被注释的粗体部分的代码,编译时会发现如下错误:
test2.h: In member function ‘void C2::func1()’:
test2.h:19: error: invalid use of undefined type ‘struct C1’
test1.h:5: error: forward declaration of ‘struct C1’
test2.h:20: error: invalid use of undefined type ‘struct C1’
test1.h:5: error: forward declaration of ‘struct C1’编译器能找到未定义类的前置声明信息,但是不能确定该类定义中的具体信息,例如成员函数,导致"invalid use"。这种情况下较好的解决方案是将涉及具体定义的部分移到源文件中,例如将类的成员函数定义在源文件中(注意添加inline修饰符),类的定义在头文件中已经包含,所以所有类的定义都是完整的。
小结:前置声明是在编程实践中经常会用到的一个手段,一定程度上克服了include头文件这种方式的弊端。出于避免重复包含头文件以及循环包含等问题,编程中会用宏来控制头文件的包含关系,而在某些情况下,包含头文件是无法解决声明的问题的,例如我们上述简单示例中,在test2.h中已经无法再include头文件test1.h了,因为后者的宏已经打开,即使没有宏控制,也会导致两个头文件循环包含的问题。这种情况下,前置声明就变得很有用。
前置声明不能包治百病,在前置声明和具体定义之间的涉及标示符(变量、类、函数等等)实现细节的使用都是非法的,会导致编译错。所以好的编程习惯还是尽量将具体定义放在源文件中;另外,尽量不要在头文件中包含头文件,对于在大型工程中扩展模块功能有其如此,在原有头文件中添加新的头文件往往会导致示例代码中的问题,因为原有头文件可能会被多个其他的文件包含,牵一发而动全身,在源文件中做的所有扩展专属当前的源文件,不会影响到原有代码的其他部分。