浅析C++中前置声明的应用与陷阱

前置声明的使用
有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是强耦合关系,类A要引用B的对象,类B也要引用类A的对象。好的,不难,我的第一直觉让我写出这样的代码:


代码如下:

// A.h
#include "B.h"
class A
{

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"
#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的方法
使用了未定义的类型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 = 1;
}
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(1);
}
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 = 1;
}
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(1);
}
CBed::~CBed(void)
{
    delete num;
}
void CBed::Sleep(CHouse& h)
{
}

(0)

相关推荐

  • C++ 前置声明详解及实例

    C++ 前置声明详解及实例 [1]一般的前置函数声明 见过最多的前置函数声明,基本格式代码如下: #include <iostream> using namespace std; void fun(char ch, int *pValue, double dValue); void main() { int nValue = 100; double dValue = 111.22; fun('a', &nValue, dValue); system("pause")

  • 浅析C++中前置声明的应用与陷阱

    前置声明的使用有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是强耦合关系,类A要引用B的对象,类B也要引用类A的对象.好的,不难,我的第一直觉让我写出这样的代码: 复制代码 代码如下: // A.h#include "B.h"class A{ public:    A(void);    virtual ~A(void);};//A.cpp#include "A.h"A::A(void){}A::~A(void){}// B.h#include &qu

  • 浅析javascript中函数声明和函数表达式的区别

    javascript中声明函数的方法有两种:函数声明式和函数表达式. 区别如下: 1).以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是可选的. 2).以函数声明的方法定义的函数,函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用. 3).以函数声明的方法定义的函数并不是真正的声明,它们仅仅可以出现在全局中,或者嵌套在其他的函数中,但是它们不能出现在循环,条件或者try/catch/finally中,而     函数表达式可以在任何地方声明. 下面分别用两种方法定

  • 浅析正则表达式中的lastIndex以及预查

    依次写出下列输出内容. var reg1 = /a/; var reg2 = /a/g; console.log(reg1.test('abcabc')); // true console.log(reg1.test('abcabc')); // true console.log(reg1.test('abcabc')); // true console.log(reg1.test('abcabc')); // true console.log(reg2.test('abcabc')); //

  • 深入浅析JavaScript中的Function类型

    Function是javascript里最常用的一个概念,javascript里的function是最容易入手的一个功能,但它也是javascript最难理解最难掌握的一个概念. 1. Function类型是js中引用类型之一,每个函数实际上都是Function类型的实例对象,具有自己的属性和方法.正因为函数式对象,所以函数名实际上也是一个指向函数对象的指针. 2. 常用的函数定义方式 1. 函数声明: function sum(a , b ){ return a+b; } 2. 表达式: va

  • 深入浅析python 中的self和cls的区别

    python 中的self和cls 一句话描述:self是类(Class)实例化对象,cls就是类(或子类)本身,取决于调用的是那个类. @staticmethod 属于静态方法装饰器,@classmethod属于类方法装饰器.我们需要从声明和使用两个方面来理解. 详细介绍 一般来说,要使用某个类的方法,需要先⚠️实例化一个对象再调用方法.而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用.这有利于组织代码,把某些应该属于某个类的函数给放到

  • 浅析java中String类型中“==”与“equal”的区别

    一.前言 1.1.首先很多人都知道,String中用"=="比较的是地址,用equals比较的是内容,很多人对此用的是记忆法,通过记忆来加强此的引用,但是其真正的原理其实并不难,当我们真正明白其为什么的时候,用起来也会更加灵活,更加有底气(形容得不太好,朋友别见怪): 二相关知识的准备 类型常量池 运行时常量池 字符串常量池 我们今天讨论的主题是当然是字符串常量池: 为什么在这要把另外两个常量池拿出说一下呢,首先小生我在网上或者cnds上看到很多人在争论字符串常量池是存在与方法区还是堆

  • 区分c++中的声明与定义

    C++编码过程中,我们经常谈及"定义"和"声明",二者是编程过程中的基本概念.我们需要使用一个变量.类型(类.结构体.枚举.共用体)或者函数时,我们需要提前定义和声明.定义和声明的过程,就像我们向图书馆借阅书籍一般,需要先完成书籍的印刷,即创造出书籍,这是一个定义的过程,有了书籍,我们需要到图书馆完成借阅的登记手续,这是申明的过程.完成了申明,我们有了使用书籍的权限,就可以尽情的畅游在知识的海洋.如果说书籍是自己委托印刷厂印刷的,那么你无需向他人借阅,即无需声明,可

  • 详解C++ 前置声明

    前置声明是C/C++开发中比较常用的技巧,主要用在三种情形: 变量/常量,例如extern int var1;; 函数,例如void foo();,注意类的成员函数无法单独做前置声明: 类,例如class Foo;,也可以前置声明模板类:template class<typename T1, int SIZE>Foo;.如果类包含在名字空间中,需在名字空间内做前置声明:namespace tlanyan {class Foo;};,而不能这样:class tlanyan::Foo;. 前置声明

  • 浅析Java中为什么要设计包装类

    目录 一.为什么需要包装类 二.装箱与拆箱 三.不简单的 Integer.valueOf 四.Object 类可以接收所有数据类型 五.包装类在集合中的广泛使用 六.数据类型转换 一.为什么需要包装类 在 Java 中,万物皆对象,所有的操作都要求用对象的形式进行描述.但是 Java 中除了对象(引用类型)还有八大基本类型,它们不是对象.那么,为了把基本类型转换成对象,最简单的做法就是将基本类型作为一个类的属性保存起来,也就是把基本数据类型包装一下,这也就是包装类的由来. 这样,我们先自己实现一

  • 浅析java中常用的定时任务框架-单体

    目录 一.阅读收获 二.本章源码下载 三.Timer+TimerTask 四.ScheduledExecutorService 五.Spring Task 5.1 单线程串行执行-@Scheduled 5.2 多线程并发运行-@Scheduled+配置定时器的程池(推荐) 5.3 多线程并发执行-@Scheduled+@Async+配置异步线程池 5.4 @Scheduled参数解析 六.Quartz 6.1. 创建任务类 6.2. 配置任务描述和触发器 一.阅读收获 1. 了解常用的单体应用定

随机推荐