• 【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都适用。   

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

  • 相关阅读:
    gost源码分析心得
    go语言net编程,设置TCP连接发出使用源IP
    代理程序gost使用
    squid关闭缓存
    shell中的if比较
    10年以上年化20%以上收益率的基金经理
    股票信息查询
    02.win2003虚拟机安装和dos命令
    01.网络安全和虚拟机
    部署kali渗透环境
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785441.html
Copyright © 2020-2023  润新知