Effective C++ 摘录

来源:互联网 发布:网络cos 参数 编辑:程序博客网 时间:2024/06/06 00:27

 Item 1: 将 C++ 视为 federation of languages(语言联合体)
最简单的方法是不要将 C++ 视为一个单一的语言,而是一个亲族的语言的 federation(联合体)。在每一个特定的 sublanguage(子语言)中,它的特性趋向于直截了当,简单易记。但你从一个 sublanguage(子语言)转到另外一个,它的规则也许会发生变化。为了感受 C++,你必须将它的主要的 sublanguages(子语言)组织到一起。幸运的是,它只有 4 个:

C ——归根结底,C++ 依然是基于 C 的。blocks(模块),statements(语句),preprocessor(预处理器),built-in data types(内建数据类型),arrays(数组),pointers(指针)等等,全都来自于 C。在很多方面。C++ 提出了比相应的 C 版本更高级的解决问题的方法(例如,参见 Item 2(取代 preprocessor(预处理器))和 13(使用 objects(对象)管理 resources(资源))),但是,当你发现你自己工作在 C++ 的 C 部分时,effective programming(高效编程)的规则表现了 C 的诸多限制范围:没有 templates(模板),没有 exceptions(异常),没有 overloading(重载)等等。
Object-Oriented C++ —— C++ 的这部分就是 C with Classes 涉及到的全部:classes(类)(包括构造函数和析构函数),encapsulation(封装),inheritance(继承),polymorphism(多态),virtual functions (dynamic binding)(虚拟函数(动态绑定))等。C++ 的这一部分直接适用于 object-oriented design(面向对象设计)的经典规则。
Template C++ ——这是 C++ 的 generic programming(泛型编程)部分,大多数程序员对此都缺乏经验。template(模板)的考虑已遍及 C++,而且好的编程规则中包含特殊的 template-only(模板专用)条款已不再不同寻常(参见 Item 46 通过调用 template functions(模板函数)简化 type conversions(类型转换))。实际上,templates(模板)极为强大,它提供了一种全新的 programming paradigm(编程范式)—— template metaprogramming (TMP) (模板元编程)。Item 48 提供了一个 TMP 的概述,但是,除非你是一个 hard-core template junkie(死心塌地的模板瘾君子),否则你不需在此费心,TMP 的规则对主流的 C++ 编程少有影响。
STL —— STL 是一个 template library(模板库),但它一个非常特殊的 template library(模板库)。它将 containers(容器),iterators(迭代器),algorithms(算法)和 function objects(函数对象)非常优雅地整合在一起,但是。templates(模板)和 libraries(库)也可以围绕其它的想法建立起来。STL 有很多独特的处事方法,当你和 STL 一起工作,你需要遵循它的规则。

Item 2: 用 consts, enums 和 inlines 取代 #defines
#define ASPECT_RATIO 1.653
compiler(编译器)也许根本就没有看见这个符号名 ASPECT_RATIO,被 preprocessor(预处理器)消除了。
名字 ASPECT_RATIO 可能就没有被加入 symbol table(符号表)。
编译的时候,发现一个 constant(常量)使用的错误,你可能会陷入混乱之中。
在 symbolic debugger(符号调试器)中也会遇到同样的问题,因为这个名字可能并没有被加入 symbol table(符号表)。

解决方案是用 constant(常量)来取代 macro(宏):


用 constant(常量)代替 #defines 时,有两个特殊情况值得提出。                                   

*首先是关于 constant pointers(常量指针)的定义                                   
例如,在头文件中定义一个 constant char*-based string(基于 char* 的字符串常量)时,你必须写两次 const:
const char * const authorName = "Scott Meyers";

*第二个特殊情况涉及到 class-specific constants(类属(类内部专用的)常量)。
为了将一个 constant(常量)的作用范围限制在一个 class(类)内,
你必须将它作为一个类的 member(成员),为了确保它最多只有一个 constant(常量)拷贝,你还必须把它声明为一个 static member(静态成员)。

class GamePlayer {

private:

  static const int NumTurns = 5;      // constant declaration

