C++中前置声明的应用与陷阱
前置声明的使用
有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是强耦合关系,类A要引用B的对象,类B也要引用类A的对象。好的,不难,我的第一直觉让我写出这样的代码:
// A.h
#include "B.h"
class A
{
B b;
public:
A(void);
virtual ~A(void);
}; //A.cpp
#include "A.h"
A::A(void)
{
} A::~A(void)
{
} // B.h
#include "A.h"
class B
{
A a;
public:
B(void);
~B(void);
}; // B.cpp
#include "B.h"
B::B(void)
{
} B::~B(void)
{
}
好的,完成,编译一下A.cpp,不通过。再编译B.cpp,还是不通过。编译器都被搞晕了,编译器去编译A.h,发现包含了B.h,就去编译B.h。编译B.h的时候发现包含了A.h,但是A.h已经编译过了(其实没有编译完成,可能编译器做了记录,A.h已经被编译了,这样可以避免陷入死循环。编译出错总比死循环强点),就没有再次编译A.h就继续编译。后面发现用到了A的定义,这下好了,A的定义并没有编译完成,所以找不到A的定义,就编译出错了。提示信息如下:
1>d:/vs2010/test/test/a.h(5): error C2146: syntax error : missing ';' before identifier 'b'
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
那怎么办?有办法,C++为我们提供了前置声明。前置声明是什么?举个形象点的例子,就是我要盖一个屋子(CHOuse),光有屋子还不行啊,我还得有床(CBed)。但是屋子还没盖好,总不能先买床吧,床的大小我定了,改天买。先得把房子盖好,盖房子的时候我先给床留个位置,等房子盖好了,我再决定买什么样的床。前置声明就是我在声明一个类(CHouse)的时候,用到了另外一个类的定义(CBed),但是CBed还没有定义呢,而且我还先不需要CBed的定义,只要知道CBed是一个类就够了。那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):
class CBed;
然后在CHouse中用到CBed的,都用CBed的指针类型代(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。等到要实现CHouse定义的时候,就必须要知道CBed的定义了,那是再包好CBed的头文件就行了。
前置声明有时候很有用,比如说两个类相互依赖的时候要。还有前置声明可以减少头文件的包含层次,减少出错可能。上面说的例子。
// House.h
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed* bed; // 我先给床留个位置
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
}; // House.cpp
#include "Bed.h" //注:这个很关键在源文件cpp处是要增加头文件的
#include "House.h" // 等房子开始装修了,要买床了 CHouse::CHouse(void)
{
bed = new CBed(); // 把床放进房子
} CHouse::~CHouse(void)
{
} void CHouse::GoToBed()
{
bed->Sleep();
} // Bed.h
class CBed
{ public:
CBed(void);
~CBed(void);
void Sleep();
}; // Bed.cpp
#include "Bed.h" CBed::CBed(void)
{
} CBed::~CBed(void)
{
} void CBed::Sleep()
{ }
前置声明中的陷阱
注意这里有陷阱:
1、CBed* bed;必须用指针或引用
引用版本:
// House.h
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed& bed; // 我先给床留个位置
// CBed bed; // 编译出错
public:
CHouse(void);
CHouse(CBed& bedTmp);
virtual ~CHouse(void);
void GoToBed();
}; // House.cpp
#include "Bed.h"
#include "House.h" // 等房子开始装修了,要买床了 CHouse::CHouse(void)
: bed(*new CBed())
{
CBed* bedTmp = new CBed(); // 把床放进房子
bed = *bedTmp;
} CHouse::CHouse(CBed& bedTmp)
: bed(bedTmp)
{
} CHouse::~CHouse(void)
{
delete &bed;
} void CHouse::GoToBed()
{
bed.Sleep();
}
2、不能在CHouse的声明中使用CBed的方法,但是可以在源文件中CHouse.cpp中使用,因为有#include "Bed.h"
使用了未定义的类型CBed;
bed->Sleep的左边必须指向类/结构/联合/泛型类型
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed* bed; // 我先给床留个位置
// CBed bed; // 编译出错
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed()
{
bed->Sleep(); // 编译出错,床都没买,怎么能睡
}
};
3、在CBed定义之前调用CBed的析构函数
// House.h
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed* bed; // 我先给床留个位置
// CBed bed; // 编译出错
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
void RemoveBed()
{
delete bed; // 我不需要床了,我要把床拆掉。还没买怎么拆?
}
}; // House.cpp
#include "Bed.h"
#include "House.h" // 等房子开始装修了,要买床了 CHouse::CHouse(void)
{
bed = new CBed(); // 把床放进房子
} CHouse::~CHouse(void)
{
int i = ;
} void CHouse::GoToBed()
{
bed->Sleep();
} // Bed.h
class CBed
{
int* num;
public:
CBed(void);
~CBed(void);
void Sleep();
}; // Bed.cpp
#include "Bed.h" CBed::CBed(void)
{
num = new int();
} CBed::~CBed(void)
{
delete num; // 调用不到
} void CBed::Sleep()
{ } //main.cpp
#include "House.h" int main()
{
CHouse house;
house.RemoveBed();
}
前置声明解决两个类的互相依赖
接下来,给出开篇第一个问题的答案:
// A.h
class B;
class A
{
B* b;
public:
A(void);
virtual ~A(void);
}; //A.cpp
#include "B.h"
#include "A.h"
A::A(void)
{
b = new B;
} A::~A(void)
{
} // B.h
class A;
class B
{
A a;
public:
B(void);
~B(void);
}; // B.cpp
#include "A.h"
#include "B.h"
B::B(void)
{
a = New A;
} B::~B(void)
{
}
???前置声明在友元类方法中的应用
《C++ Primer 4Edition》在类的友元一章节中说到,如果在一个类A的声明中将另一个类B的成员函数声明为友元函数F,那么类A必须事先知道类B的定义;类B的成员函数F声明如果使用类A作为形参,那么也必须知道类A的定义,那么两个类就互相依赖了。要解决这个问题必须使用类的前置声明。例如:
// House.h
#include "Bed.h"
class CHouse
{
friend void CBed::Sleep(CHouse&);
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed();
void RemoveBed()
{
}
}; // House.cpp
#include "House.h" CHouse::CHouse(void)
{
} CHouse::~CHouse(void)
{
int i = ;
} void CHouse::GoToBed()
{
} // Bed.h
class CHouse;
class CBed
{
int* num;
public:
CBed(void);
~CBed(void);
void Sleep(CHouse&);
}; // Bed.cpp
#include "House.h"
CBed::CBed(void)
{
num = new int();
} CBed::~CBed(void)
{
delete num;
} void CBed::Sleep(CHouse& h)
{ }
转自:http://blog.csdn.net/yunyun1886358/article/details/5672574
C++中前置声明的应用与陷阱的更多相关文章
- C++中前置声明介绍
前置声明是指对类.函数.模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义.在很多场合我们可以用前置声明来代替#include语句. 类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小 ...
- C++类型前置声明
前言 本文总结了c++中前置声明的写法及注意事项,列举了哪些情况可以用前置声明来降低编译依赖. 前置声明的概念 前置声明:(forward declaration), 跟普通的声明一样,就是个声明, ...
- C++中头文件相互包含与前置声明
一.类嵌套的疑问 C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题.假设我们有两个类A和B,分别定义在各自的有文件A.h和B.h中, ...
- 关于C++中的前置声明(附程序运行图)
实验于华中农业大学逸夫楼2017.3.10 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中 ...
- c++中的前置声明
引用google c++编码规范: When you include a header file you introduce a dependency that will cause your cod ...
- 【原创】SystemVerilog中的typedef前置声明方式
SystemVerilog中,为了是代码简洁.易记,允许用户根据个人需要使用typedef自定义数据类型名,常用的使用方法可参见"define和typedef区别".但是在Syst ...
- C++ 类的前置声明
http://www.2cto.com/kf/201311/260705.html 今天在研究C++”接口与实现分离“的时候遇到了一个问题,看似很小,然后背后的东西确值得让人深思!感觉在学习的过 ...
- C++ 前置声明 和 包含头文件 如何选择
假设有一个Date类 Date.h class Date { private: int year, month, day; }; 如果有个Task类的定义要用到Date类,有两种写法 其一 Task1 ...
- C++_前置声明
为什么要有前置声明? eg: -定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样: //a.h #include "b. ...
随机推荐
- socket概述和字节序、地址转换函数
一.什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口. socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信. socket API是一层抽象的网 ...
- 谈谈CListCtrl如何调整行高
原文链接: http://blog.csdn.net/sstower/article/details/9094939 调整CListCtrl 行高通常有3种方法: 1.设定字体2.设定图片3.处理Me ...
- python标准库介绍——18 StringIO 模块详解
==StringIO 模块== [Example 2-8 #eg-2-8] 展示了 ``StringIO`` 模块的使用. 它实现了一个工作在内存的文件对象 (内存文件). 在大多需要标准文件对象的地 ...
- 关于VS中的调试信息输出
有时候一些项目的调试信息不方便输出到界面中,比如ASP.NET或者WPF之类的 可以使用Debug.WriteLine()等方法输出到"输出"窗口,不过"输出" ...
- TRIZ系列-创新原理-9~11-预先反作用原理、预处理原理、预先防范原理
一.预先反作用原理表述例如以下: 1)预先给物体施加反作用,以补偿过量的或者不想要的压力. 假设知道系统在执行过程中,会有不利的或者有害的作用(负面作用)产生,则能够预先採取一定的措施来抵消.控制这样 ...
- [Linux]运维三十六计--腾讯两位大神的总结
这里是腾讯两位大神梁定安.周小军总记得运维DBA三十六计,So有道理
- IAsyncResult 接口
IAsyncResult 接口由包含可异步操作的方法的类实现.它是启动异步操作的方法的返回类型,如 FileStream.BeginRead,也是结束异步操作的方法的第三个参数的类型,如 FileSt ...
- interactive_timeout wait_timeout
官方解释: interactive_timeout The number of seconds the server waits for activity on an i ...
- How to Use HTML5 FUll Screen API(如何使用HTML5全屏接口) 【精】
原文链接:http://www.sitepoint.com/use-html5-full-screen-api/ 如果你不太喜欢变化太快的东西,那么web开发可能不适合你.我曾在2012年末有写过Fu ...
- 章鱼哥出品—VB.NET DataGridView绑定数据源 "与货币管理器的位置关联的行不能设置为不可见" 问题的解决
DtaGridView绑定数据源后.假设想让数据条件显示的话,直接使用 My_Row.Visible = False就会出错.错误类型是 "与货币管理器的位置关联的行不能设置为不可见&qu ...