【effective c++读书笔记】【第5章】实现(2)

条款29:为“异常安全”而努力是值得的

1、在异常抛出时,带有异常安全性的函数会:

a、不泄露任何资源

b、不允许数据败坏

2、异常安全函数(Exception-safefunctions)提供以下三个保证之一:

a、基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。然而程序的现实状态不可预料。

b、强烈保证:如果异常被抛出,程序状态不改变。

c、不抛掷(nothrow)保证:承诺绝不抛出异常,因为他们总能完成它们原先承诺的功能。

3、copy and swap会导致强烈保证。原则是为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)。

请记住:

  • 异常安全函数(Exception-safefunctions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
  • “强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
  • 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

条款30:透彻了解inlining的里里外外

1、inline函数只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内:

class Person{
public:
	...
	int age() const { return theAge; } //隐喻申请
	...
private:
	int theAge
};

这样的函数通常是成员函数,friend函数也可被定义于class内,如果真是那样,它们也是被隐喻声明为inline。

明确声明inline函数的做法则是在其定义式钱加上关键字inline。例如标准的max template:

template<typename T>
inline const T& std::max(const T& a, const T& b){
	return a < b ? b : a;
}

2、大部分编译器拒绝将过于复杂(例如带有循环或递归)的函数inlining,而所有对虚函数的调用也都会使inlining落空。因为虚函数直到运行期才确定调用哪个函数,而内联函数意味执行前先将调用动作替换为被调用函数的本体。

3、一个表面上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。编译器通常不对“通过函数指针而进行的调用”实施inlining。

4、程序库设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。例如f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序必须重新编译。但如果f是non-inline函数,客户端只需重新连接即可;如果是动态链接库,升级版函数甚至可以不知不觉地被应用程序吸纳。

5、大部分调试器面对inline函数都束手无策,因为你不能在一个不存在的函数内设立断点。

请记住:

  • 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
  • 不要只因为function templates出现在头文件,就将它们声明为inline。

条款31:将文件间的编译依存关系降至最低

1、例子:  
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 theBirthday;    //实现细节
	Address theAddress;  //实现细节
};

如果没有取得其实现代码所用到的class stringDateAddress的定义式,那么class Person无法通过编译。所以Person定义文件的最上方可能存在:

#include<string>
#include"date.h"
#include"address.h"

这么一来使Person定义文件和其含入文件之间形成了一种编译依赖关系。如果这些头文件中有任何一个被改变,或者这些头文件依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。

2Handle classes可以解除接口和实现之间的耦合关系,从而降低文件间的编译依存性。

Handle classes例子:

//Address.h
#ifndef ADDRESS_H
#define ADDRESS_H

#include<string>
class Address{
public:
	Address(const std::string& addr) :address(addr){}
	std::string getAddress() const{ return address; }
	Address(const Address& addr) :address(addr.address){}
private:
	std::string address;
};

#endif

//Date.h
#ifndef DATE_H
#define DATE_H

class Date{
public:
	Date(int d, int m, int y) :day(d), month(m), year(y){}
	int getDay() const{ return day; }
	int getMonth() const{ return month; }
	int getYear() const{ return year; }
	Date(const Date& date) :day(date.day), month(date.month), year(date.year){}
private:
	int day;
	int month;
	int year;
};

#endif

//PersonImpl.h
#ifndef PERSONIMPL_H
#define PERSONIMPL_H

#include"date.h"
#include"Address.h"

class PersonImpl{
public:
	PersonImpl(const std::string& n,const Address& addr,const Date& date) :name(n),address(addr),birthday(date){}
	std::string getName() const{ return name; }
	Address getAddress() const{ return address; }
	Date getBirthday() const{ return birthday; }
private:
	std::string name;
	Address address;
	Date birthday;
};

#endif

//Person.h
#ifndef PERSON_H
#define PERSON_H

#include<string>
#include<memory>

class Date;//Person接口用到的class的前置声明
class Address;//Person接口用到的class的前置声明
class PersonImpl; //Person实现类的前置声明

class Person{
public:
	Person(const std::string& name, const Address& addr, const Date& birthdaty);
	void getName();
	void getAddress();
	void getBirthday();
private:
	std::tr1::shared_ptr<PersonImpl> pImpl;
};

#endif

//Person.cpp
#include"Person.h"
#include"PersonImpl.h" //必须#include PersonIplm的class的定义式,否则无法调用成员函数
#include<iostream>     //注意PersonImpl有着和Person完全相同的成员函数,两者接口完全相同

