类前置声明的使用

 

定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样:

一编译,就出现了一个互包含的问题了,这时就有人跳出来说,这个问题的解决办法可以这样,在a.h文件中声明类B,然后使用B的指针。

然后,问题就解决了。

但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。

类的前置声明是有许多的好处的。

我们使用前置声明的一个好处是,从上面看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。

另外一个好处是减小类A的大小,上面的代码没有体现,那么我们来看下:

我们看上面的代码,类B的大小是12(在32位机子上)。

如果我们在类A中包含的是B的对象,那么类A的大小就是12(假设没有其它成员变量和虚函数)。如果包含的是类B的指针*b变量,那么类A的大小就是4,所以这样是可以减少类A的大小的,特别是对于在STL的容器里包含的是类的对象而不是指针的时候,这个就特别有用了。

在前置声明时,我们只能使用的就是类的指针和引用(因为引用也是居于指针的实现的)。

那么,我问你一个问题,为什么我们前置声明时,只能使用类型的指针和引用呢?

如果你回答到:那是因为指针是固定大小,并且可以表示任意的类型,那么可以给你80分了。为什么只有80分,因为还没有完全回答到。

想要更详细的答案,我们看下下面这个类:


我们看下上面定义的这个类A,其中_b变量和get_b()函数是新增加进这个类的。

那么我问你,在增加进_b变量和get_b()成员函数后这个类发生了什么改变,思考一下再回答。

好了,我们来列举这些改变:

第一个改变当然是增加了_b变量和get_b()成员函数;

第二个改变是这个类的大小改变了,原来是4,现在是8。

第三个改变是成员_a的偏移地址改变了,原来相对于类的偏移是0,现在是4了。

上面的改变都是我们显式的、看得到的改变。还有一个隐藏的改变,想想是什么。。。

这个隐藏的改变是类A的默认构造函数和默认拷贝构造函数发生了改变。

由上面的改变可以看到,任何调用类A的成员变量或成员函数的行为都需要改变,因此,我们的a.h需要重新编译。

如果我们的b.h是这样的:

那么我们的b.h也需要重新编译。

如果是这样的:

那么我们的b.h就不需要重新编译。

像我们这样前置声明类A:

class A;

是一种不完整的声明,只要类B中没有执行需要了解类A的大小或者成员的操作,则这样的不完整声明允许声明指向A的指针和引用。

而在前一个代码中的语句

A a;

是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含a.h来获得类A的大小,同时也要重新编译类B。

再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是只要这个声明没有执行需要了解类A的大小或者成员的操作就可以了,所以声明成指针或引用是没有执行需要了解类A的大小或者成员的操作的。

 

 

另外:

在书写C++代码的过程当中,人们有时会用到一个叫类前置声明的东西。那么使用这个类前置声明对于程序员来讲有什么好处呢,我们什么时候可以利用这个类前置声明呢?下面就用一个具体的例子来解释类前置声明的用法。

      假设我们在一个叫OtherClass.h的文件中定义了一些类,其代码如下所示:

 

  1. #ifndef OTHER_TEST_H  
  2. #define OTHER_TEST_H  
  3.   
  4. class CClassA  
  5. {  
  6.   
  7. };  
  8.   
  9. class CClassB  
  10. {  
  11.   
  12. };  
  13.   
  14. #endif  

 

      现在我们新写叫CTest的类,与上面两个类形成组合/聚合关系,即我们新写的类中含有上面这些类的指针/引用。其头文件Test.h如下所示:

 

  1. #ifndef TEST_H  
  2. #define TEST_H  
  3.   
  4. class CClassA;  
  5. class CClassB;  
  6.   
  7. class CTest  
  8. {  
  9. public:  
  10.     CTest();  
  11.     ~CTest();  
  12.   
  13. private:  
  14.     void AFunction( const CClassA * pA, const CClassB * pB );  
  15.     void AnotherFunction( const CClassA & pA, const CClassB &pB );  
  16.   
  17. private:  
  18.     CClassA * m_pCA;  
  19.     CClassB * m_pCB;  
  20. };  
  21.   
  22. #endif  

 

      注意到上面的头文件中没有包含OtherClass.h这个头文件,而是作了一个类的前置声明。其对应的源文件Test.cpp如下所示:

 

  1. #include "OtherClass.h"  
  2. #include "Test.h"  
  3.   
  4. CTest::CTest()  
  5. {  
  6.   
  7. }  
  8.   
  9. CTest::~CTest()  
  10. {  
  11.   
  12. }  
  13.   
  14. void CTest::AFunction( const CClassA * pA, const CClassB * pB )  
  15. {  
  16.   
  17. }  
  18.   
  19. void CTest::AnotherFunction( const CClassA & pA, const CClassB &pB )  
  20. {  
  21.   
  22. }  

 

      这样一来,我们CTest类的实现就完成了。下面我们在其他的文件中调用这个CTest类,这里以main函数调用为例,代码如下所示:

 

  1. #include <iostream>  
  2. #include "Test.h"  
  3.   
  4. int main()  
  5. {  
  6.     CTest test;  
  7.     return 0;  
  8. }  

 

      我们可以发现,在使用CTest这个类的时候,我们只包含了Test.h这个头文件,而没有包含OtherClass.h这个头文件,前置声明的好处就在这里体现了。在编程规范中,在一个头文件中包含另一个大的头文件着实不是一种良好的风格,尤其对于比较大的工程项目更是如此,而使用前置声明便很好的避免了这种情况。

      不过注意,前置声明的类,一般用在新写的类的私有方法或属性里,且必须为指针引用类型;如果是用在公有方法或属性,其他的文件在调用这个类时,还是要包含相关的头文件。

posted on 2012-07-23 14:20  很多不懂呀。。  阅读(2436)  评论(1编辑  收藏  举报

导航