  int scores[NumTurns];               // use of constant
  ...
};

你从上面只看到了 NumTurns 的 declaration(声明),而不是 definition(定义)。
通常,C++ 要求你为你使用的任何东西都提供一个 definition(定义),
但是一个 static(静态)的 常量是一个例外。只要你不去取得它们的 address(地址),
你可以只声明并使用它,而不提供它的 definition(定义)。
但如果你要取得一个 class constant(类属常量)的 address(地址),你可以提供如下这样一个独立的 definition(定义):
const int GamePlayer::NumTurns;     // definition of NumTurns; see
                                    // below for why no value is given
你应该把它放在一个 implementation file(实现文件)而非 header file(头文件)中。并且定义处允许没有 initial value(初始值)因为已经在声明的时候给出。

比较老的 compilers(编译器)可能不接受上面的语法,
如果上述语法不能使用,你也可以将 initial value(初始值)放在定义处:

class CostEstimate {

private:

  static const double FudgeFactor;   // declaration of static class
  ...                                // constant; goes in header file
};

const double                         // definition of static class

  CostEstimate::FudgeFactor = 1.35;  // constant; goes in impl. file


如果要定义array一个可接受的替代方案被亲切地(并非轻蔑地)昵称为 "the enum hack"。

class GamePlayer {

private:

  enum { NumTurns = 5 };             // "the enum hack" - makes
                                     // NumTurns a symbolic name for 5

  int scores[NumTurns];              // fine
  ...
};

the enum hack 的行为在几个方面上更像一个 #define 而不是 const,
而有时这正是你所需要的。例如:可以合法地取得一个 const 的 address(地址),
但不能合法地取得一个 enum 的 address(地址),这正像同样不能合法地取得一个 #define 的 address(地址)。
如果你不希望人们得到你的 integral constants(整型族常量)的 pointer(指针)或 reference(引用),
enum(枚举)就是强制约束这一点的好方法。

使用好的 compilers(编译器)不会为 integral types(整型族类型)的 const objects(const 对象)分配多余的内存
(除非你创建了这个对象的指针或引用),即使拖泥带水的 compilers(编译器)乐意,
你也决不会乐意为这样的 objects(对象)分配多余的内存。
像 #defines 和 enums(枚举)就绝不会导致这种 unnecessary memory allocation(不必要的内存分配)。


#define 指令的另一个普遍的(不好的)用法是实现看来像函数,但不会引起一个函数调用的开销的 macros
幸运的是,你并不是必须要和这样不知所云的东西打交道。你可以通过一个 inline function(内联函数)的 template(模板)来获得 macro(宏)的效率,以及完全可预测的行为和常规函数的类型安全(参见 Item 30):

template<typename T>                               // because we don't

inline void callWithMax(const T& a, const T& b)    // know what T is, we

{                                                  // pass by reference-to-

  f(a > b ? a : b);                                // const - see Item 20

}

需要记住的是:

对于 simple constants(简单常量),用 const objects(const 对象)或 enums(枚举)取代 #defines。
对于 function-like macros(类似函数的宏),用 inline functions(内联函数)取代 #defines。


Item 3: 只要可能就用 const

char greeting[] = "Hello";

char *p = greeting;                    // non-const pointer,
                                       // non-const data

const char *p = greeting;              // non-const pointer,
                                       // const data

char * const p = greeting;             // const pointer,
                                       // non-const data

const char * const p = greeting;       // const pointer,
                                       // const data


当指针指向的内容为 constant(常量)时,一些人将 const 放在类型之前,
另一些人将它放在类型之后星号之前。两者在意义上并没有区别,所以,如下两个函数具有相同的 parameter type(参数类型):

void f1(const Widget *pw);         // f1 takes a pointer to a
                                   // constant Widget object

void f2(Widget const *pw);         // so does f2


STL iterators(迭代器)以 pointers(指针)为原型,所以一个 iterator 在行为上非常类似于一个 T* (指针)
注意这里T* 类似于int类型的东西,所以const iterator 代表了这个iterator指针自身是const的。
那怎么定义指针指向内容是const 的呢,没办法只能引入const_iterator

