您的位置:首页 > 编程语言 > C语言/C++

Effective c++之 将文件间的编译依存关系降至最低

2014-05-16 15:02 447 查看
自己的实例代码
//////////////////////////////////////////////////////////////////
//person.h
#include <string>
#include <memory>
class PersonImpl;
class Person{
public:
Person(const std::string& name, const std::string& birthday,const std::string& addr);
std::string getname()const;
std::string birthDate() const;
std::string address()const;
private:
std::tr1::shared_ptr<PersonImpl> pImpl; //指向实现物的指针
};
////////////////////////////////////////////////////////////
//person.cpp
#include "stdafx.h"
#include "Person.h"
#include "PersonImpl.h"

Person::Person(const std::string& name, const std::string& birthday,const std::string& addr)
:pImpl(new PersonImpl(name,birthday,addr))
{}

std::string Person::getname() const
{
return pImpl->getname();
}
std::string Person::birthDate() const
{
return pImpl->birthDate();
}
std::string Person::address()const
{
return pImpl->address();
}
///////////////////////////////////////////////////////////////
//personImpl.h
#include <string>
class PersonImpl{
public:
PersonImpl(const std::string& name, const std::string& birthday, const std::string& addr)
:mName(name),
mBirthday(birthday),
mAddr(addr)
{ }
std::string getname()const;
std::string birthDate() const;
std::string address()const;
private:
std::string mName;
std::string mBirthday;
std::string mAddr;
};
////////////////////////////////////////////////////////////////////
//personImpl.cpp
#include "stdafx.h"
#include "PersonImpl.h"

std::string PersonImpl::getname() const
{
return mName;
}
std::string PersonImpl::birthDate() const
{
return mBirthday;
}
std::string PersonImpl::address()const
{
return mAddr;
}

//main.cpp
#include "stdafx.h"
#include "person.h"

int _tmain(int argc, _TCHAR* argv[])
{
std::string lname = "name";
std::string lDate = "1990";
std::string lAddr = "beijing";
class Person lper(lname,lDate,lAddr);
return 0;
}


//////////////////////////////////////////////////////////

一下内容为参考资料

大师说了,C++的设计还是有缺陷的:它无法把接口(interface)的设计和实现(implementation)的设计完全划分开来。比如说在一个类的(接口)声明当中,总是或多或少的会泄漏一些实现上的细节,虽然这样做与接口的设计并没有太多联系。

有同学说应该多放些代码一起炒冷饭,是个好主意,下面是书中的修改版本,大致是一样的。

大师说了,C++的设计还是有缺陷的:它无法把接口(interface)的设计和实现(implementation)的设计完全划分开来。比如说在一个类的(接口)声明当中,总是或多或少的会泄漏一些实现上的细节,虽然这样做与接口的设计并没有太多联系。

下面是书中的修改版本,大致是一样的。

 class  AClass {

 public :

    void  interface_1();

   std:: string  interface_2();

    // .. 

 private :

    //  implementation details are leaking as below.. 

    std:: string  internalData_1;

   BClass internalData_2;

    // .. 

 } 

这些实现上的细节往往需要引用其他头文件中相关对象的定义(比如说下面的代码),从而产生了对这些头文件的(在编译时的)依赖。因此每次这些文件中的某个有变化时,依赖它的所有文件都需要重新编译。

 #include  < string > 

#include  " BClass.h " 

 // ..

【注意】这里貌似逻辑不是很顺:就算没有那些私有成员的声明,接口函数的返回值如果是string或是BClass等类型,不还是一样需要依赖引用其他头文件吗?其实这是两种不一样的情况,实现和接口。前面说的实现细节的泄漏是会导致编译依赖的,因为编译器需要了解这些类型对象的大小进而为其分配内存空间;但是接口,比如说函数的返回值或是参数表中的参数,就不需要编译器去考虑分配内存的问题,因此也就没有所谓的编译依赖了。

