EC读书笔记系列之14:条款26、27、28、29

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

记住:

★尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率

----------------------------------------------------------------------

举例说明:

std::string encryptPassword( const std::string &password ) {

    using namespace std;
    string encrypted;  //过早定义变量“encrypted”
    if( password.length() < MininumLength ) {

        throw logic_error( "Password is too short" );
    }
    ...
    return encrypted;
}

一旦if语句中异常抛出,则上面的encrypted变量则未被使用,但仍得付出该变量的构造和析构成本,∴最好延后encrypted的定义式直到确实需要它。

更有甚,不只应延后变量的定义,甚至还应延后这份定义直到能给它初值为止,定义和赋初值一并完成,避免无谓的 先default构造 再赋初值。

对于循环的情况:

//方法A:定义于循环外
Widget w;
for( int i=0; i<n; i++ ) {

    w = 取决于i的某个值;
    ...
}

//方法B:定义于循环内
for( int i=0; i<n; i++ ) {

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

方法A 的成本:1个constructor + 1个destructor + n个assignment operate

方法B的成本:n个constructor + n个destructor

若赋值成本低于一组构造+析构成本,这样A大体而言交高效,否则B好。另外A造成w的作用域(覆盖整个循环)比B更大,有时那对程序的可理解性和易维护性造成冲突。

条款27 尽量少做转型动作

记住:

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

★若转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内

★宁可使用C++风格转型(新式转型),不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌

---------------------------------------------------------------------------------------------------

两种旧式转型语法:

  (T)expression

  T(expression) //函数风格的转型动作

C++的四种新式转型:

  const_cast<T>( expression ); //消除对象的常量性

  dynamic_cast<T>( expression ); //安全向下转型(即向派生类端)

  reinterpret_cast<T>( expression ); //执行低级转型

  static_cast<T>( expression ); //强迫隐式转换

“我”(书作者)唯一使用旧式转型的情况是:当我要调用一个explicit构造函数(不允许隐式转换)将一个对象传递给一个函数时:

class Widget {

    public:
        explicit Widget( int size ); //ctor声明为explicit就不会导致隐式转换!!!
        ...
};

void doSomeWork( const Widget &w );
doSomeWork( Widget(15) );  //以一个int加上“函数风格”的转型动作创建一个Widget

不要错误地认为转型其实什么都没做,只是告诉编译器把某个类型视为另一种类型。任何一个类型转化往往真的令编译器编译出运行期间执行的码。

------------------

关于转型的一个错误理解的例子:

假设有个Window基类和SpecialWindow派生类,进一步假设SpecialWindow的onResize被要求首先调用Window的onResize:

class Window {

    public:
        virtual void onResize() {...}
        ...
};

class SpecialWindow : public Window {

    public:
        virtual void onResize() {

            static_cast<Window>(*this).onResize();
            ... //这里进行SpecialWindow专属行为
        }
        ...
};

其实这句static_cast<Window>(*this).onResize();并非在当前对象身上调用Window::onResize之后又在该对象身上执行SpecialWindow专属动作。他是在“当前对象之base class成分”的副本上(!!!)调用Window::onResize,然后在当前对象身上执行SpecialWindow专属动作。这很有可能使当前对象出现“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改倒是落实了。

解决方案如下:避免使用转换

class SpecialWindow : public Window {

    public:
        virtual void onResize() {

            Window::onResize();
            ...
        }
        ...
};

-----------------

除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏与猜疑。∵dynamic_cast的许多实现版本执行速度相当慢!!!

-----------------

之所以需dynamic_cast,通常是因为想在一个derived class对象身上执行derived class函数,但手上仅一个指向base的pointer或reference,有两个一般性做法:

做法一:使用容器并在其中存储直接指向derived class对象的指针(通常是smart pointer)

假设先前的Window/SpecialWindow继承体系中仅SpecialWindow才支持闪烁效果:

不要这样做:
class Window {...};
class SpecialWindow : public Window {

    public:
        void blink();
        ...
};

typedef std::vector< std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for( VPW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) {

    if( SpecialWindow *psw = dynamic_cast<SpecialWindow*>( iter->get() )  ) {

        psw->blink();
    }
}
而应这样做:
typedef std::vector< std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
for( VPSW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) {

    (*iter)->blink(); //这样写比较好,∵摆脱了dynamic_cast
}

做法二:在base class内提供virtual函数做你想对各个Window派生类做的事:

class Window {

    public:
        virtual void blink() {} //缺省实现代码,啥也没做
                //条款34将说明缺省实现代码可能是个馊主意
};

class SpecialWindow : public Window {

    public:
        virtual void blink();
        ...
};

typedef std::vector< std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for( VPW::iterator iter=winPtrs.begin(); iter != winPtrs.end(); iter++ ) {

    (*iter)->blink(); //也摆脱了dynamic_cast
}

-------------------------------------------

绝对要避免“连串dynamic_cast”

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()) ) {...}
    else if( SpecialWindow2 *psw2 = dynamic_cast<SpecialWindow2*>(iter->get()) ) {...}
    else if( SpecialWindow3 *psw3 = dynamic_cast<SpecialWindow3*>(iter->get()) ) {...}
    ...
}