std::vector<int> vec;
...
const std::vector<int>::iterator iter =     // iter acts like a T* const
  vec.begin();
*iter = 10;                                 // OK, changes what iter points to
++iter;                                     // error! iter is const

std::vector<int>::const_iterator cIter =    // cIter acts like a const T*
  vec.begin();
*cIter = 10;                                // error! *cIter is const
++cIter;                                    // fine, changes cIter

对 const 最强有力的用法来自于它在 function declarations(函数声明)中的应用。
class Rational { ... };

const Rational operator*(const Rational& lhs, const Rational& rhs);

const member functions(const 成员函数)

member functions(成员函数)被声明为 const 的目的是标明这个 member functions(成员函数)可能会被 const objects(对象)调用。
member functions(成员函数)即使只有 constness(常量性)不同时也是可以被 overloaded(重载)的

一个 member function(成员函数),当且仅当它不改变 object(对象)的任何 data members(数据成员)(static(静态的)除外),也就是说如果不改变 object(对象)内的任何 bits(二进制位),则这个 member function(成员函数)就是 const。

利用以 mutable 闻名的 C++ 的 const-related(const 相关)的灵活空间。mutable 将 non-static data members(非静态数据成员)从 bitwise constness(二进制位常量性)的约束中解放出来:

需要记住的是:

将某些东西声明为 const 有助于编译器发现使用错误。const 能被用于任何 scope(范围)中的 object(对象),用于 function parameters(函数参数)和 return types(返回类型),用于整个 member functions(成员函数)。
编译器坚持 bitwise constness(二进制位常量性),但是你应该用 conceptual constness(概念上的常量性)来编程。(此处原文有误,conceptual constness为作者在本书第二版中对 logical constness 的称呼,正文中的称呼改了,此处却没有改。其实此处还是作者新加的部分,却使用了旧的术语,怪!——译者注)
当 const 和 non-const member functions(成员函数)具有本质上相同的实现的时候,使用 non-const 版本调用 const 版本可以避免 code duplication(代码重复)。


Item 4: 确保 objects(对象)在使用前被初始化
class FileSystem {                    // from your library

public:
  ...
  std::size_t numDisks() const;       // one of many member functions
  ...
};

extern FileSystem tfs;                // object for clients to use;
                                      // "tfs" = "the file system"

现在假设一些客户为一个 file system(文件系统)中的目录创建了一个 class(类),他们的 class(类)使用了 tfs object(对象):

class Directory {                       // created by library client

public:

   Directory( params );
  ...
};

Directory::Directory( params )

{
  ...
  std::size_t disks = tfs.numDisks();   // use the tfs object
  ...
}

更进一步,假设这个客户决定为临时文件创建一个单独的 Directory object(对象):

Directory tempDir( params );            // directory for temporary files

现在 initialization order(初始化顺序)的重要性变得明显了,
因为定义在不同转换单元内的非局部静态对象的初始化的相对顺序是没有定义的。

一个小小的设计改变从根本上解决了这个问题。全部要做的就是将每一个 non-local static object(非局部静态对象)移到它自己的函数中,在那里它被声明为 static(静态)。
class FileSystem { ... };           // as before

FileSystem& tfs()                   // this replaces the tfs object; it could be
{                                   // static in the FileSystem class
  static FileSystem fs;             // define and initialize a local static object
  return fs;                        // return a reference to it
}

class Directory { ... };            // as before

Directory::Directory( params )      // as before, except references to tfs are
{                                   // now to tfs()
  ...
  std::size_t disks = tfs().numDisks();
  ...
}

Directory& tempDir()                // this replaces the tempDir object; it
{                                   // could be static in the Directory class
  static Directory td;              // define/initialize local static object
  return td;                        // return reference to it
}


