前置声明

来源:互联网 发布:查看linux系统版本 编辑:程序博客网 时间:2024/04/28 20:16

作者:朱金灿

http://blog.csdn.net/clever101

 

    类的前置声明(forward declaration)和包含头文件(#include)的区别常常会迷惑我们,特别是涉及两个类相互包含的时候。因此我们有必要搞清楚二者的区别以及二者的适用场合。

 

首先我们需要问一个问题是:为什么两个类不能互相包含头文件?所谓互相包含头文件,我举一个例子:我实现了两个类:图层类CLayer和符号类CSymbol,它们的大致关系是图层里包含有符号,符号里定义一个相关图层指针,具体请参考如下代码(注:以下代码仅供说明问题,不作为类设计参考,所以不适宜以此讨论类的设计,编译环境为Microsoft Visual C++ 2005,,Windows XP + sp2,以下同):

     view plaincopy to clipboardprint?
//Layer.h  
// 图层类  
#pragma once  
 
#include "Symbol.h"  
 
class CLayer  
{  
public:  
    CLayer(void);  
    virtual ~CLayer(void);  
    void CreateNewSymbol();  
 
private:  
 
    CSymbol*    m_pSymbol;  // 该图层相关的符号指针  
};  
 
 
// Symbol.h  
// 符号类  
#pragma once  
 
#include "Layer.h"  
 
 
class CSymbol  
{  
public:  
    CSymbol(void);  
    virtual ~CSymbol(void);  
 
public:  
 
    CLayer *m_pRelLayer; // 符号对应的相关图层  
};  
 
 
// TestUnix.cpp : 定义控制台应用程序的入口点。  
//  
 
#include "stdafx.h"  
#include "Layer.h"  
#include "Symbol.h"  
 
void main( void )  
{  
     CLayer MyLayer;  
 

//Layer.h
// 图层类
#pragma once

#include "Symbol.h"

class CLayer
{
public:
 CLayer(void);
 virtual ~CLayer(void);
 void CreateNewSymbol();

private:

 CSymbol* m_pSymbol;  // 该图层相关的符号指针
};


// Symbol.h
// 符号类
#pragma once

#include "Layer.h"


class CSymbol
{
public:
 CSymbol(void);
 virtual ~CSymbol(void);

public:

    CLayer *m_pRelLayer; // 符号对应的相关图层
};


// TestUnix.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Layer.h"
#include "Symbol.h"

void main( void )
{
     CLayer MyLayer;

}
 

    

   

现在开始编译,编译出错,出错信息如下:

1>正在编译...

1>TestUnix.cpp

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C2143: 语法错误: 缺少“;”(在“*”的前面)

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

1>Layer.cpp

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C2143: 语法错误: 缺少“;”(在“*”的前面)

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

1>f:/mytest/mytest/src/testunix/symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

1>Symbol.cpp

1>f:/mytest/mytest/src/testunix/layer.h(18) : error C2143: 语法错误: 缺少“;”(在“*”的前面)

1>f:/mytest/mytest/src/testunix/layer.h(18) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

1>f:/mytest/mytest/src/testunix/layer.h(18) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int

 

    现在让我们分析一下编译出错信息(我发现分析编译信息对加深程序的编译过程的理解非常有好处)。首先我们明确:编译器在编译文件时,遇到#include   "x.h"时,就打开x.h文件进行编译,这相当于把x.h文件的内容放在#include   "x.h"处。编译信息告诉我们:它是先编译TestUnix.cpp文件的,那么接着它应该编译stdafx.h,接着是Layer.h,如果编译Layer.h,那么会编译Symbol.h,但是编译Symbol.h又应该编译Layer.h啊,这岂不是陷入一个死循环? 呵呵,如果没有预编译指令,是会这样的,实际上在编译Symbol.h,再去编译Layer.h,Layer.h头上的那个#pragma once就会告诉编译器:老兄,这个你已经编译过了,就不要再浪费力气编译了!那么编译器得到这个信息就会不再编译Layer.h而转回到编译Symbol.h的余下内容。当编译到CLayer *m_pRelLayer;这一行编译器就会迷惑了:CLayer是什么东西呢?我怎么没见过呢?那么它就得给出一条出错信息,告诉你CLayer没经定义就用了呢?在TestUnix.cpp中#include "Layer.h"这句算是宣告编译结束(呵呵,简单一句弯弯绕绕不断),下面轮到#include "Symbol.h",由于预编译指令的阻挡,Symbol.h实际上没有得到编译,接着再去编译TestUnix.cpp的余下内容。

 

     当然上面仅仅是我的一些推论,还没得到完全证实,不过我们可以稍微测试一下,假如在TestUnix.cpp将#include "Layer.h"和#include "Symbol.h"互换一下位置,那么会不会先提示CSymbol类没有定义呢?实际上是这样的。当然这个也不能完全证实我的推论。

 

    照这样看,两个类的互相包含头文件肯定出错,那么如何解决这种情况呢?一种办法是在A类中包含B类的头文件,在B类中前置盛明A类,不过注意的是B类使用A类变量必须通过指针来进行,具体见拙文:类互相包含的办法。为何不能前置声明只能通过指针来使用?通过分析这个实际上我们可以得出前置声明和包含头文件的区别。我们把CLayer类的代码改动一下,再看下面的代码:
     view plaincopy to clipboardprint?
// 图层类  
 
//Layer.h  
 
#pragma once  
 
//#include "Symbol.h"  
 
class CSymbol;  
 
class CLayer  
{  
public:  
    CLayer(void);  
    virtual ~CLayer(void);  
 
//  void SetSymbol(CSymbol *pNewSymbol);  
    void CreateNewSymbol();  
 
private:  
 
    CSymbol*    m_pSymbol; // 该图层相关的符号  
 
//  CSymbol m_Symbol;  
 
};  
// Layer.cpp  
 
 
#include "StdAfx.h"  
#include "Layer.h"  
 
CLayer::CLayer(void)  
{  
    m_pSymbol = NULL;  
}  
 
CLayer::~CLayer(void)  
{  
    if(m_pSymbol!=NULL)  
    {  
        delete m_pSymbol;  
        m_pSymbol=NULL;       
    }  
}  
 
 
 
 
void CLayer::CreateNewSymbol()  
{  
      
}  
      
// 图层类

//Layer.h

#pragma once

//#include "Symbol.h"

class CSymbol;

class CLayer
{
public:
 CLayer(void);
 virtual ~CLayer(void);

// void SetSymbol(CSymbol *pNewSymbol);
 void CreateNewSymbol();

private:

 CSymbol* m_pSymbol; // 该图层相关的符号

// CSymbol m_Symbol;

};
// Layer.cpp


#include "StdAfx.h"
#include "Layer.h"

CLayer::CLayer(void)
{
 m_pSymbol = NULL;
}

CLayer::~CLayer(void)
{
 if(m_pSymbol!=NULL)
 {
        delete m_pSymbol;
  m_pSymbol=NULL;  
 }
}

 


void CLayer::CreateNewSymbol()
{
   
}
    
 

   

然后编译,出现一个编译警告:>f:/mytest/mytest/src/testunix/layer.cpp(16) : warning C4150: 删除指向不完整“CSymbol”类型的指针;没有调用析构函数

1>                            f:/mytest/mytest/src/testunix/layer.h(9) : 参见“CSymbol”的声明
 
       看到这个警告,我想你一定悟到了什么。下面我说说我的结论:类的前置声明和包含头文件的区别在于类的前置声明是告诉编译器有这种类型,但是它没有告诉编译器这种类型的大小、成员函数和数据成员,而包含头文件则是完全告诉了编译器这种类型到底是怎样的(包括大小和成员)。这下我们也明白了为何前置声明只能使用指针来进行,因为指针大小在编译器是确定的。上面正因为前置声明不能提供析构函数信息,所以编译器提醒我们:“CSymbol”类型的指针是没有调用析构函数。如何解决这个问题呢?在Layer.cpp加上#include "Symbol.h"就可以消除这个警告。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/clever101/archive/2009/10/31/4751717.aspx

 

 

 

 

 

 

 

 

 

 

 

 

在软件设计过程中,到处充满着陷阱,程序结构设计的不合理,会让你花费成倍的时间在一个死胡同里苦苦的寻找出路。下面详细解释一下如何解决常见的类相互包含的解决方法,使得模块的完全独立。

假设有两个类classA 、classB。classA 中包含classB。例如

#include "classB.h"

#include <iostream>

using namespace std;

class classA

{

private:

  classB cb;

public:

  int show(int a);

};

int classA::show(int a)

{

  cout << a << endl;

}

在classB中有一个函数需要获取classA的某属性a.例如

class classA;           // 前置声明


class classB

{

private:

public:

  void showA();

};

void classB:showA()

{

  //调用classA中的show函数 (我们需要完成的功能)

}

如何实现这个功能呢

最简单的办法是在classB的头文件中添加一行前置声明,

 class classA;

然后在classB的定义文件中添加一个包含头文件

 include "classA.h"

这样classB便可以声明并且使用classA的对象。

这样看起来似乎能解决问题,但是不知不觉中就陷入了陷阱当中。因为我们一厢情愿的在classB中include了一个它的父类的头文件。

这实在不是一个明智之举,一旦classB需要做成一个单独的模块,如何解耦将会是一个问题。

 


由此分析可以看出,我们不应该在classB中去包含一个classA的头文件。

新的解决方法产生了,我们换一种思路,于是可以这样考虑。

在classB中提供一个函数接口,接收一个函数指针,而classA中有一个注册函数,这个注册函数会调用classB提供的这个接口,把

classA的一个函数地址注册到classB中去,这样在调用classB::showA()函数时,可以让它实际去执行这个函数指针所指向的

classA::show()函数。实现方法如下。

========================classB.h===============================

class classB

{

private:

  int (classA::*ptr)(int);      // 声明一个classA的成员函数指针 ,函数参数和返回值类型都为int

  classA *ca; 

public:

  void showA();

  // 提供注册的函数接口,由于类成员函数指针是相对于类的偏移量,所以必须传递一个类指针才能正确调用函数指针所指向的方法

  void setPtr( classA *a, int (classA::*p) (int) );

};

void classB:showA()

{

  //调用classA中的show函数 (我们需要完成的功能)

  (ca->*p)(1);                     // 实现调用,注意前面一对括号不能少,否则会因为操作符的优先级导致链接错误

}

void classB::setPtr( classA *a, int (classA::*p)(int) )

{

  ca = a;

  ptr = p;

}

========================classA.h===============================

#include "classB.h"

#include <iostream>

using namespace std;

class classA

{

private:

  classB cb;

public:

  int show(int a);

  void regist();

  classB getB();

};

classB getB()

{

  return cb;

}

int classA::show(int a)

{

  cout << a << endl;

}

void classA::regist()       // 注册函数

{

  cb.setPtr(&classA::show);   // 传入函数指针

}

========================main.cpp===============================

int main()

{

   A a;

   a.regist();

   a.getB().showA();  // 于是当调用A.getB().showA()时,实际调用的是A中的show()成员函数。


}

当然到目前为止并没有解决问题,大家仔细看了就会发现这种方式仍然需要在classB中include "classA.h",因为

在classB中不仅需要声明classA的类型指针,还要声明classA的成员函数指针。那该怎么办呢?

template将会帮助我们解决这个问题!

我们可以用template 来替代classB中的classA类型。

对classB.h进行修改

======================修改后的classB.h===============================

template < class T>

class classB

{

private:

  int ( T::*ptr)(int);      // 声明一个classA的成员函数指针 ,函数参数和返回值类型都为int

  T *ca; 

public:

  void showA();

  // 提供注册的函数接口,由于类成员函数指针是相对于类的偏移量,所以必须传递一个类指针才能正确调用函数指针所指向的方法

  void setPtr( T*a, int ( T::*p) (int) );

};

template < class T>

void classB<T>:showA()

{

  //调用classA中的show函数 (我们需要完成的功能)

  (ca->*p)(1);                     // 实现调用,注意前面一对括号不能少,否则会因为操作符的优先级导致链接错误

}

template < class T>

void classB<T>::setPtr( T*a, int (T::*p)(int) )

{

  ca = a;

  ptr = p;

}

========================修改后的classA.h===============================

#include "classB.h"

#include <iostream>

using namespace std;

class classA

{

private:

  classB cb;

public:

  int show(int a);

  void regist();

  classB<classA> getB();

};

classB<classA> getB()

{

  return cb;

}

int classA::show(int a)

{

  cout << a << endl;

}

void classA::regist()       // 注册函数

{

  cb.setPtr(&classA::show);   // 传入函数指针

}

注意:因为模板不是函数,它们不能单独编译,模板必须与特定的模板实例化请求一起使用。最简单的方法是将所有模板信息放在一个头文件重,并在要使用这些模板的文件中包含该头文件。如果编译器实现了新的export关键字, 则可以将模板方法定义在一个独立的文件中,条件是每个模板声明都以export开始。

例如

export template <class T>

class classB

{

...

};

至此我们就完全可以把类classB完全分离出来,即使再复杂的逻辑我们也可以用类似的方法来解决。降低模块间的耦合度。设计健壮性强、易扩展的程序来。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/liuchangyu23/archive/2009/12/11/4984387.aspx

 

 

 

 

 


原创粉丝点击