zoukankan      html  css  js  c++  java
  • C++ 类的前置声明

    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();  
    }  
  • 相关阅读:
    Unix命令大全
    vs2008 与 IE8出现的兼容性问题
    Java 创建文件、文件夹以及临时文件
    如何修改Wamp中mysql默认空密码
    PAT 乙级真题 1003.数素数
    Tags support in htmlText flash as3
    DelphiXE4 FireMonkey 试玩记录,开发IOS应用 还是移植
    10 Great iphone App Review sites to Promote your Apps!
    HTML tags in textfield
    Delphi XE4 IOS 开发, "No eligible applications were found“
  • 原文地址:https://www.cnblogs.com/584709796-qq-com/p/5005256.html
Copyright © 2011-2022 走看看