需要记住的是:
手动初始化 built-in type(内建类型)的 objects(对象),因为 C++ 只在某些时候才会自己初始化它们。
在 constructor(构造函数)中,用 member initialization list(成员初始化列表)代替函数体中的 assignment(赋值)。initialization list(初始化列表)中 data members(数据成员)的排列顺序要与它们在 class(类)中被声明的顺序相同。
通过用 local static objects(局部静态对象)代替 non-local static objects(非局部静态对象)来避免跨 translation units(转换单元)的 initialization order problems(初始化顺序问题)。


Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
将copy构造函数等定义成私有的,并故意不去实现他们! 这样即使使用,也会被linker发现

class HomeForSale {
public:
  ...

private:
  ...
  HomeForSale(const HomeForSale&);            // declarations only
  HomeForSale& operator=(const HomeForSale&);
};


将 link-time error(连接时错误)提前到编译时间也是可行的(早发现错误毕竟比晚发现好),通过不在 HomeForSale 本身中声明 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)为 private,而是在一个为 prevent copying(防止拷贝)而特意设计的 base class(基类)中声明。这个 base class(基类)本身非常简单:

class Uncopyable {
protected:                                   // allow construction
  Uncopyable() {}                            // and destruction of
  ~Uncopyable() {}                           // derived objects...

private:
  Uncopyable(const Uncopyable&);             // ...but prevent copying
  Uncopyable& operator=(const Uncopyable&);
};

为了阻止拷贝 HomeForSale objects(对象),我们现在必须让它从 Uncopyable 继承:

class HomeForSale: private Uncopyable {      // class no longer
  ...                                        // declares copy ctor or
};       

需要记住的是:

为了拒绝编译器自动提供的机能,将相应的 member functions(成员函数)声明为 private,而且不要给出 implementations(实现)。使用一个类似 Uncopyable 的 base class(基类)是方法之一。


Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
无故地将所有 destructors(析构函数)声明为 virtual(虚拟),和从不把它们声明为 virtual(虚拟)一样是错误的。
实际上,很多人总结过这条规则:declare a virtual destructor in a class if and only if that class contains at least one virtual function
(当且仅当一个类中包含至少一个虚拟函数时,则在类中声明一个虚拟析构函数)。

甚至在完全没有 virtual functions(虚拟函数)时,也有可能纠缠于 non-virtual destructor(非虚拟析构函数)问题。例如,标准 string 类型不包含 virtual functions(虚拟函数),但是被误导的程序员有时将它当作 base class(基类)使用:

class SpecialString: public std::string {   // bad idea! std::string has a
  ...                                       // non-virtual destructor
};

并非所有的 base classes(基类)都被设计用于 polymorphically(多态)。例如,无论是 standard string type(标准 string 类型),还是 STL container types(STL 容器类型)全被设计成 base classes(基类),可没有哪个是 polymorphic(多态)的。

需要记住的是:
如果一个 class(类)不包含 virtual functions(虚拟函数),这经常预示不打算将它作为 base class(基类)使用。当一个 class(类)不打算作为 base class(基类)时,将 destructor(析构函数)虚拟通常是个坏主意。

polymorphic base classes(多态基类)应该声明 virtual destructor(虚拟析构函数)。如果一个 class(类)有任何 virtual functions(虚拟函数),它就应该有一个 virtual destructor(虚拟析构函数)。

不是设计用来作为 base classes(基类)或不是设计用于 polymorphically(多态)的 classes(类)就不应该声明 virtual destructor(虚拟析构函数)。


Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
Terminate the program if close tHRows(如果 close 抛出异常就终止程序),一般是通过调用 abort:
DBConn::~DBConn()
{
 try { db.close(); }
 catch (...) {
   make log entry that the call to close failed;
   std::abort();
 }
}

如果在析构的过程遭遇到错误后程序不能继续运行,这就是一个合理的选择。它有一个好处是:如果允许从 destructor(析构函数)传播 exception(异常)可能会导致 undefined behavior(未定义行为),这样就能防止它发生。也就是说,调用 abort 就可以预先防止 undefined behavior(未定义行为)。

Swallow the exception arising from the call to close(抑制这个对 close 的调用造成的异常):
DBConn::~DBConn()
{
 try { db.close(); }
 catch (...) {
      make log entry that the call to close failed;
 }
}

