Effective C++之五:实现

来源:互联网 发布:swiper.jquery.min.js 编辑:程序博客网 时间:2024/04/29 06:58

条款26 尽可能延后变量定义式的出现时间

(1)最好是定义时候赋初值

尽可能延后真实意义:不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给他初值实参为止

如果是这样,不仅能便面构造(析构)非必要对象,还可以避免无意义的默认构造行为。


(2)

//A1构造 1析构 n copy

Widget w;

for (int i =0; i < n; i++)

{

    w = 取决于i的某个值;

    ...

}


//Bn构造 n析构

for (int i =0; i < n; i++)

{

    Widget w(取决于i的某个值);

    ...

}

除非(1)赋值成本比“构造+析构”低 (2)你正在处理代码中效率高度敏感的部分。 否则应该选B。


条款27 尽量少做转型动作

(0)

如果可以尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。


如果转型是必须的,试着将它隐藏与某个函数背后。客户随后可以调用该函数,而不虚将转型放进他自己的代码。


宁可使用c++(新式)转型,不要使用老式(  (T)expression  )。前者很容易辨认出来,而且也比较有着分门别类的执掌。


新式转型学习:

(1)const_cast<T>(expreesion)     

将对象的常量性移除, const -> non-const


(2)dynamic_cast<T>(expreesion)

执行安全向下转型(base->derived)决定某个对象是否归属继承体系中的某个类型


通常你想在一个认定为派生类对象上执行派生类操作函数,但手上只有指向基类的指针或引用,只能靠他们来处理对象。有两个一般性做法可以解决:


第一:使用容器并在其中存储直接指向派生类对象的指针(智能指针)。

class Window{...};

class SpecialWindow: public Window{

public:

    void blink();

    ...

};


typedef vector<shared_ptr<SpecialWindow>> VPSW; //直接存的是派生类的指针!

VPSW winPtrs;

...

for (auto it = winPtrs.begin(); it != winPtrs.end(); it++)

    (*it)->blink();


第二:在基类提供virtual函数做你想对各个Window派生类做的事。

class Window{

public:

    virtualvoid blink(){};

    ...

};

class SpecialWindow: public Window{

public:

    virtualvoid blink();

    ...

};


typedef vector<shared_ptr<Window>> VPSW;

VPSW winPtrs;          //内含指针,指向所有可能的Window类型

...

for (auto it = winPtrs.begin(); it != winPtrs.end(); it++)

    (*it)->blink();     //动态绑定,多态


一定避免连串的if dynamic_cast转型判断!


(3)reinterpret_cast<T>(expreesion)

执行低级转型,例如pointer to int -> int


(4)static_cast<T>(expreesion)

强迫隐式转型,non-const -> const,  int -> double,  pointer-to-base -> pointer-to-derived。

    int x =3, y = 2;

    double d =static_cast<double>(x) / y;


class SpecialWindow: public Window{

public:

    virtualvoid onresize()

    {

        static_cast<Window>(*this).onresize(); //*this转型为Window类型,然后调用其onresize函数,是非常错误的!

        ...                                    //其实是在当前对象之base class成分的副本上调用Window::onresize(),并不能修改对象的内容,改动的是副本

    }

    ...

};


class SpecialWindow: public Window{

public:

    virtualvoid onresize()

    {

        Window::onresize();                 //正确用法!

        ...

    }

    ...

};


条款28 避免返回handles(号码牌,用来取得某个对象)指向对象内部成分


避免返回handles(包括reference,指针,迭代器)指向 对象内部

遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生"虚吊号码牌"的可能性将至最低。


(1)成员函数返回引用,后者的访问级别就会提高如前者

class Point{

public:

    Point(int x,int y);

    ...

    void setX(int x);

    void setY(int y);

    ...

};


struct RectData{    //两个点表示一个矩形

    Point ulhc;

    Point lrhc;

};


class Rectangle{

public:

    ...

    const Point& upperLeft()const          //这一句必须返回const,不然返回的引用就能被修改了

    {return pData->ulhc;}

    const Point& lowerRight()const

    {return pData->lrhc;}

    

private:

    shared_ptr<RectData> pData;

};


int main()

{

    Point coord1(0,0);

    Point coord2(100,100);

    constRectangle rec(coord1, coord2);

    rec.upperLeft().setX(50);          //报错!因为upperLeft()返回值不让修改

    return0;

}


