前置声明的使用

有一定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++中前置声明的应用与陷阱的更多相关文章

  1. C++中前置声明介绍

    前置声明是指对类.函数.模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义.在很多场合我们可以用前置声明来代替#include语句. 类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小 ...

  2. C++类型前置声明

    前言 本文总结了c++中前置声明的写法及注意事项,列举了哪些情况可以用前置声明来降低编译依赖. 前置声明的概念 前置声明:(forward declaration), 跟普通的声明一样,就是个声明, ...

  3. C++中头文件相互包含与前置声明

    一.类嵌套的疑问 C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题.假设我们有两个类A和B,分别定义在各自的有文件A.h和B.h中, ...

  4. 关于C++中的前置声明(附程序运行图)

    实验于华中农业大学逸夫楼2017.3.10 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中 ...

  5. c++中的前置声明

    引用google c++编码规范: When you include a header file you introduce a dependency that will cause your cod ...

  6. 【原创】SystemVerilog中的typedef前置声明方式

    SystemVerilog中,为了是代码简洁.易记,允许用户根据个人需要使用typedef自定义数据类型名,常用的使用方法可参见"define和typedef区别".但是在Syst ...

  7. C++ 类的前置声明

    http://www.2cto.com/kf/201311/260705.html    今天在研究C++”接口与实现分离“的时候遇到了一个问题,看似很小,然后背后的东西确值得让人深思!感觉在学习的过 ...

  8. C++ 前置声明 和 包含头文件 如何选择

    假设有一个Date类 Date.h class Date { private: int year, month, day; }; 如果有个Task类的定义要用到Date类,有两种写法 其一 Task1 ...

  9. C++_前置声明

    为什么要有前置声明? eg: -定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样: //a.h #include "b. ...

随机推荐

  1. python练习笔记——编写一个装饰器,模拟登录的简单验证

    编写一个装饰器,模拟登录的简单验证(至验证用户名和密码是否正确) 如果用户名为 root 密码为 123则正确,否则不正确.如果验证不通过则不执行被修饰函数 #编写一个装饰器,模拟登录的简单验证 #只 ...

  2. [Android实例] 拖动滑块进行图片拼合验证方式的实现

    该篇文章从eoeAndroid搬迁过来的,原文地址:[Android实例] 拖动滑块进行图片拼合验证方式的实现 现在网站上有各种各样的验证码验证方式,比如计算大小,输入图片内容等,今天在一家网站上看到 ...

  3. Python中的迭代和可迭代对象

    什么是迭代(iteration)呢? 给定一个list或者tuple,通过for循环来遍历这个list或者tuple.这种遍历就是迭代(iteration).只要是可迭代的对象都可以进行迭代.怎么判断 ...

  4. Cordic算法简介

    作者:桂. 时间:2017-08-14  19:22:26 链接:http://www.cnblogs.com/xingshansi/p/7359940.html 前言 CORDIC算法常用来求解信号 ...

  5. How To Setup Apache Hadoop On CentOS

    he Apache Hadoop software library is a framework that allows for the distributed processing of large ...

  6. ubuntu的交换分区和系统休眠

    因为休眠功能在部分计算机上不能正常工作,所以自16.04后,ubuntu不在默认开启休眠功能. 要开启休眠功能需要如下条件. 1.要有交换分区(swap). 2.交换分区的容量至少要和实际内存一样大, ...

  7. Disable Oracle Automatic Jobs

    By default, Oracle will run some maintance jobs every night. If you don't want to run those jobs, yo ...

  8. python2.7 跨文件全局变量的方法

    有关python实现跨文件全局变量的方法. 在使用Python编写的应用的过程中,有时会遇到多个文件之间传递同一个全局变量的情况.文件1:globalvar.py #!/usr/bin/env pyt ...

  9. 【Android】10.1 扩展组件库和其他视图--本章示例主界面

    分类:C#.Android.VS2015: 创建日期:2016-02-18 1.主界面运行截图 2.MainActivity.cs文件中对应的代码 chItems.Add(new Chapter() ...

  10. angular学习笔记(十四)-$watch(1)

    本篇主要介绍$watch的基本概念: $watch是所有控制器的$scope中内置的方法: $scope.$watch(watchObj,watchCallback,ifDeep) watchObj: ...