http://www.2cto.com/kf/201311/260705.html
 
 今天在研究C++”接口与实现分离“的时候遇到了一个问题,看似很小,然后背后的东西确值得让人深思!感觉在学习的过程中有太多的为什么,而每一个为什么背后都隐藏着一些原理和目的,所以得多问自己”为什么“,这样才不仅知其然,更知其所以然,才能举一反三、融会贯通,这也是我以前学习不够重视的一个问题,好在”亡羊补牢,犹未晚也!“。

        C++中将”接口与实现分离“的两个重要目的就是”降低文件间的编译依存关系“和”隐藏对象的实现细节“,都是考虑到C++的高效和安全。而实现这一目的的关键技术就是”Pimpl模式(pointer to implementation)”,也即是”把一个类所有的实现细节都“代理”给另一个类来完成,而自己只负责提供接口“,而实现”Pimpl模式”的关键又是“依赖对象的声明(declaration)而非定义(definition)”,这样就引出了今天的话题:为什么通过“依赖对象的声明”可以实现Pimpl模式“,进而实现”接口与实现分离“?我们一步步来抽丝剥茧吧!
 
 
什么是“前置声明”?
 
        ”前置声明“和”include“就是一对冤家!我们先看一个例子:
[cpp]  
// A.h  
#include "B.h"  
class A  
{  
  
public:  
    A(void);  
    virtual ~A(void);  
private:  
    B b;  
};  
  
// B.h  
#include "A.h"  
class B  
{  
private:  
    A a;  
public:  
    B(void);  
    ~B(void);  
};  
        一编译,就出现了一个互包含的问题了,A中有B类型的成员变量所以需要include<b.h>,而B中又有A类型的成员变量也需要include<a.h>,这就导致了循环include,编译是肯定通过不了的!
        解决办法就是使用”前置声明“,在A.h中加上class B的声明:
[cpp] 
// A.h  
#include "B.h"  
  
class B;  
class A  
{  
  
public:  
    A(void);  
    virtual ~A(void);  
private:  
    B b;  
};  
  