需要记住的是:
destructor(析构函数)应该永不引发 exceptions(异常)。如果 destructor(析构函数)调用了可能抛出异常的函数,destructor(析构函数)应该捕捉所有异常,然后抑制它们或者终止程序。
如果 class(类)客户需要能对一个操作抛出的 exceptions(异常)做出回应,则那个 class(类)应该提供一个常规的函数(也就是说,non-destructor(非析构函数))来完成这个操作。

 

Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
base class construction(基类构造)期间,virtual functions(虚拟函数)被禁止。

这个表面上看起来匪夷所思的行为存在一个很好的理由。因为 base class constructors(基类构造函数)在 derived class constructors(派生类构造函数)之前执行,当 base class constructors(基类构造函数)运行时,derived class data members(派生类数据成员)还没有被初始化。
际上还有比这更基本的原理。在一个 derived class object(派生类对象)的 base class construction(基类构造)期间,object(对象)的类型是 base class(基类)的类型。不仅 virtual functions(虚拟函数)会解析到 base class(基类),而且用到 runtime type information(运行时类型信息)的语言构件(例如,dynamic_cast(参见 Item 27)和 typeid),也会将那个 object(对象)视为 base class type(基类类型)。

class Transaction {
public:
  Transaction()
  { init(); }                                     // call to non-virtual...

  virtual void logTransaction() const = 0;
  ...

private:
  void init()
  {
    ...
    logTransaction();                             // ...that calls a virtual!
  }
};

这个代码很更阴险

有不同的方法来解决这个问题。其中之一是将 Transaction 中的 logTransaction 转变为一个 non-virtual function(非虚拟函数),这就需要 derived class constructors(派生类构造函数)将必要的日志信息传递给 Transaction constructor(构造函数)。那个函数就可以安全地调用 non-virtual(非虚拟)的 logTransaction。


需要记住的是:

在 construction(构造)或 destruction(析构)期间不要调用 virtual functions(虚拟函数),因为这样的调用不会转到比当前执行的 constructor(构造函数)或 destructor(析构函数)所属的 class(类)更深层的 derived class(派生类)。


Item 12: 拷贝一个 object(对象)的所有 parts(构件)

需要记住的是:

copying functions(拷贝函数)应该保证拷贝一个 object(对象)的所有 data members(数据成员)以及所有的 base class parts(基类构件)。
不要试图依据一个 copying functions(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。


Item 13: 使用 objects(对象)管理资源

auto_ptr 是一个 pointer-like object(类指针对象)(一个 smart pointer(智能指针)),它的 destructor(析构函数)自动在它指向的东西上调用 delete。

因为当一个 auto_ptr 被销毁的时候,会自动删除它所指向的东西,所以不要让超过一个的 auto_ptr 指向一个 object(对象)非常重要。如果发生了这种事情,那个 object(对象)就会被删除超过一次,而且会让你的程序通过捷径进入 undefined behavior(未定义行为)。为了防止这样的问题,auto_ptrs 具有不同寻常的特性:拷贝它们(通过 copy constructor(拷贝构造函数)或者 copy assignment operator(拷贝赋值运算符))就是将它们置为空,而拷贝的指针取得资源的唯一所有权。

STL containers(容器)要求其内含物能表现出“正常的”拷贝行为,所以 auto_ptrs 的容器是不被允许的。

相对于 auto_ptrs,另一个可选方案是一个 reference-counting smart pointer (RCSP)(引用计数智能指针)。一个 RCSP 是一个能持续跟踪有多少 objects(对象)指向一个特定的资源,并能够在不再有任何东西指向那个资源的时候自动删除它的 smart pointer(智能指针)。就这一点而论,RCSPs 提供的行为类似于 garbage collection(垃圾收集)的行为。然而,与 garbage collection(垃圾收集)不同的是, RCSPs 不能打破循环引用(例如,两个没有其它使用者的 objects(对象)互相指向对方)。
Things to Remember

为了防止 resource leaks(资源泄漏),使用 RAII objects(对象),在 RAII objects(对象)的 constructors(构造函数)中获取资源并在它们的 destructors(析构函数)中释放它们。
两个通用的 RAII classes(类)是 tr1::shared_ptr 和 auto_ptr。tr1::shared_ptr 通常是更好的选择,因为它的拷贝时的行为是符合直觉的。拷贝一个 auto_ptr 是将它置为空。


Item 16: 成对使用的 new 和 delete 要使用相同的形式

需要记住的是:

如果你在 new 表达式中使用了 [],就必须在对应的 delete 表达式中也使用 []。如果你在 new 表达式中没有使用 [],你也不必在对应的 delete 表达式中使用 []。

 

Item 17: 在 standalone statements(独立语句)中将 new 出来的 objects(对象)存入 smart pointers(智能指针)

现在考虑一个对 processWidget 的调用:

processWidget(new Widget, priority());

且慢,别想这样调用。它不能编译。tr1::shared_ptr 的取得一个 raw pointer(裸指针)的 constructor(构造函数)是 explicit(显式)的,所以没有从表达式 "new Widget" 返回的 raw pointer(裸指针)到 processWidget 所需的 tr1::shared_ptr 之间的隐式转换。然而,下面的代码,是可以编译的:

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

令人惊讶的是,尽管我们这里到处都在使用 object-managing resources(由对象管理的资源),这个调用还是可能泄漏资源。看看这是如何发生的会得到一些启示。


需要记住的是:

在 standalone statements(独立语句)中将 new 出来的对象存入 smart pointers(智能指针)。如果疏忽了这一点,当 exceptions(异常)被抛出时,可能引起微妙的 resource leaks(资源泄漏)。


Item 18: 让接口容易被正确使用,不易被误用


Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)

