c++前置声明

前置声明的作用

    C++中将”接口与实现分离“的两个重要目的就是”降低文件间的编译依存关系“和”隐藏对象的实现细节“,都是考虑到C++的高效和安全。而实现这一目的的关键技术就是”Pimpl模式(pointer to implementation)”,也即是”把一个类所有的实现细节都“代理”给另一个类来完成,而自己只负责提供接口“,而实现”Pimpl模式”的关键又是“依赖对象的声明(declaration)而非定义(definition)”,这样就引出了今天的话题:为什么通过“依赖对象的声明”可以实现Pimpl模式“,进而实现”接口与实现分离“?我们一步步来抽丝剥茧吧!

XYZ.h

#ifndef FORWARD_DECLARATION_A_H
#define FORWARD_DECLARATION_A_H
class X{
public:
	X();
	
	void Print();

};

class Y{
public:
	Y();
	void Print();


};

class Z{
public:
	Z();
	void Print();
};

#endif

XYZ.cpp

#include "XYZ.h"  

#include <iostream>  

  

X::X(){}  

void X::Print(){  
    std::cout<<"X::Printa"<<std::endl;  

    std::cout<<"X::Printa"<<std::endl;  

}  

  

Y::Y(){}  

void Y::Print(){  

    std::cout<<"Y::Printc"<<std::endl;  

}  

  

Z::Z(){}  

void Z::Print(){  

    std::cout<<"Z::Print"<<std::endl;  

}  

 

AImpl.h

#ifndef FORWARD_DECLARATION_B_H
#define FORWARD_DECLARATION_B_H
class X;
class Y;
class Z;
//#include "XYZ.h"  
class AImpl{
	public:
		AImpl();
		~AImpl();
		X* getX();
		Y* getY();
		Z* getZ();
		void doSth();
	private:
		X* x;
		Y* y;
		Z* z;
};

#endif

AImpl.cpp



#include "AImpl.h"  
#include "XYZ.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

#ifndef FORWAR_DECLARATIN_AA_H
#define FoRWAR_DECLARATIN_AA_H
#include <memory>
#include <iostream>
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;
	
};

#endif

A.cpp

#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();
}

main.cpp

#include "A.h"
#include <iostream>
using namespace std;
int main(int argc , char **argv){
	A a;
	a.doSth();
	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
PROJECT(hello)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -std=c++0x")
INCLUDE_DIRECTORIES(/home/roger/WorkSpace/C++/forward_declaration)
add_library(a A.cpp)
add_library(XYZ XYZ.cpp)

add_library(impl AImpl.cpp )

add_executable(main main.cpp)

target_link_libraries(impl XYZ)
target_link_libraries(a impl)
target_link_libraries(main a)

可以通过更改XYZ.h来使用cmake工具查看building文件。
在这个例子中,A.h是提供给客户使用的头文件,在该文件中定义了class A,其中只含有一个指针成员(pImpl),指向其实现类(AImpl)。在这样的设计之下,使用class A的客户端就完全与X、Y、Z以及AImpl的实现细节分离了,同时实现了“实现细节”的隐藏,这样,无论这些classes的任何实现修改都不需要A的客户端重新编译。

使用“前置声明”的注意事项

在前置声明时,我们只能使用的就是类的指针和引用,自然也就不能调用对象的方法了!

像我们这样前置声明类A:

      class A;

      是一种不完整的声明,只要类B中没有执行"需要了解类A的大小或者成员"的操作,则这样的不完整声明允许声明指向A的指针和引用。

      而在前一个代码中的语句

      A a;

      是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含A.h来获得类A的大小,同时也要重新编译类B。

      再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是:只要这个声明没有执行“需要了解类A的大小或者成员”的操作就可以了,所以声明成指针或引用是没有“执行需要了解类A的大小或者成员的操作”的。

我们将上面这个例子的A.cpp稍作修改:

#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();  

}  

以上不加入#include<XYZ.h>则会出现编译错误,加上则会破坏分离实现。

posted @ 2017-10-30 20:14  jinzhongxiao  阅读(347)  评论(0编辑  收藏  举报