zoukankan      html  css  js  c++  java
  • 【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都适用。   

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

  • 相关阅读:
    BZOJ5212 ZJOI2018历史(LCT)
    BZOJ5127 数据校验
    253. Meeting Rooms II
    311. Sparse Matrix Multiplication
    254. Factor Combinations
    250. Count Univalue Subtrees
    259. 3Sum Smaller
    156. Binary Tree Upside Down
    360. Sort Transformed Array
    348. Design Tic-Tac-Toe
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785441.html
Copyright © 2011-2022 走看看