需要记住的是:
用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)。典型情况下它更高效而且可以避免 slicing problem(切断问题)。
这条规则并不适用于 built-in types(内建类型)及 STL iterator(迭代器)和 function object(函数对象)类型。对于它们,pass-by-value(传值)通常更合适。

Item 21: 当你必须返回一个对象时不要试图返回一个引用
需要记住的是:

绝不要返回一个局部栈对象的指针或引用,绝不要返回一个被分配的堆对象的引用,如果存在需要一个以上这样的对象的可能性时,绝不要返回一个局部 static 对象的指针或引用。(Item 4 提供的一个返回一个局部 static 的设计的例子是合理的,至少在单线程的环境中是这样。)

Item 22: 将数据成员声明为 private

需要记住的是:

声明数据成员为 private。它为客户提供了访问数据的语法层上的一致,提供条分缕析的访问控制,允许不变量被强制,而且为类的作者提供了实现上的弹性。
protected 并不比 public 的封装性强。


Item 26: 只要有可能就推迟变量定义

// Approach A: define outside loop   // Approach B: define inside loop

Widget w;
for (int i = 0; i < n; ++i){         for (int i = 0; i < n; ++i) {
  w = some value dependent on i;       Widget w(some value dependent on i);
  ...                                  ...
}                                    }

这里我将一个类型 string 的对象换成了一个类型 Widget 的对象,以避免对这个对象的构造、析构或赋值操作的成本的任何已有的预见。

对于 Widget 的操作而言,就是下面这两个方法的成本:

方法 A:1 个构造函数 + 1 个析构函数 + n 个赋值。
方法 B:n 个构造函数 + n 个析构函数。
对于那些赋值的成本低于一个构造函数/析构函数对的成本的类,方法 A 通常更高效。特别是在 n 变得很大的情况下。否则,方法 B 可能更好一些。此外,方法 A 与方法 B 相比,使得名字 w 在一个较大的区域(包含循环的那个区域)内均可见,这可能会破坏程序的易理解性和可维护性。因此得出以下结论:除非你确信以下两点:(1)赋值比构造函数/析构函数对成本更低,而且(2)你正在涉及你的代码中的性能敏感的部分,否则,你应该默认使用方法 B。

需要记住的是:

只要有可能就推迟变量定义。这样可以增加程序的清晰度并提高程序的性能。