(2)成员函数返回引用,可能导致引用所指的东西不复存在

class GUIObject{...};

const  Rectangle boundingBox(constGUIObject& obj);     //返回 by-value。如果不存下来,就会被销毁


GUIObject* pgo;

...

const Point* pupperleft = &(boundindBox(*pgo).upperLeft());//取得一个指针指向外框左上点

                                                            //其实是tmp.upperLeft(),这个语句之后,tmp被销毁,Point指针无处可指



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

(0)

异常安全性的函数:

1、不泄露任何资源

2、不允许数据破坏


异常安全的函数提供以下三种承诺之一:

1.基本保证,异常被抛出,程序内的任何事物都在有效的状态下。

2.强烈保证,如果异常被抛出,程序状态不改变。如果函数中失败,程序恢复到调用函数之前状态。往往能够用copy and swap手法。

3.承诺决不抛出异常:一定成功。


class PrettyMenu{

public:

    ...

    void changeBackground(istream& imgSrc); //改变背景图像

    ...

private:

    Metux mutex;        //互斥器

    Image* bgImage;     //目前的背景图像

    int imageChanges;  //被改变次数

};


void PrettyMenu::changeBackground(istream& imgSrc)

{

    lock(&mutex);

    delete bgImage;

    ++imageChanges;

    bgImage = new Image(imgSrc);   //这步导致异常,unlock就不会执行,互斥器就锁住了,泄露了资源。

                                    //bgImage指向一个已删除对象,这就破坏了数据。

    unlock(&mutex);

}


(2)

在我们有能力提供以上保证的时候,我们尽可能的提供最高的异常保证,但是当我们的代码调用了过去的传统的代码,那我们无法提供任何保证。

在保证异常安全性方面,我们最常用的手段就是以对象来管理资源以及copy and swap手法


对于以对象管理资源对来说,我们主要的目的是,在代码抛出异常的时候,资源离开其作用域时,自动释放其资源,比如说互斥锁(这很重要,应为不这样的话我们是很容易造成死锁的),以资源管理对象的重要手段就是智能指针

解决资源泄露 是 使用对象管理资源-资源管理类。

void PrettyMenu::changeBackground(istream& imgSrc)

{

    Lock ml(&mutex);    //条款14

    delete bgImage;

    ++imageChanges;

    bgImage = new Image(imgSrc);

}


class PrettyMenu{

    ...

private:

    shared_ptr<Image> bgImage;     //用于资源管理的智能指针

    ...

};

void PrettyMenu::changeBackground(istream& imgSrc)

{

    Lock ml(&mutex);    //条款14

    bgImage.reset(new Image(imgSrc));//new Image(imgSrc)的结果设置bgImage内部指针。

    ++imageChanges;

}


copy and swap手法是我们提供强烈保证的重要手段,对对象状态做出“全有或全无”改变的一个很好的办法:

先在副本上做出修改,待所有改变都成功后,将副本和对象在不抛出异常的操作中置换(swap)。


struct PMImpl{  

    shared_ptr<Image> bgImage;

    int imageChanges;

};


class PrettyMenu{

    ...

private:

    Mutex mutex;

    shared_ptr<PMImpl> pImpl;

};


void PrettyMenu::changeBackground(istream& imgSrc)

{

    usingstd::swap;    //条款25

    Lock ml(&mutex);    //条款14

    shared_ptr<PMImpl> pNew(newPMImpl(*pImpl));   //生成副本

    pNew->bgImage.reset(new Image(imgSrc));

    ++pNew->imageChanges;

    swap(pImpl, pNew); //置换(swap)数据,释放mutex

}


函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。


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


(0)incline函数背后的整体观念是:对此函数每个调用,用函数本体替换之。 


(1)隐喻的inline是将函数定义于class定义式内。注:friend函数也可被定义于class内,如果真是那样,也是隐喻声明为inline。

class Person{

public:

    ...

    int age()const {return theage;}   //一个隐喻的inlineage被定义于class定义式内。

private:

    int theage;

};


(2)明确声明的inline:template和inline无关,不要只因为function templates出现在头文件,就将它声明为inline。

template<typename T>

inline const T&std::max(const T& a,const T& b)

{

    return a < b ? b : a;

}


大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。


(3)inline是个申请,编译器可以忽略。大部分编译器拒绝将太过复杂的函数inline,而对virtual的inline肯定会落空。因为:

virtual意味着“等待,直到运行期才确定调用哪个函数”,而inline意味执行前,先将调用动作替换为调用函数本体。


(4)如果编译器无法给函数inline,会给出警告。


(5)通过函数指针进行的调用,通常编译器也许不会进行inline

inline void f(){}

void (*pf)() = f;  //pf指向f

...

f();    //会被inlined

pf();   //或许不会被inlined,因为是通过函数指针达成


(6)实际上构造函数和析构函数往往是inline糟糕候选人。

class Base{

public:

    ...

private:

    string bm1, bm2;

};


class Derived:publicBase{

public:

    Derived(){}         //构造函数是空的??所以看起来是inline的绝佳候选人?

    ...

private:

    string dm1, dm2, dm3;

};

下面是实际上 编译器为 Derived构造函数所产生的代码

Derived::Derived()

{

    Base::Base();

    

    try{dm1.string::string();}

    catch(...){

        Base::~Base();

        throw;

    }

    try{dm2.string::string();}

    catch(...){

        dm1.string::~string();

        Base::~Base();

        throw;

    }

    try{dm3.string::string();}

    catch(...){

        dm2.string::~string();

        dm1.string::~string();

        Base::~Base();

        throw;

    }

}

所以说并不适合inline。


(7)大部分调试器面对inline函数都束手无策。


(8)写不写为inline的策略:

一开始不要将任何函数声明为inline,或至少将inline范围局限在那些“一定成为inline”或“十分平淡无奇,比如age() ”的函数身上。


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


(1) pointer to implementation

如果使用object reference或object pointers 可以完成任务,就不要使用objects。

你可以只靠一个类型声明式定义出指向该类型的reference和pointers。但如果定义某类型的objects,就需要用到该类型的定义式。

//Person客户就完全与Dates,Addresses以及PersonImpl的实现细节完全分离。

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

class Date;        //person接口用到的classes的前置声明

class Address;


class Person{

public:

    Person(conststring& name, constDate& birthday, constAddress& addr);

    string name()const;

    string birthDate()const;

    string address()const;

    ...

private:

    shared_ptr<PersonImpl> pImpl;   //指向实现物

};


(2)

如果能够,尽量以class声明式替换class定义式。当用到某个class,并不需要它的定义式,pass-by-value也是。 

在函数调用的时候再include它的Date头文件!

class Date; //class声明式

Date today(); //没问题,不需要Date的定义式

void clearAppointments(Date d);  //同上


正式使用是:为声明式和定义式提供不同的头文件

    include "datefwd.h"                    //这个头文件内声明(但未定义)Date

    Date today();   //没问题,不需要Date的定义式

    void clearAppointments(Date d);   //同上


支持“编译依存性最小化”的一般构思是:相依于声明式,不要相依于定义式

基于此构思的两个手段是Handle classesInterface classes


(3)像person这样的使用pimpl idiom的classes,往往被称为Handle classes

#include "Person.h"    //正在实现Person类,肯定要有class定义式

#include "PersonImpl.h"//也要有PersonImpl的定义式,否则无法调用其成员函数


person::person(const string& name,const Date& birthday, const Address& addr):pImpl(new PersonImpl(name, birthday, addr)){}


string Person::name()const

{

    return pImpl->name();

}


(4)Interface classes

class Person{//抽象基类

public:

    virtual ~Person();

    virtual string name()const = 0;

    virtual string birthDate()const = 0;

    virtual string address()const = 0;

    ...

    static shared_ptr<Person> create(const string& name,   //返回智能指针-条款18 静态成员函数没法访问非静态成员函数

                                     const Date& birthday,

                                     const Address& addr);

    

};


class RealPerson: publicPerson{

public:

    RealPerson(conststring& name,

               constDate& birthday,

               constAddress& addr)

    :thename(name), theBirthDate(birthday), theAddress(addr){}

    

    virtual ~RealPerson();

    string name()const;    //这三个函数实现略

    string birthDate()const;

    string address()const;

private:

    string thename;

    Date theBirthDate;

    Address addr;

};


shared_ptr<Person>Person::create(conststring& name,

                                  constDate& birthday,

                                  constAddress& addr)

{

    return shared_ptr<Person>(new RealPerson(name, birthday, addr));

}


(5)

程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。