// B.h  
#include "A.h"  
class B  
{  
private:  
    A a;  
public:  
    B(void);  
    ~B(void);  
};  
        但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。
        前置声明是什么?举个形象点的例子,就是我要盖一个屋子(CHOuse),光有屋子还不行啊,我还得有床(CBed)。但是屋子还没盖好,总不能先买床吧,床的大小我定了,改天买。先得把房子盖好,盖房子的时候我先给床留个位置,等房子盖好了,我再决定买什么样的床。前置声明就是我在声明一个类(CHouse)的时候,用到了另外一个类的定义(CBed),但是CBed还没有定义呢,而且我还先不需要CBed的定义,只要知道CBed是一个类就够了。那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):
 
        class CBed;
 
        然后在CHouse中用到CBed的,都用CBed的指针类型代(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。等到要实现CHouse定义的时候,就必须要知道CBed的定义了,那是再包好CBed的头文件就行了。
[cpp] 
// 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:
        (1)解决两个class的相互依赖问题,也就是两个文件相互include,减少头文件的包含层次。比如以上的几个例子!
        (2)降低文件之间的“编译依存关系”,从第一个例子我们看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。如果使用include的话,一个文件改变,所有include这个文件的文件都得重新编译,这是非常耗时的!
        (3)通过“前置声明”可以实现“接口与实现分离“。我们将需要提供给客户的类分割为两个classes:一个只提供接口,另一个负责实现!例如以下实例:
[cpp]  
//XYZ.h  
  
class X{  
public:  
    X();  
    void Print();  
};  
  
class Y{  
public:  
    Y();  
    void Print();  
};  
  
class Z{  
public:  
    Z();  
    void Print();  
};  
  
//XYZ.cpp  
  
#include "stdafx.h"  
#include "XYZ.h"  
#include <iostream>  
  
X::X(){}  
void X::Print(){  
    std::cout<<"X::Print"<<std::endl;  
}  
  
Y::Y(){}  
void Y::Print(){  
    std::cout<<"Y::Print"<<std::endl;  
}  
  
Z::Z(){}  
void Z::Print(){  
    std::cout<<"Z::Print"<<std::endl;  
}  
  
// AImpl.h  
#include "stdafx.h"  
#include "XYZ.h"  
  
class AImpl    
{    
public:    
    AImpl();  
    ~AImpl();  
    X* getX();    
    Y* getY();    
    Z* getZ();    
    void doSth();  
private:    
    X* x;    
    Y* y;    
    Z* z;    
};   
  
  
   
//AImpl.cpp  
#include "stdafx.h"  
#include "Aimpl.h"  
#include <iostream>  
  
AImpl::AImpl(){  
    x = new X();  
    y = new Y();  
    z = new Z();  
}  
AImpl::~AImpl(){  
    if (x)  
    {  
        delete x;  
    }  
    if (y)  
    {  
        delete y;  
    }  
    if (z)  
    {  
        delete z;  
    }  
}  
X* AImpl::getX(){  
    return x;  
}  
Y* AImpl::getY(){  
    return y;  
}  
Z* AImpl::getZ(){  
    return z;  
}  
void AImpl::doSth(){  
    x->Print();  
    y->Print();  
    z->Print();  
}  
  
  
// A.h    
#include "stdafx.h"  
#include <memory>  
  
class AImpl;    
class X;  
class Y;  
class Z;  
  
class A    
{    
public:    
    A();  
    X* getX();  
    Y* getY();  
    Z* getZ();     
    void doSth();  
private:    
    std::shared_ptr<AImpl> pImpl;    
};   
  
  
//A.cpp  
#include "stdafx.h"  
#include "A.h"  
#include "Aimpl.h"  
  
A::A(){  
    pImpl = std::shared_ptr<AImpl>(new AImpl());  
}  
  
X* A::getX(){  
    return pImpl->getX();  
}  
  
Y* A::getY(){  
    return pImpl->getY();  
}  
  
Z* A::getZ(){  
    return pImpl->getZ();  
}  
  
void A::doSth(){  
    pImpl->doSth();  
}  
  
  
//PreDeclaration.cpp  
  
#include "stdafx.h"  
#include "A.h"  
  
int main()  
{  
    A a;  
    a.doSth();  
    return 0;  
}  
 
        在这个例子中,A.h是提供给客户使用的头文件,在该文件中定义了class A,其中只含有一个指针成员(pImpl),指向其实现类(AImpl)。在这样的设计之下,使用class A的客户端就完全与X、Y、Z以及AImpl的实现细节分离了,同时实现了“实现细节”的隐藏,这样,无论这些classes的任何实现修改都不需要A的客户端重新编译。
 
使用“前置声明”的注意事项
 
        (1)在前置声明时,我们只能使用的就是类的指针和引用,自然也就不能调用对象的方法了!
          像我们这样前置声明类A:
          class A;
          是一种不完整的声明,只要类B中没有执行"需要了解类A的大小或者成员"的操作,则这样的不完整声明允许声明指向A的指针和引用。
          而在前一个代码中的语句
          A a;
          是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含A.h来获得类A的大小,同时也要重新编译类B。
          再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是:只要这个声明没有执行“需要了解类A的大小或者成员”的操作就可以了,所以声明成指针或引用是没有“执行需要了解类A的大小或者成员的操作”的。
          我们将上面这个例子的A.cpp稍作修改:
[cpp]  
//A.cpp  
#include "stdafx.h"  
#include "A.h"  
#include "Aimpl.h"  
  
A::A(){  
    pImpl = std::shared_ptr<AImpl>(new AImpl());  
}  
  
X* A::getX(){  
    return pImpl->getX();  
}  
  
Y* A::getY(){  
    return pImpl->getY();  
}  
  
Z* A::getZ(){  
    return pImpl->getZ();  
}  
  
void A::doSth(){  
    //pImpl->doSth();  
    getX()->Print();  
    getY()->Print();  
    getZ()->Print();  
}  

C++ 类的前置声明的更多相关文章

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

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

  2. C++中前置声明的应用与陷阱

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

  3. C++类型前置声明

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

  4. C++ 类声明 类前置声明范例

    转载自http://www.cnblogs.com/staring-hxs/p/3244251.html 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的 ...

  5. 【C++】类前置声明范例

    • 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面 ...

  6. c++ 类前置声明【转】

    [转自 here] 在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明 ...

  7. C++类前置声明

    cpp前置声明: 前置声明只能作为指针或引用,不能定义类的对象,也不能调用对象中的方法. 详见:https://www.cnblogs.com/dobben/p/7440745.html

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

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

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

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

随机推荐

  1. [zt]Which are the 10 algorithms every computer science student must implement at least once in life?

    More important than algorithms(just problems #$!%), the techniques/concepts residing at the base of ...

  2. ios9和ios10的新特性

    昨天面试了一个做ios开发的公司,其中面试官问我最新的ios系统版本是多少,以及它的特性是什么?由于自己是初学者,所以对这些没有关注过.今天特地搜索了一下关于ios9和ios10的新特性,并整理了一下 ...

  3. C++中的位运算总结

    1)位运算 位运算是指对转换成二进制的数字进行每一位上的0.1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>). 如下表所示:   ...

  4. Oracle笔记 二、常用dba命令行

    1.给用户解锁 alter user scott account unlock; 2.注销.断开.切换当前用户连接 quit conn scott/tiger 3.用户权限查询 A .查看所有用户: ...

  5. gulp.spritesmith修改px为rem单位

    移动端开发中,使用gulp.spritesmith进行小图sprite并生成样式,但由于spritesmith默认是以px为单位,所以就把插件的内容修改了下让生成rem单位并且能把background ...

  6. JS常用的设计模式(11)—— 中介者模式

    中介者对象可以让各个对象之间不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互. 打个比方,军火买卖双方为了安全起见,找了一个信任的中介来进行交易.买家A把钱交给中介B,然后从中 ...

  7. javascript之值传递与引用传递

    在分析这个问题之前,我们需了解什么是按值传递(call by value),什么是按引用传递(call by reference).在计算机科学里,这个部分叫求值策略(Evaluation Strat ...

  8. 一款非常简单的android音乐播放器源码分享给大家

    一款非常简单的android音乐播放器源码分享给大家,该应用虽然很小,大家常用的播放器功能基本实现了,可能有点还不够完善,大家也可以自己完善一下,源码在源码天堂那里已经有了,大家可以到那里下载学习吧. ...

  9. OpenStack实战(一)

    OpenStack作为当前发展势头迅猛的云计算开源项目,去年进行了一些了解,现在有空回来进行一些补充记录,当时实战的版本是那会最新版本,当然现在已经更新了好几版了,不过还是那句话“这些丝毫不影响,了解 ...

  10. nginx+php与apache+php性能对比

    测试工具http_load相同的动态页面测试,相同的硬件资源,相同并发,相同请求数量的前提下,nginx+php比apache+php的性能要 差,而且如果请求的压力大于硬件资源的承受能力,nginx ...