Person::Person(const std::string& name, const Address& addr, const Date& birthdaty) :pImpl(new PersonImpl(name, addr, birthdaty)){}
void Person::getName(){ std::cout << pImpl->getName() << std::endl; }
void Person::getAddress(){ std::cout << pImpl->getAddress().getAddress() << std::endl; }
void Person::getBirthday(){
	std::cout << pImpl->getBirthday().getYear() << "."
		<< pImpl->getBirthday().getMonth() << "."
		<< pImpl->getBirthday().getDay() << std::endl;
}

//main.cpp
#include"Person.h"
#include"Address.h"
#include"Date.h"
using namespace std;

int main(){
	string name("Tom");
	Address addr("Shanghai");
	Date date(5, 8, 2015);
	Person p(name, addr, date);
	p.getName();
	p.getAddress();
	p.getBirthday();

	system("pause");
	return 0;
}

Person class只内含一个指针成员,指向其实现类,这种设计被称为pimpl idiom,Person class被称为Handle classes。这样的设计下,Peason的客户就完全与DatesAddressesPersons的实现细目分离了。如果修改了其他几个类的定义,那么Peason并不需要重新编译。这个分离的关键在于以“声明的依存性”替换“定义的依存性”:即让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。由此我们可以得出几个设计策略:

a、如果使用对象的引用或对象指针可以完成任务,那么就不要使用对象。因为对象的引用和指针时只需类型声明式,而定义对象时必须要有类型的定义式。

b、尽量用类声明替换类定义。声明一个函数而它用到某个类时,只需类的声明。

c、为声明式和定义式提供不同的头文件。

3、解除接口和实现之间的耦合关系,从而降低文件间的编译依存性的方法还有Interface classes

Interface classes例子:

//Address.h
#ifndef ADDRESS_H
#define ADDRESS_H

#include<string>
class Address{
public:
	Address(const std::string& addr) :address(addr){}
	std::string getAddress() const { return address; }
	Address(const Address& addr) :address(addr.address){}
private:
	std::string address;
};

#endif

//Date.h
#ifndef DATE_H
#define DATE_H

class Date{
public:
	Date(int d, int m, int y) :day(d), month(m), year(y){}
	int getDay() const{ return day; }
	int getMonth() const{ return month; }
	int getYear() const{ return year; }
	Date(const Date& date) :day(date.day), month(date.month), year(date.year){}
private:
	int day;
	int month;
	int year;
};

#endif

//Person.h
#ifndef PERSON_H
#define PERSON_H

#include<string>
#include<memory>

class Date;//Person接口用到的class的前置声明
class Address;//Person接口用到的class的前置声明

class Person{
public:
	virtual ~Person();
	virtual void getName() const = 0;
	virtual void getAddress() const = 0;
	virtual void getBirthday() const = 0;
	static std::tr1::shared_ptr<Person> creat(const std::string& n, const Address& addr, const Date& b);
};

#endif

//RealPerson.h
#ifndef REALPERSON_H
#define REALPERSON_H

#include "date.h"
#include "Person.h"
#include"Address.h"
#include<iostream>

class RealPerson :public Person{
public:
	RealPerson(const std::string& n, const Address& addr, const Date& b) :name(n),address(addr), birthday(b){}
	virtual ~RealPerson(){}
	void getName() const { std::cout << name << std::endl; }
	void getAddress() const { std::cout << address.getAddress() << std::endl; }
	void getBirthday() const {
		std::cout << birthday.getYear() << "."
			<< birthday.getMonth() << "."
			<< birthday.getDay() << std::endl;
	}
private:
	std::string name;
	Address address;
	Date birthday;
};

#endif

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

Person::~Person(){}
std::tr1::shared_ptr<Person> Person::creat(const std::string& n, const Address& addr, const Date& b){
	return std::tr1::shared_ptr<Person>(new RealPerson(n, addr, b));
}

//main.cpp
#include"RealPerson.h"
#include<memory>
using namespace std;

int main(){
	string name("Tom");
	Address addr("Shanghai");
	Date date(5, 8, 2015);
	shared_ptr<Person> pp(Person::creat(name, addr, date));
	pp->getName();
	pp->getAddress();
	pp->getBirthday();

	system("pause");
	return 0;
}

请记住:

  • 支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classed和Interface classes。
  • 程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。   

版权声明:本文为博主原创文章,未经博主允许不得转载。

原文地址:https://www.cnblogs.com/ruan875417/p/4785441.html