Effective C++条款31 将文件的编译依赖降低到最低

  •   搜了一下:不建议使用前置声明:如何看待C++前置声明? - 知乎,所以重点讨论pimpl这种操作。

  • 如果修改了某个class的实现,而依赖这个class有很多个文件,于是可能会出现修改了一点点代码,但编译需要很长的时间。这就是文件之间的依赖太多导致的。
  • 比如有这样一份代码:
#include <string>
#include "date.h"
#include "address.h"

class Person
{
private:
	std::string theName;
	Date theBirthDate;
	Address theAddress;
public:
	...
};

Class Person和其依赖的头文件就形成了一种编译依存关系,如果date.h和address.h有什么修改,那么所有包含此头文件的文件都会重新编译。

原因也很简单:如果有这样的声明:Person pp; 那么要定义pp,必须要知道Pesron的大小,而Pesron的大小需要 Class Person {...};来确定,所以改变了某个文件A,那么依赖A的所有相关文件都需要被重新编译。

  • 那有什么办法可以降低依赖吗?
  • 针对Person我们可以这样做:把Person分割为两个类,一个只提供接口(Person提供接口函数),另一个负责实现接口(PersonImpl实现Person接口的函数):这样Person的客户完全与Date,Address以及Person的实现(PersonImpl)细目分离了。
#include <string>
#include <memory>
class PersonImpl;
class Date;  //声明
class Person
{
public:
	...
private:
	std::shared_prt<PersonImpl>p;
}

(1)首先是class Date;这种写法,相比于用#include "Date.h",区别是:用声明的依赖替换定义的依赖(声明与定义-参考:C++类中变量的初始化顺序_zion--6135的博客-CSDN博客)。这样如果#include "Date.h"有修改,对于Class Pesron{...};所在文件不需要重新编译。实现了编译上的隔离。Person类与Date类之间的修改互不影响,实现了真正的接口(class Date)与实现(Pesron看不到也不需要关心Date内变量构成)隔离。

  • 通过pimpl实现接口与实现隔离

(2)Person与PersonImpl的隔离:std::shared_prt<PersonImpl>p; 这种设计叫做pimpl idiom(pointer to implementation指向实现的指针),将Pesrson类的函数实现都放到类PersonImpl里面去管理,这样一个对客户的类Person就完全将实现隐藏,客户连函数都看不到(如果PesronImpl类也不能看到,应该是可以的,因为class PeseonImpl隐藏了头文件)。具体例子如下:

//main.cpp
#include <iostream>
#include <string>
#include <memory>
#include "Person.h"

int main() {
    Person pp;
    std::cout << pp.getname() << std::endl;
    return 0;
}


//Person.h
#include "iostream"
#include <memory>
#include "PersonImpl.h"

class Person {
   public:

    const std::string getname(){
        std::cout << p->getname() << std::endl;
        return p->getname();
    };

private:
    std::shared_ptr<PersonImpl> p = std::make_shared<PersonImpl>();
};

//PersonImpl.h
#include "iostream"
using namespace std;
class PersonImpl
{
public:
 PersonImpl() { cout << "start" << endl; };
 const string getname() { return name; };
 ~PersonImpl() {
	 cout << "finish" << endl;
 }
private:
	string name = "Person TT";
	int n2;
};

main.cpp代表客户端调用,通过在Person类的接口函数,里面对PersonImpl的实现函数进行封装。这就是所谓的“接口与实现完全隔离”

  • 通过virtual class 来定义接口, derived class来具体实现。实现接口与实现分离

(1)需要名字的接口person类:(通过pure virtual function来表明是接口)

(2)需要一个类PersonImpl来继承并实现这些接口

class Person {  // interface class
public:
    virtual const std::string getname() = 0;
    virtual ~Person(){};

};

class PersonImpl : public Person  //derived class
{
public:
	virtual const string getname() { return name; };

	PersonImpl(string nameTmp):name(nameTmp)
	{
		cout << "start" << endl;
	};
	~PersonImpl() {
		cout << "finish" << endl;
	}
private:
	string name = "Person default";
};

(3)既然接口有了,实现有了,希望一个factory函数来获取一个PersonImpl对象,(为啥不直接new PersonImpl来使用?因为这是接口,统一流程,关于内存的管理,需要交给Person类来操作,不希望调用接口的人去管理)所以Class Person需要有如下函数create;

class Person {  // interface class
   public:
    virtual const std::string getname() = 0;
    virtual ~Person(){};

	static std::shared_ptr<Person> create(string name);
};

(4)所以通过Person类实现create,以及调用,客户用的所有函数都在Person类里面,真正的实现在PersonImpl里面。

//person.h 
#include "iostream"
#include <memory>
// #include "PersonImpl.h"
using namespace std;

class Person {  // interface class
   public:
    virtual const std::string getname() = 0;
    virtual ~Person(){};

	static std::shared_ptr<Person> create(string name);
private:
    // static std::shared_ptr<PersonImpl> pp;  //管理内存
};

class PersonImpl : public Person  //derived class
{
public:
	virtual const string getname() { return name; };

	PersonImpl(string nameTmp):name(nameTmp)
	{
		cout << "start" << endl;
	};
	~PersonImpl() {
		cout << "finish" << endl;
	}
private:
	string name = "Person default";
};

//person.cpp
#include "Person.h"


std::shared_ptr<Person> Person::create(string name) {
    static std::shared_ptr<Person>pp (new PersonImpl(name));
    return pp;
}




客户端调用main.cpp,完全没有PersonImpl的痕迹,客户啥都看不了!!!

#include <iostream>
#include <string>
#include <memory>
#include "Person.h"

int main() {
    static std::shared_ptr<Person> p1 = Person::create("lbw");
    std::cout << p1->getname() << std::endl;
    return 0;
}

运行结果:

start
lbw
finish

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值