Effectiv C++条款31 将文件间的编译依存关系降至最低 Handle Class和Interface Class完整实现

一、背景

1.如果一个源文件以头文件的形式包含了其他文件,则它们之间便形成了一种“编译依存关系”;一旦被包含的文件(或这些文件包含的其他文件)被修改,则每一个包含该源文件的其他文件都必须重新编译。

以“前置声明”代替“头文件包含”似乎是一个好办法,但是会遇到一些问题:

  • 对诸如string这样的头文件而言,正确的前置声明比较复杂,因为会涉及模板(string是个typedef而非类)。事实上,前置声明标准库并没有必要,直接包含这些文件并不会造成太大的编译依赖性问题。
  • 在编译期间,编译器必须知道对象的大小,从而必须访问它的定义式,从而使得该类必须列出实现细节。

2.支持“编译依存性最小化”的一般构想是:依赖于声明式,而非定义式。基本做法是:

  • 如果使用对象的引用或者指针可以完成任务,就不要使用对象本身。
  • 如果能够,尽量以类的声明式替换类的定义式:如果声明一个函数需要用到某个类(作为返回值类型或参数类型)时,并不需要它的定义式(虽然传值方式效率低下)。如果能够将“提供类的定义式”(通过#include完成)的必要性从“函数声明所在”的头文件转移到“内含函数调用”的客户文件,便可去除“并非真正必要的类型定义”与客户端之间的编译依存性去除掉。
  • 为声明式和定义式提供不同的头文件:为促进严守上述准则,需要定义两个头文件:一个用于声明式,一个用于定义式。如下:
#include "datefwd.h"	//该头文件内声明但未定义Date类,这样就不需要做前置声明了
Date today();
void clearAppointments(Date d);


 

类似datefwd的命名方式取自STL中的<iosfwd>。这同时说明本条款同样适用于模板类(这可能需要构建环境的支持)。

注:C++中提供关键字export允许模板声明式和定义式分别处于不同的文件中,但是支持的编译器很少。

3.基于此构想的两个手段是使用Handle class和Inferface class。

4.代码优化过程:比如以下项目,包含的头文件中具有完整的定义式:

#include <string>
#include "date.h"
#include "address.h"
class Person{
	public:
		Person(const std::string& name, const Date& birthday, const Address& addr);
		std::string name() const;
		std::string birthday() const;
		std::string address() const;
		...
	private:
	std::string theName;
	Date theBirthDate;
	Address theAddress;
};

这样编译依赖关系就十分紧密。一种思路是用前置声明方法:

namespace std{
	class string;		//string是个typedef:basic_string<char>,该前置声明并不正确,事实上对STL中的类做前置声明也没有必要,直接包含就可以了
}
class Date;//因为这里仅仅只是声明,没有调用任何关于Date的函数,因此不需要加入相应的头文件
class Address;
class Person{
	...
};

 

二、解决方案

//条款31 将文件间的编译依存关系降至最低
/*
Handle class
简而言之,将类的接口和实现分离。
接口里面存储一个指针,指向实现类,然后客户调用接口。
这样当实现改变时候,客户不用重编译。

Handle Class (Person.h PersonImpl.h PersonImpl.cpp)
Person的客户就与Person的实现细目分离,Person实现的修改不需要Person客户端重新编译。
接口与实现分离。一个只负责提供接口,另一个负责实现该接口。main class(Person)内含有一个指针成员
shared_ptr<PersonImpl>pImpl.(pimpl pointer to implementation)。Person客户完全与Person实现细目分离。
这个分离的关键在于以“声明的依存性”替换“定义的依存性”。现实中让头文件尽可能自我满足,万一做不到,
则让它与其它文件内的声明式(而非定义式)相依。
*/

/*
Interface class
简而言之,将类抽象为接口,然后客户调用接口,这样当类的实现改变时不会受影响。
类的实现通过继承抽象基类完成。
客户调用的构造接口是static,因为它定义在抽象基类中,而抽象基类不能实例化,
只能在实例化派生类时部分实例化,所以定义成static。static的函数属于整个类,
可以通过类名::函数名的方式调用,具体构造函数在派生类中定义。


另一种制作Handle class的办法是,令Person称为抽象基类,称为:
Interface Class (Person2.h Person2.cpp RealPerson2.h)
Person类定义成抽象基类,成为接口。这个class的客户必须以Person的指针或引用来撰写应用程序。
Interface class的客户通常调用factory(工厂)函数或virtual构造函数。它们返回指针(或智能指针),指向
动态分配所得对象,而该对象支持Interface class的接口。这样的函数又往往在Interface class内被
声明为static。
支持Interface class接口的那个具象类(concrete classes)必须被定义出来,而且真正的构造函数必须被调用。
它提供继承而来的virtual函数的实现。

*/


 

Handle class

Person.h

#include <string>
#include <memory>
using namespace std;

class PersonImpl;						// 	Person实现类的前置声明

class Person
{
public:
	Person(const string& name);
	string getname() const;
	
private:
	
	tr1::shared_ptr<PersonImpl> pImpl;	// 指针,指向实现物,或者PersonImpl* pImpl
	string name;
};


Person.cpp

#include "stdafx.h"
#include "Person.h"
#include "PersonImpl.h"

Person::Person(const string& name) : pImpl(new PersonImpl(name))
{
	this->name = name;
}

string Person::getname() const
{
	return pImpl->getname();
}


PersonImpl.h

#include <string>
#include <iostream>
using namespace std;
class PersonImpl
{
public:
	PersonImpl(const string& name)
	{
		this->name = name;
		//cout << "Impl" << endl;
	}
	string getname() const;
private:
	string name;
};


PersonImpl.cpp

#include "stdafx.h"
#include "PersonImpl.h"
string PersonImpl::getname() const
{
	return this->name;
}


main.cpp

#include "stdafx.h"
#include "Person.h"
#include <memory>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
	// Handle Class
	Person p("Yerasel");
	cout << p.getname() << endl;


	return 0;
}


 

以上方法是pImpl方法,它是微软的Herb Sutter提出来的,该方法是为了尽量减小接口和实现之间的耦合,以避免接口改动对程序重新编译等带来的影响。简单来说,如果你的大型程序因为复杂的头文件包含关系,使得你对某头文件的某小改动可能引起的巨大编译时间成本望而生畏,那么你需要用pImpl方法来改善这种处境。

 

Interface class

Person2.h

#ifndef PER2_H
#define PER2_H
#include <string>
#include <memory>
using namespace std;

class Person2
{
public:

	Person2(const string& name)
	{
		this->thename = name;
	}
	virtual ~Person2(){};
	
	static tr1::shared_ptr<Person2> create(const string& name);
	virtual string getname() const = 0;
private:
	string thename;
};
#endif


 

Person2.cpp

#include "stdafx.h"

#include "Person2.h"
#include "RealPerson2.h"
#include <memory>


tr1::shared_ptr<Person2> Person2::create(const string& name)
{
	return tr1::shared_ptr<Person2>(new RealPerson2(name));
}

 

RealPerson2.h

#ifndef REAL2_H
#define REAL2_H
#include "Person2.h"
#include <iostream>
using namespace std;

class RealPerson2 : public Person2
{
public:
	RealPerson2(const string& name)
		:Person2(name), thename(name)
	{
	}
	virtual ~RealPerson2(){}
	string getname() const
	{
		return this->thename;
	}
private:
	string thename;
};

#endif


 

main.cpp

#include "Person.h"
#include <memory>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
// Interface Class
	tr1::shared_ptr<Person2>p2 = (Person2::create("jandosim"));
	cout << p2->getname() << endl;
	return 0;
}



 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值