这样产生出来的代码又大又慢,且基础不稳。

条款28 避免返回handles指向对象内部成分

记住:

★避免返回handles(包括reference,指针,迭代器)指向对象内部。遵守这个条款可增加封装性;帮助const成员函数的行为像个const;并将发生“虚吊号码牌”的可能性降到最低。

-------------------------------------------------------------------------

时间: 2024-10-03 08:20:54

EC读书笔记系列之14:条款26、27、28、29的相关文章

EC读书笔记系列之1:条款1、条款2、条款3

条款1:视C++为一个语言联邦 记住: ★C++高效编程守则视状况而变化,这取决于你使用C++的哪一部分 C: Object-oriented c++: Template c++: STL 条款2:尽量以const,enum,inline替换#define 记住: ★对于单纯常量,最好以const对象或enums替换#define ★对于形似函数的宏,最好改用inline函数替换#define ---------------------------------------------------

EC读书笔记系列之3:条款5、条款6、条款7

条款5:了解C++默默编写并调用哪些函数 记住: ★编译器可以(仅仅是可以,并非必须,仅当程序中有这样的用法时才会这么做!!!)暗自为class创建default构造函数,copy构造函数,copy assignment操作符以及析构函数. ---------------------------------------------------------------------------------------------------------------------------------

EC读书笔记系列之17:条款41、42、43、44、45、46

条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函数发生于运行期 ★对templates而言,接口是隐式的(implicit),奠基于有效表达式.多态则是通过template具现化和函数重载解析发生于编译期 条款42 了解typename的双重意义 记住: ★声明template参数时,前缀关键字class和typename可互换(函数模板或类模板

EC读书笔记系列之12:条款22、23、24

条款22 将成员变量声明为private 记住: ★切记将成员变量声明为private.这可赋予客户访问数据的一致性.可细微划分访问控制.允诺约束条件获得保证,并提供class作者以充分的实现弹性. ★protected并不比public更具封装性 条款23 宁以non-member-non-friend替换member函数 记住: ★宁可拿non-member-non-friend函数替换member函数.这样可增加封装性.包裹弹性和机能扩充性. -----------------------

EC读书笔记系列之11:条款20、21

条款20 宁以pass-by-reference-to-const替换pass-by-value 记住: ★尽量以pass-by-reference-to-const替换pass-by-value.前者通常高效,并可避免切割问题 ★以上规则并不适用于内置类型,以及STL的迭代器和函数对象.那些应用pass-by-value 条款21 必须返回对象时,别妄想返回其reference 记住: ★绝不要返回pointer或reference指向一个local stack对象(如函数里的局部对象):或返

EC读书笔记系列之18:条款47、48

条款47 请使用traits classes表现类型信息 记住: ★Traits classes使得“类型相关信息”在编译期可用.它们以templates和“templates特化”完成实现 ★整合重载技术后,traits classes有可能在编译期对类型执行if...else测试 --------------------------------------------------------- 条款48 认识template元编程(TMP) 记住: ★TMP(模板元编程)可将工作由运行期移往

EC读书笔记系列之9:条款16、17

条款16 成对使用new和delete时要采取相同形式 记住: ★若你在new表达式中使用[ ],必须在相应的delete中也使用[ ],反之亦然 ------------------------------------------------------------------ 当结合typedef使用时,应特别注意:如: typedef std::string AddressLines[4]; std::string *pal = new AddressLines; //等同于new str

EC读书笔记系列之10:条款16、17

条款18 让接口容易被正确使用,不易被误用 记住: ★“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容 ★“阻止误用”的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的资源管理责任(即类的设计者应先发制人). ★tr1::shared_ptr支持定制型删除器.这可防范DLL问题,可被用来自动解除互斥锁等等. -------------------------------------------------------------------------- C++的

EC读书笔记系列之20:条款53、54、55

条款53 不要轻忽编译器的警告 记住: ★严肃对待编译器发出的警告信息.努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉 ★不要过度依赖编译器的报警能力,∵不同的编译器对待事情的态度并不相同.一旦移植到另一个编译器上,你原本倚赖的警告信息有可能消失. 条款54 让自己熟悉包括TR1在内的标准程序库 记住: ★C++标准程序库的主要机能由STL.iostreams.locales组成.并包含C99标准程序库. ★TR1添加了智能指针(tr1::shared_ptr).一般化函数指