Item 27: 将强制转型减到最少
class Widget {
public:
  explicit Widget(int size);
  ...
};

void doSomeWork(const Widget& w);

doSomeWork(Widget(15));                    // create Widget from int
                                           // with function-style cast

doSomeWork(static_cast<Widget>(15));       // create Widget from int
                                           // with C++-style cast
关于强制转型的一件有趣的事是很容易写出看起来对(在其它语言中也许是对的)实际上错的东西。例如,许多应用框架(application framework)要求在派生类中实现虚成员函数时要首先调用它们的基类对应物。假设我们有一个 Window 基类和一个 SpecialWindow 派生类,它们都定义了虚函数 onResize。进一步假设 SpecialWindow 的 onResize 被期望首先调用 Window 的 onResize。这就是实现这个的一种方法,它看起来正确实际并不正确:

class Window {                                // base class
public:
  virtual void onResize() { ... }             // base onResize impl
  ...
};

class SpecialWindow: public Window {          // derived class
public:
  virtual void onResize() {                   // derived onResize impl;
    static_cast<Window>(*this).onResize();    // cast *this to Window,
                                              // then call its onResize;
                                              // this doesn't work!

    ...                                       // do SpecialWindow-
  }                                           // specific stuff

  ...

};

我突出了代码中的强制转型。正像你所期望的,代码将 *this 强制转型为一个 Window。因此调用 onResize 的结果就是调用 Window::onResize。你也许并不期待它没有调用当前对象的那个函数!作为替代,强制转型创建了一个 *this 的基类部分的新的,临时的拷贝,然后调用这个拷贝的 onResize!上面的代码没有调用当前对象的 Window::onResize,然后再对这个对象执行 SpecialWindow 特有的动作——它在对当前对象执行 SpecialWindow 特有的动作之前,调用了当前对象的基类部分的一份拷贝的 Window::onResize。如果 Window::onResize 改变了当前对象(可能性并不小,因为 onResize 是一个 non-const 成员函数),当前对象并不会改变。作为替代,那个对象的一份拷贝被改变。如果 SpecialWindow::onResize 改变了当前对象,无论如何,当前对象将被改变,导致的境况是那些代码使当前对象进入一种病态,没有做基类的变更,却做了派生类的变更。

解决方法就是消除强制转型,用你真正想表达的来代替它。你不应该哄骗编译器将 *this 当作一个基类对象来处理,你应该调用当前对象的 onResize 的基类版本。就是这样:

class SpecialWindow: public Window {
public:
  virtual void onResize() {
    Window::onResize();                    // call Window::onResize
    ...                                    // on *this
  }
  ...

};

需要记住的是:

避免强制转型的随时应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。
如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。
尽量用 C++ 风格的强制转型替换旧风格的强制转型。它们更容易被注意到,而且他们做的事情也更加明确。


Item 28: 避免返回对象内部构件的“句柄”
需要记住的是:

避免返回对象内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,帮助 const 成员函数产生 cosnt 效果,并将空悬句柄产生的可能性降到最低。'


Item 29: 争取异常安全(exception-safe)的代码

需要记住的是:

即使当异常被抛出时,异常安全的函数不会泄露资源,也不允许数据结构被恶化。这样的函数提供基本的,强力的,或者不抛出保证。
强力保证经常可以通过 copy-and-swap 被实现,但是强力保证并非对所有函数都可用。
一个函数通常能提供的保证不会强于他所调用的函数中最弱的保证。


Item 30: 理解 inline 化的介入和排除
需要记住的是:

将大部分 inline 限制在小的,调用频繁的函数上。这使得程序调试和二进制升级更加容易,最小化潜在的代码膨胀,并最大化提高程序速度的几率。
不要仅仅因为函数模板出现在头文件中,就将它声明为 inline。


Item 31: 最小化文件之间的编译依赖
需要记住的是:

最小化编译依赖后面的一般想法是用对声明的依赖取代对定义的依赖。基于此想法的两个方法是 Handle 类和 Interface 类。
库头文件应该以完整并且只有声明的形式存在。无论是否包含模板都适用于这一点。

原创粉丝点击