Effective C++第五章-实现

来源:互联网 发布:arp攻击软件使用方法 编辑:程序博客网 时间:2024/06/05 05:09

不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。这样不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。

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

//不理想编码std::string encryptPassword(const std::string& password)//encrypt加密{    using namespace std;    string encrypted;    if(...){throw logic_error("wrong")};//如果if抛出异常,则encrypted未被使用    return encrypted;}
//理想编码std::string encryptPassword(const std::string& password)//encrypt加密{    using namespace std;    if(...){throw logic_error("wrong")};    string encrypted;    return encrypted;}

(default构造函数构造出一个对象然后对它赋值)比(直接在构造时指定初值)的效率差

//不理想编码std::string encrypted;encrypted = password;
//理想编码std::string encrypted(password);

涉及到循环的变量:

方法A:widget w;for(int i = 0;i<n ;i++){    w = i;    ...}//1个构造函数+1个析构函数+n个赋值操作
方法B:for(int i = 0;i<n ;i++){    widget w(i);    ...}//n个构造函数+n个析构函数

A和B比较:

  • A造成名称w的作用域比方法B更大,有时对程序的可理解性和易维护性造成冲突。
  • 如果赋值成本比“构造+析构”成本低,或者正在处理代码中效率高度敏感的部分则使用A,否则使用B

尽量少做转型动作(minimize casting)-优良的C++代码很少使用转型

三种不同的转型形式:

  1. C风格的转型

    (T)expression

  2. 函数风格的转型

    T(expression)

  3. C++风格的转型

    • const_cast< T >(expression)
    • dynamic_cast< T >(expression)
    • static_cast< T >(expression)
    • reinterpret_cast< T >(expression)

    • 尽量避免转型,特别是注重效率的代码中避免dynamic_cast。dynamic_cast的许多实现版本执行速度相当慢

    • 之所以需要dynamic_cast,通常是因为你想在一个你认定派生对象上执行派生类操作函数,但却只有一个指向基类的指针或引用

    • 绝对必须避免连串的dynamic_cast,例如:

    class window{};class specialwindow1:public window{};...//其它派生类的定义typedef std::vector<std::tr1::shared_ptr<window> > VPW;//> >必须中间用空格分开,不然会被当做右移操作符VPW winPtrs;...for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ; ++iter){if(specialwindow1 * psw1 = dynamic_cast<specialwindow1>(iter->get())){}elseif(specialwindow1 * psw1 = dynamic_cast<specialwindow1>(iter->get())){}...}//这段代码的问题在于:1、如果window类继承体系改变,则所有这类代码需要修改,或者加入新的派生类是或许需要加入条件分支。//这样的代码应该以某些“基于virtual函数调用”的东西取而代之。
    • 如果有个设计需要转型动作,试着发展无需转型的代替设计
    //需要转型动作的设计typrdef std::vector<std::tr1::shared_ptr<window> > VPW;VPW::winPtrs;for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ;++iter){if(specialwindow1 *psw = dynamic_cast<specialwindow1>(iter->get())){psw->fun();}}

    可以改为

    //无需转型的代替设计typrdef std::vector<std::tr1::shared_ptr<specialwindow1> > VPSW;VSPW::winPtrs;for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ;++iter){psw->fun();}//但是这种做法使得该容器只能存储一种window的派生类specialwindow1
    • 如果转型是必要的,试着将它隐藏于某个函数背后。

    • 尽量使用C++风格的转型,不要使用1和2旧式转型,C++转型容易识别且有分门别类的执掌,出错时易于查找

    • 似是而非的转型编码

    单一对象可能拥有一个以上的地址,例如“以base* 指向它”时的地址和“以derived*指向它”时的地址。一旦使用多重继承,这事几乎一直发生。

    class window{public:    virtual void onresize(){}    ...};class specialwindow:public window{public:    virtual void onresize(){        static_cast<window>(*this).onresize();//我们想让派生类的虚函数onresize先调用基类的对应函数,但此处编码不可行    }};

    错误分析:此时将this指针转型为window基类,并调用的是window::onresize。但是,调用的是稍早于转型动作所建立的一个this指针的base class成分的副本的onresize,这时会导致其base class成分的更改没有落实,但derived class成分的更改落实了。

    正确示例

    class specialwindow:public window{public:    virtual void onresize(){        window::onresize();    }};

避免返回handles(号码牌,用来取得某个对象,有:引用、指针、迭代器)指向对象内部成分

返回一个代表对象内部数据(内部数据=成员变量+被声明为protected、private的函数)的handle,

  • “降低对象封装性”的风险
  • 也可能导致“虽然调用const成员函数却造成对象状态被更改”。
  • 会面临“handle比其所指对象更长寿”的风险,handle比其所指对象更长寿会导致虚吊号码牌(dangling handle)。

异常安全性(exception safety)

C++中异常

当异常被抛出时,带有异常安全性的函数会:

  • 不泄漏任何资源
  • 不允许数据败坏

异常安全函数提供以下三个保证之一:(对大部分函数而言,抉择往往落在基本保证和强烈保证之间)

  1. 基本承诺:如果异常被抛出,程序内的任何事 物仍然保持在任何一个有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。

  2. 强烈保证:如果异常被抛出,程序状态不改变。也就是如果函数成功,就是完全成功,如果函数失败,程序会恢复到调用函数之前的状态。

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

  3. 不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成原先承诺的功能。作用于内置类型身上的所有操作都提供该保证。

函数的声明式并不能告诉你函数是否是正确的、可移植的或高效的,也不能够告诉你是否提供任何异常安全性保证。所有这些性质都由函数的实现决定,无关乎声明。

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

inline函数– 将对此函数的每个调用都以函数本体替换之

inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出

  • 隐喻方式:将函数定义与class定义式内:

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

    这样的函数通常是成员函数,friend函数也可被定义与class内。

  • 明确声明inline函数是在其定义式前加上关键字inline

    template<typename T>inline const T& std::max(const T& a, const T& b){return a<b ? b:a;}//明确声明//inline函数和template两者通常被定义于头文件。但是不要只因为function template出现在头文件,就将其声明为inline。

编译器通常不对“通过函数指针而进行的调用”实施inline,这意味对inline函数的调用有可能被inline,也有可能不被inline,取决于该调用的实施方法:

inline void f() {...}void (* pf)() = f;...f();//这个调用将被inlined,因为是一个正常调用。pf();//这个调用或许不被inlined,因为通过函数指针达成的。

因此,一个表面上看似inline的函数是否真是inline,取决于编译器。如果编译器无法将你要求的函数inline化,会给你一个警告信息。

大部分调试器面对inline函数都束手无策,也就是无法设立断点。因此,慎重使用inline,将大多数inline限制在小型、被频繁调用的函数身上。

将文件之间的编译依存关系降至最低

举例:

class person{public:    person(const std::string& name , const Date& birthday , const Address& addr);    std::string getname() const;    std::string getbirthDate() const;    std::string getaddress() const;private:    std::string thename;    Date thebirthdate;    Address theaddress;}

该文件还应该有:

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

person类的定义文件和含入文件之间形成了一种编译依存关系。如果这些头文件中有任何一个被改变,或这些头文件所依赖的其它头文件有任何改变,那么每一个含入person定义文件的文件都重新编译,任何使用person的文件也必须重新编译。

相关链接1

相关链接2

原创粉丝点击