关于前置声明

在计算机编程中,一个前置声明是指在程序员尚未给出完整定义之前对一个标示符(一个类型、一个变量或者一个函数)的声明。一个很简单的例子就是我们在函数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了,因为后者的宏已经打开,即使没有宏控制,也会导致两个头文件循环包含的问题。这种情况下,前置声明就变得很有用。

           前置声明不能包治百病,在前置声明和具体定义之间的涉及标示符(变量、类、函数等等)实现细节的使用都是非法的,会导致编译错。所以好的编程习惯还是尽量将具体定义放在源文件中;另外,尽量不要在头文件中包含头文件,对于在大型工程中扩展模块功能有其如此,在原有头文件中添加新的头文件往往会导致示例代码中的问题,因为原有头文件可能会被多个其他的文件包含,牵一发而动全身,在源文件中做的所有扩展专属当前的源文件,不会影响到原有代码的其他部分。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值