问题知道了,那么解决办法呢,大师提出“骨肉分离法”(嗯……其实是我的杜撰@#¥%):将声明(declaration)和定义(definition)分开。

呃……下面的比喻,最好吃完饭再继续。

如果说接口是一个类的骨架,那么实现就是他的血肉;如果说声明让你摸到了骨头,那么定义应该就是血和肉生长的地方。

根据骨肉分离法,对于一个AClass类,第一步先把血肉(定义/实现)剥离开,只留下骨架。然后找个盒子(新建一个类,比如说AClassImpl),把血肉放进去。

接下来还有一步,在骨头盒子里(原AClass类)加一条绳子连着血肉盒子(一个指向AClassImpl的指针),这样才不至于让骨肉真正的分离,只要找到了骨头盒子,就一定能找到血肉盒子,然后对于这个“可怜”的AClass来说,它的全部“零件”都是完整的,啥也没丢,但是做到了骨肉分离。也做到了没有编译依赖。

因为对于AClass的用户来说,他们面对的将是一个没有定义的类,这个类的后继改动,只要不涉及接口的改动,都不会导致用户程序的重新编译。

看到这里想想工作时看到的代码,原来前辈也有看过啊……

对比前面的例程,给一个“骨肉分离”了的版本吧:

 class  AClassImpl {

 // .. 

 private :

    //  implementation details are moved here.. 

    std:: string  internalData_1;

   BClass internalData_2;

 // .. 

 } 

 

 class  AClass {

 public :

    void  interface_1();

   std:: string  interface_2();

    // .. 

 private :

    //  there is only a pointer to implementation 

    std::tr1::shared_ptr < AClassImpl >  pImpl;



 

 // a constructor: instantiations of AClass and AClassImpl should always be bound together. 

 AClass::AClass( // ..) : pImpl(new AClassImpl( // ..)) 

 {

    // .. 

 }

前面的文字是自己的理解,而大师的真言是这样的:

如果可以用指针/引用的话,就不用对象。

如果可以做到仅依赖声明,就不要依赖定义。

为定义和声明分别准备两个头文件。这样一来,用户就可以很简单做到上面两点。

如果觉得骨肉分离太残忍,大师还有另外一个工具:工厂(factory)。

第二种方法中,抽象类/接口类提供了所有接口的纯虚函数形式:会有该类的子类去实现这些接口。与此同时,在抽象类/接口类中还会有一个静态(static)的工厂函数(比如create()/produce()/factory()……),这个函数实际上起到了构造函数的作用,它“制造”出子类对象来完成真正的任务,同时返回这个对象的指针(通常是智能指针如shared_ptr)。凭借这个返回的指针就可以进行正常的操作,同时不会有编译依赖的担心。一个简陋的代码见下:

 class  AClass: public  AClassFactory {

 public :

   AClass()  {} 

    void  interface_1();

   std:: string  interface_2();

    virtual   ~ AClass();

 // .. 

 } 

 

 class  AClassFactory {

 public :

    virtual   void  interface_1()  =   0 ;

    virtual  std:: string  interface_2()  =   0 ;

    // .. 

     virtual   ~ AClassFactory() { /* .. */ } 

     static  std::tr1::shared_ptr < AClassFactory >  Produce( /* .. */ )

    {

       // this factory function could be more complicated in practice.. 

        return  std::tr1::shared_ptr < AClassFactory > ( new  AClass);

   } 

 // .. 

 } 

 

 // AClassFactory could be used in this way.. 

 std::tr1::shared_ptr < AClassFactory >  pAClassObject;

pAClassObject  =  AClassFactory::Produce( /* .. */ );

 // pAClassObject->..

无论是骨肉分离法还是工厂模式,都可以去除编译依赖。代价是有的,要为之付出一点点额外代码执行的时间和空间。这个代价又可以通过内联函数(inline function)来减小一些。(不过有听过这种说法:大部分的编译器都会将短小的函数自动转成内联函数的)

尽管如此,只有在以上做法很明显地降低了系统的性能的情况下,才可以放弃分离实现和接口的努力。

这是大师的忠告。原文地址:http://www.cppblog.com/note-of-justin/archive/2012/10/02/105784.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