现在的位置: 首页 > 综合 > 正文

Effective C++读书笔记 (2)

2014年09月05日 ⁄ 综合 ⁄ 共 59154字 ⁄ 字号 评论关闭

下面这份读书笔记更全:


因为手头只有Scott的第二版《EffectiveC++》电子版和第三版纸质印刷版本。本想为了方便,以第二版为模板。仔细看来,二者区别还是不小的,所以还是以第三版为模板。

第一章从C转向C++

我应该算是从CC++的典型了,虽然我不是什么老人,可是从我接触编程至今,区区67年间,我竟是用了5年多的C,而且一直是在TC2.0的环境下。当我决心放弃TC的时候(2006年上半年),距C++之父BjarneStroustrup所著的《TheC++
Programming Language
》问世已经过了21年了,距ScottMeyers的《EffectiveC++》第一版也已16年之久,想来不禁为自己的后知后觉感到惭愧。

条款01:视C++为一个语言联邦

凭个人体会,不同背景的人会将C++看作不同的语言,正应了“横看成岭侧成峰,远近高低各不同”。

C++时代,对于C程序员最大的幸事和不幸都是基于一点:C++>=
C
,我可以抱残守缺,认为C就是C++

当然,大多数C程序员并不会这么做,起码,我不会一直这么做。我首先会积极地用class替代struct,然后试着自定义constructorsdestructors,用protectedvirtual……

这样大概过了半年,我就从一个Structrue-Orientedcoder进化成了一个Object-Orientedcoder

现在,为了工作的方便,在没有经过任何理论学习的情况下,我开始使用STL,却没有去深入探寻STL源代码。当然,我知道这是不够的,也是不好的,等我把《EffectiveC++》看得更好一些,我就去看TemplateSTL

条款02:尽量以constenuminline替换#define

尽量用编译器而不用预处理,作者这样说的原因是因预处理造成的bugs如果在编译时暴露,那将是非常难揪的,尤其是带参数的宏:

#definemax(a,b) ((a) > (b) ? (a) : (b))

inta = 5, b = 0;
max(++a, b);         // a
的值增加了2
max(++a,b+10);       // a
的值只增加了1

原因就在于上述语句在预处理时被处理成:

((++a)> (b) ? (++a) : (b));
((++a) > (b+10) ? (++a) : (b));

危险啊!

即使我可以凭借丰富的经验避免上述错误,但违反OO原则的帽子我是不愿戴的,所以我更愿意用inline函数来代替带参的宏。

即使我只使用无参数的宏,也不如多考虑用constenum。在大多数情况下,用const(如果想保证一个类只有一个对应常量,就用staticconst)来声明常量显然比#define来得高明,此时#define的优势在于其地址不能被获取,而const并不具备这一功能,考虑一下enum吧,OK,问题迎刃而解。

第二版条款02:尽量用<iostream>而不用<stdio.h>
C中,scanfprintf的最大问题是其类型安全的不确定。经常遇到有朋友提出类似的困惑:为什么scanf的数据一团糟,为什么printf的结果和预期的不一致?另一个问题是scanfprintf对于复杂的结构不能扩展使用。

至于使用<iostream>的理由,听听Scott怎么说吧:“iostream库的类和函数所提供的类型安全和可扩展性的价值远远超过你当初的想象,所以不要仅仅因为你用惯了<stdio.h>而舍弃它。毕竟,转换到iostream后,你也不会忘掉<stdio.h>。”

条款03:尽可能使用const

尽可能使用const可能会让你多写很多个const,而且会在你试图改变被const修饰的“变量”时报告一大堆bugs。然而,如果没有了它们,就成了掩耳盗铃了吧?后果是不是不堪设想呢?很简单,你只需要看看const修饰的是谁,就知道谁不能被改变了。

对于返回const的函数,它是为了防止你或者别人对返回的对象进行写操作(赋值)。

bitwiseconst成员函数保证了在函数内部任何成员(static除外)都不会被改变,logicalconst则会给需要改变的成员变量在声明时加上mutable关键字。

第二版条款03:尽量用newdelete而不用mallocfree

C中,我们谨记mallocfree是我们在使用指针时的忠实朋友,在C++中,我们需要为对象、为含有对象的对象进行内存分配的时候,mallocfree就帮不了我们太多了,因为它们不知道还有构造和析构这样的事情。

为了能够把constnon-const严格区别对待,你需要重载函数,这就意味着代码重复。或许你也想到让non-const去调用const或者反过来(有点危险和莫名其妙,我们不是为了const而来的吗?)。

classTextBlock {
public:
  ...
  const char&operator[](std::size_t position) const  // same as before
 {
   ...                                              // do bounds checking
   ...                                              // log access data
   ...                                              // verify data integrity
    returntext[position];
  }

  char&operator[](std::size_t position)             // now just calls const op[]
  {
   return
      const_cast<char&>(        // cast away const on  
这儿很好理解
       static_cast<constTextBlock&>(*this)
                                // add const to *this's type;
避免递归调用自身
         [position]            // call const version of op[]
      );
 }
...
};

条款04:确定对象在使用前已先被初始化

关于语言本身对初始化的保证,从CC++一直是个讨论的热点,Scott说:“alwaysinitialize
your objects before you use them”

classPhoneNumber { ... };

classABEntry {                      // ABEntry = "Address Book Entry"
public:
 ABEntry(const std::string& name, const std::string&address,
         const std::list<PhoneNumber>& phones);
private:
 std::string theName;
  std::string theAddress;
 std::list<PhoneNumber> thePhones;
  int numTimesConsulted;
};

//赋值做法,相信每一个C程序员都对这样的写法倍感亲切
ABEntry::ABEntry(conststd::string& name, const std::string&address,
                const std::list<PhoneNumber>& phones)
{
 theName = name;                      // these are all assignments,
  theAddress =address;                // not initializations
  thePhones = phones
 numTimesConsulted = 0;
}

//成员初始列做法,这才是初始化
ABEntry::ABEntry(conststd::string& name, const std::string&address,
                const std::list<PhoneNumber>& phones)
: theName(name),
 theAddress(address),             // these are now all initializations
  thePhones(phones),
 numTimesConsulted(0)             //
这个built-intype无所谓,不存在效率上的问题
{}                                 // the ctor body is now empty

不仅仅是习惯问题,先调用defaultconstructor再进行copyassignment显然比直接调用copyconstructor要慢半拍(岂止啊)!对于constreferences因为无法赋值,只能使用初始列。而且,初始化顺序也应该遵循先basederivedFCFS的原则。

至于例外也不是没有,为了避免涉及数据读写的大量变量在赋值前多余的初始化(前提是你正确的知道那是多余的),合理改用赋值是明智的。

Therelative order of initialization of non-local static objects definedin different translation units is undefined.”对于differenttranslation
units
中定义的non-localstaticobjects们,我们不可能也没有必要在每次使用时都对其初始化,然而我们又的确需要初始化已经实施过这样的保证,GoF提出的Singleton模式、解决了这个问题。

说句实话,我很少使用non-localstatic objects,但是我也很少使用localstatic
objects
。虽然我对《DesignPattern》看过多遍,但你也体会到了,那又能说明什么呢?

第二版条款04:尽量使用c++风格的注释

为了强调要从C转向C++ScottC的注释风格也一起批判:

if( a > b ) {
// int temp = a; // swap a and b
// a = b;
//b = temp;
}
==>

if( a > b ) {
/* int temp = a;  /* swap a and b */
 a = b;
  b = temp;
*/
}

啊喔,因为注释不能嵌套,上面的修改导致了编译时无法通过。

看来,从CC++,必须要转变的彻底才行。

第二章内存管理

在写程序的时候,尤其是写大程序的时候,尤其尤其是几十个类、几百个类联合出动的时候,我们经常能从编译器得到内存泄露的反馈消息。其实,我不怕程序编译通不过,充其量这不过是个错而已,就怕内存莫名其妙的泄露,那是罪啊!

第二版条款05:对应的newdelete要采用相同的形式

newdelete不仅仅是要成对出现的问题(当然,这是最根本最重要的问题),而更是要正确的成对出现,就像{}一样。Scott说:“如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]。”如果问题仅仅这么简单,那么第二章至此应该结束了,至少本条款应该结束了。

typedefstring addresslines[4];

string*pal = new addresslines;  //
这个typedef使用的太有创意了!
deletepal;                                       //
错误!
delete[] pal;                                    //
正确

第二版条款06:析构函数里对指针成员调用delete

对指针的使用,很多人在C时代就是诚惶诚恐,到了C++时代更是临深履薄。上面我说过,对于C++程序员,内存泄露犹如犯罪,而错误的使用指针则是C++程序员最大的罪名。

Scott给了三条原则:

  • 在每个构造函数里对指针进行初始化。对于一些构造函数,如果没有内存要分配给指针的话,指针要被初始化为0(即空指针)

  • 删除现有的内存,通过赋值操作符分配给指针新的内存。

  • 在析构函数里删除指针。

 

如果你用了兄弟什么东西,忘了还,兄弟不怪你;如果你用了内存,千万要还,千万要还啊!哪怕是一个空指针,你最好也把它删除,因为删除空指针是安全的。还记得当年汪精卫说过什么吗?“宁可错杀三千,不可使一人漏网”,如果你自认为不是一个英明的神(不仅能照顾自己,还能救人水火的神),就学学汪精卫的这种精神吧!

当然,为了和汪精卫有区别,你还要记住最基本的一点:newdelete要成对出现,如果你没有new过,那你就不要delete,过于残暴的delete所有指针毕竟不能修成正果。

第二章Constructors,Destructors,
and Assignment Operators

这一章内容参考StanleyB.
Lippman
的《Insidethe
C++ Object Model
》也会有不小的收获。

条款05Knowwhat
functions C++ silently writes and calls

在最早接触C++class的时候,你就被告知每一个class在缺省情况下都会具有:defaultconstructorscopyconstructorsdestructorscopyassignment
operators

当对象的成员也是复杂对象的时候,在使用copyconstructorcopyassignment
operator
时,要注意成员对象的copyconstructorcopyassignment
operator
应该是逻辑正确的(而不仅是语法正确的)。在使用copyconstructorcopyassignment
operator
时,要注意成员对象的copyconstructorcopyassignment
operator
应该是逻辑正确的(而不仅是语法正确的)。

当对象的成员是复杂对象的引用的时候,当复杂对象含有const成员的时候,问题变得更加复杂:当多个引用指向同一个对象并试图对其进行修改时,危险是显而易见的,而更改const成员更是不合法的。

template<classT>
class NamedObject {
public:
  // this ctor nolonger takes a const name, because nameValue
  // is now areference-to-non-const string. The char* constructor
  // isgone, because we must have a string to refer to.
 NamedObject(std::string& name, const T& value);
 ...                         // as above, assume no operator= is declared

private:
 std::string& nameValue;      // this isnow a reference
  const T objectValue;        // this is now const
};

还有就是当baseclass声明其copyassignment
operator
private时,编译器不会为derivedclass自动生成copyassignment
operator
,对于derivedclass的对象无法调用的私有成员函数,只能在编译时报错了。

usingnamespace std;

template<typename T>
class CBase
{
public:
......

private:
CBase&operator=(const CBase &rhs){m_T = rhs.m_T;}  //
不能被子类的drv2访问

private:
Tm_T;
};

template<typename T>
class CDerived : public CBase <T>
{
......
};

int_tmain(int argc, _TCHAR* argv[])
{
CDerived<int> drv1,drv2;
drv2 = drv1;

return0;
}

条款06Explicitlydisallow
the use of compiler-generated functions you do not want

能够让编译器自动为我们生成defaultconstructorscopyconstructorsdestructorscopyassignmentoperators,这一切看上去很安逸,可是如果你不需要它们呢?或者你不希望别人使用它们呢?在STL中很多对象是不允许使用copyconstructorscopyassignment
operators
。如果你不想别人用,那就给它个private吧!这样,即使是其它memberfunctions
or friend function
霸王硬上弓也将导致linkingerror,想让这样的error也在compiling时期报告出来,让你的类从Uncopyable派生:

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

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

条款07Declaredestructors
virtual in polymorphic base classes

在最早接触destructor的时候,我所见到的destructor前面大多是有virtual的,很长一段时间都以后那是习惯,抑或是规定。直到有人提出why,我才跟着想:是啊,Why

如果derivedclass
object
baseclass
object
数据成员更多(大多数情况下是这样),这意味着derivedclass
object
占用了更多的内存。一个指向derivedclass
object
baseclass
type pointer
delete时,被销毁的自然只会是baseclass
type
大小的内存块。

为什么不加个virtual呢?问题解决了。

StanleyStanleyB.
Lippman
,《Insidethe
C++ Object Model
》)和Scott都告诉我们,如果一个classvirtualfunction(包括virtualdestructor),该class会包含一个vtblvirtualtable),而其对象则必然有一指向vtblvptrvirtualpointer)。

这样来看,把一个不带virtualdestructor的类作为baseclass是不合适的,而让一个不可能充当baseclass的类成员函数带上virtual也是没有道理的。即使一个类可能作为baseclass,而不用于polymorphic用途也无需声明virtualdestructor

个人认为,只有当derivedclass
object
baseclass
object
所占内存大小不一致时,virtualdestructor才是必需的,其它情况下都没有必要。

条款08Preventexceptions
from leaving destructors

异常处理也是一个常说常新的话题,Scott假定exceptions出现在destructors当中,很难想像destructors中的excelptionshandles再次抛出异常会怎么样。

Scott说:Destructorsshould
never emit exceptions
,否则:they'rein
no position to complain if the class swallows the exception orterminates the program. After all, they had first crack at dealingwith the problem, and they chose not to use it.

看来,异常处理还是应该放在普通函数中处理才好。

条款09Nevercall
virtual functions during construction or destruction

这一条款初看莫名其妙:constructor/destructorvirtualfunctions的关系有什么特别吗?仔细一想:constructderived
class object
时,先调用的是baseclassconstructor,则derivedclass的成员显然不可能被初始化,所调用的virtualfunctions也不可能被指为derivedclass。析构时刚好相反,derivedclass
object
的成员变量被析构掉后,baseclass中也无法看到它们。

条款10Haveassignment
operators return a reference to *this

并非什么强制规定,仅仅是为了能够连续赋值:

intx, y, z;

//x = (y = (z = 15));
x = y = z = 15;                       // chain of assignments

classWidget {
public:
  ...
Widget& operator=(constWidget& rhs)   // return type is a referenceto
{                                     // the current class
  ...
  return*this;                       // return the left-hand object
}
  ...
};

条款11Handleassignment
to self in operator=

w= w;                                    // assignment to self
a[i] = a[j];                              // potential assignment to self
*px =*py;                                // potential assignment to self

classBitmap { ... };

classWidget {
  ...
private:
  Bitmap*pb;                             // ptr to a heap-allocated object
};

Widget&Widget::operator=(const Widget& rhs)   // unsafeimpl. of operator=
{
// 1 unsafe
  deletepb;                              // stop using current bitmap
// 2 safe : unless new operationfailed
  if (this != &rhs)     //identity test: if a self-assignment, do nothing
  {
   pb = new Bitmap(*rhs.pb);       //start using a copy of rhs's bitmap
  }

//3 safe : no identity test
  Bitmap *pOrig = pb;              // remember original pb
  pb = new Bitmap(*rhs.pb);        // make pb point to a copy of *pb
  deletepOrig;                    // delete the original pb

//4 safe : moving the copying operation from the body of thefunction
//        toconstruction of the parameter
  Widgettemp(rhs);                // make a copy of rhs's data
 swap(temp);                      // swap *this's data with the copy's

  return*this;                    // see Item 10
}

个人喜欢第2种和第4种。

条款12Copyall
parts of an object

voidlogCall(const std::string& funcName);       // make a log entry
class Customer {
public:
  ...
//
自己声明copyingfunctions
  Customer(const Customer& rhs);
 Customer& operator=(const Customer& rhs);
  ...

private:
 std::string name;
  Date lastTransaction;
};

此时的问题是,如果你不能保证每一个成员变量都被copy,编译器不会告诉你这一点,即使是继承时也不会。

Whenyou're writing a copying function, be sure to:

(1)Copying functions should be sure to copy all of an object's datamembers and all of its
base class parts.

(2)Don't try to implement one of the copying functions in terms of theother. Instead, put
common functionality in a third function thatboth call.

Chapter3. Resource Management

Scott说:这儿的resource包括dynamicallyallocated
memory
filedescriptorsmutexlocksGUIobjectsdatabaseconnectionsnetworksockets

拿了我的给我还回来,吃了我的给我吐出来”……

Item13 - 17

条款13Useobjects
to manage resources

voidf()
{
  Investment *pInv = createInvestment();        // call factory function
 ...                                           // use pInv
  delete pInv;                                  // releaseobject
}
为了防止程序在delete之前return或因exception中断而无法调用delete,可使用smartpointer
auto_ptr(a pointer-like object, its destructor automaticallycalls delete on what it points to)

//Resource Acquisition Is Initialization(RAII)
void f()
{
 std::auto_ptr<Investment> pInv(createInvestment());  //call factory function
 ...                                           // use pInv asbefore
}                                               // automatically delete pInv via auto_ptr's dtor

auto_ptr的初始化(也可以用assignment)和resource的分配在同一条语句中完成,其destructor保证了资源会被release。为了保证在任意时刻只有一个auto_ptr指向同一个资源(这也是其缺陷),当一个auto_ptrcopy给另一auto_ptr对象之后,前一个将被置为null,这很安全,却也很麻烦。所以在使用auto_ptr指针时要注意copy操作。

如果希望多个指针指向同一资源,可使用引用计数智能指针(reference-countingsmart
pointer, RCSP
TR1tr1::shared_ptr指针。

auto_ptrtr1::shared_ptr都不能用于动态分配数组,因为二者都没有delete[]的用法。boost::scoped_arrayboost::shared_array可以分配数组。

条款14Thinkcarefully
about copying behavior in resource-managing classes

条款13主要针对heap-based的资源进行管理,不是所有资源都是heap-based,如mutex

voidlock(Mutex *pm);              // lock mutex pointed to by pm
void unlock(Mutex *pm);            // unlock the mutex
借用条款13RAII思想,设计classLock

classLock : private Uncopyable               // prohibit copying
{
public:
  explicit Lock(Mutex*pm) : mutexPtr(pm)
  { lock(mutexPtr);}                         // acquire resource
  ~Lock() { unlock(mutexPtr);}               // release resource

private:
 Mutex *mutexPtr;
};

对于申请类似criticalresources,同样可借用RCSP的思想实现,对criticalresources的申请和分配进行计数。这里的一个问题是在tr1::shared_ptr析构时如果counter0deletemutex,因此可以使用functionobject实现如下:

classLock {
public:
  explicit Lock(Mutex *pm)      // init shared_ptr with the Mutex
  : mutexPtr(pm,unlock)         // to pointto and the unlock func
  {                             // as the deleter
    lock(mutexPtr.get());       // see Item 15 for info on "get"
                                // no longer declares a destructor
  }

private:
 std::tr1::shared_ptr<Mutex> mutexPtr;    // useshared_ptr
};                                        // instead of raw pointer

条款15Provideaccess
to raw resources in resource-managing classes

为了能使资源顺利释放,Scott提倡我们使用auto_ptrtr1::shared_ptr,友情提示:智能指针auto_ptrtr1::shared_ptr不是指针,而是pointer-likeobject。因此如果要访问原始资源,需要从其中取出。

std::tr1::shared_ptr<Investment>pInv(createInvestment());  // from Item 13
// return numberof days investment has been held
int daysHeld(const Investment*pi);
// fine, passes the raw pointer in pInv to daysHeldint days= daysHeld(pInv.get());

说白了,使用auto_ptrtr1::shared_ptr时,记得它们是对象就行了。

犹如在使用string时,为了得到字符串,你需要这样去用:

stringstr("Hello");
char *pstr = str.c_str();

除了类似的explicitconvertion,也有implicitconvertion

classFont {
public:
  ...
  operator FontHandle() const{ return f; }  // implicit conversion function
  ...
};

Fontf(getFont());
int newFontSize;
...
changeFontSize(f,newFontSize);  // implicitly convert Font to FontHandle

诚如Scott所言:Thebest
design is likely to be the one that adheres to make interfaceseasy to use correctly and hard to use incorrectly.

至于对原始资源访问所造成的contraryto
encapsulation
,则可以通过design使client取其所取,避其所避。

对于C/C++程序员来讲,诸如“{”和“}”,“[”和“]”,“"”和“"”GetReleasemallocfreenewdelete这样的搭配我们早已习惯,而对于借助对象的constructordestructor来完成资源“自动”的分配和释放这样的安全操作显然还需要时间去适应。

看到这一款,我越来越觉得我的C++基础需要进一步加强。不知道Scott新加的这第三章内容是过于艰涩,还是怎么,总觉得似乎简单,但又没有那么简单,理解的不够深刻。

条款16Usethe
same form in corresponding uses of new and delete

这一款在第二版时也是有的,第三版还是加了点内容(尽管不多,却让内容更加易于理解),首先对Scott的态度表示尊敬。

newdelete不仅仅是要成对出现的问题(当然,这是最根本最重要的问题),而更是要正确的成对出现,就像{}一样。当new被使用时,发生了两件事:内存分配和constructor(s)调用。当delete被使用时,也发生了两件事:destructor(s)调用和内存回收。问题是你用了什么就要还回什么,你用了多少就要还回多少。

因此,Scott说:“如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]”

typedefstring addresslines[4];

string*pal = new addresslines;  //
这个typedef使用的太有创意了!
deletepal;                                       //
错误!
delete[] pal;                                    //
正确

条款17Storenewed
objects in smart pointers in standalone statements

intpriority();
void processWidget(std::tr1::shared_ptr<Widget>pw, int priority);

//tr1::shared_ptr's constructor taking a raw pointer isexplicit
processWidget(new Widget,priority());                                      // error!
processWidget(std::tr1::shared_ptr<Widget>(newWidget), priority());        // OK!

对于初次看到ScottResourceManagement这一章的coders,我不知道大家看过之后能领会多少,反正我自己心里没有底,各种智能指针的使用,想必也和个人的智商成一定正比。总觉得看过之后,再去写代码,难保不是邯郸学步。

这时候,Scott又说:althoughwe're
using object-managing resources everywhere here, this call mayleak resources……

BeforeprocessWidget can be called, then, compilers must generate code to dothese three things:

1)Call priority.

2)Execute "new Widget".

3)Call the tr1::shared_ptr constructor.

然而,thecall
to priority can be performed first, second, or third.
如果不幸的成了这样:

1)Execute "new Widget".

2)Call priority.

3)Call the tr1::shared_ptr constructor.

如果更加不幸的是:thecall
to priority yields anexception
。不要因为这种可能只有不到0.01%,如果被你的客户和老板抓到,那就是100%

//store newed object in a smart pointer in a standalonestatement
std::tr1::shared_ptr<Widget> pw(newWidget);
processWidget(pw, priority());               // this call won't leak

Scott建议:Storenewed
objects in smart pointers in standalone statements. Failure todo this can lead to subtle resource leaks when exceptions are thrown.

粗略地看完这一章,Scott带我们实现了ResourcesManagement

1)Use objects(smart pointers objects) to manage resources: auto_ptr,tr1::shared_ptr and
boost::shared_array;

2)Think carefully about copying behavior in resource-managing classes:prohibit copying or
reference-count, delete or unlock;

3)Provide access to raw resources in resource-managing classes:explicit or implicit, safer
or more convenient;

4)Use the same form in corresponding uses of new and delete: new anddelete, new [] and delete
[];

5)Store newed objects in smart pointers in standalone statements: avoidexception thrown
between new and copy to tr1::shared_ptr.

Chapter4. Designs and Declarations

什么样的接口是你应该提供给client的?或者,换句话说:什么样的接口是你希望被提供的?

Ifan attempted use of an interface won't do what the client expects,the code won't compile;
and if the code does compile, it will do whatthe client wants.

如果你的programs不是写给你自己用,就应该实现上面的要求。

Item18 - 25

条款18Makeinterfaces
easy to use correctly and hard to use incorrectly

想满足“易用难错”这样苛刻的要求,从编程的角度来看并不难。问题是随着你对接口及数据要求的增加,整个代码也将变得很冗繁,寻找到一个平衡点即可。

条款19Treatclass
design as type design

Howshould objects of your new type be created and destroyed?构造、析构、分配、释放。

Howshould object initialization differ from object assignment?
初始化!=赋值,因为构造!=赋值。

Whatdoes it mean for objects of your new type to be passed by value?这基本意味着你要重写copyconstructor

Whatare the restrictions on legal values for your new type?我们的codes在大多数情况下就是为了验证和处理约束条件及其引发的异常,这些工作应该在构造、赋值时被很好地完成。

Doesyour new type fit into an inheritance graph?
主要是要考虑virtualfunctionvirtualdestructor

Whatkind of type conversions are allowed for your new type?

Whatoperators and functions make sense for the new type?
成员、友元、静态?

Whatstandard functions should be disallowed?要么将其声明为private(不是万无一失的),要么就private继承一个Uncopable类。

Whoshould have access to the members of your new type?privateprotectedpublicfriend

Whatis the "undeclared interface" of your new type?

Howgeneral is your new type?
要想充分一般化,就用classtemplate

Isa new type really what you need?
冷静,看看GoFDP

条款20Preferpass-by-reference-to-const
to pass-by-value

byvalue还是byreference的根本区别在于效率和安全。所谓效率体现在byreference避免了copy,而安全则体现在避免了slicing(对象切割)。将一个derivedclass
iobject
byvalue传给baseclass
object
将发生slicing

需要注意的是,对于build-intypeSTL迭代器和函数对象最好还是用byvalue

条款21Don'ttry
to return a reference when you must return an object

constRational& operator*(const Rational& lhs,   //warning! bad code!
                         const Rational& rhs)
{
  Rational result(lhs.n *rhs.n, lhs.d * rhs.d);
  return result;
}

constRational& operator*(const Rational& lhs,    //warning! more bad
                         const Rational& rhs)    // code!
{
 Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
 return *result;
}

Rationalw, x, y, z;
w = x * y * z;                    // same as operator*(operator*(x, y), z)

constRational& operator*(const Rational& lhs,    //warning! yet more
                         const Rational& rhs)    // bad code!
{
 static Rational result;            // static object to which a
                                     // reference will be returned
  result = ...;                     // multiply lhs by rhs and putthe
                                     // product inside result
  return result;
}

booloperator==(const Rational& lhs,           // an operator==
               const Rational& rhs);          // for Rationals
Rational a, b, c, d;
...
if ((a * b) == (c* d))  {
    do whatever's appropriate whenthe products are equal;
} else    {
   do whatever's appropriate when they're not;
}

if(operator==(operator*(a, b), operator*(c, d)))

//the right way
inline const Rational operator*(const Rational&lhs, const Rational& rhs)
{
  return Rational(lhs.n *rhs.n, lhs.d * rhs.d);
}

条款22Declaredata
members private

我知道public不可取,但我觉得用protected对子类是一种方便。

条款23Prefernon-member
non-friend functions to member functions

自从开始用C++,我自己在写代码的时候,几乎再没用过non-membernon-friend函数。有时候coding纯粹是coding,没有任何比较和规划,写哪儿是哪儿。Scott提醒了我:coding的时候要记得什么才是真正的OO

条款24Declarenon-member
functions when type conversions should apply to allparameters

Ifyou need type conversions on all parameters to a function (includingthe one pointed to
by the this pointer), the function must be anon-member.

记得在社区的一篇帖子里回答一个关于友元的问题的时候,我曾经这样说:

任何时候都最好不要用,包括ops

所谓friend,就是可以帮你的忙,但是有代价:代价就是他了解了你的情况,canaccess
your resources.
破坏了封装性,故称“白箱复用”。

如果实在不能通过自身的class实现功能,就用:Adaptor(具体参考GoF<DesignPatterns>).

当然,如果你觉得用Adaptor很麻烦,那么,在你想access你本来无法accessresources的时候,你就使你成为他的friend

这就是它的好处,也是它的坏处。

条款25Considersupport
for a non-throwing swap

namespacestd {
  template<typename T>         // typical implementation of std::swap;
  void swap(T& a,T& b)         // swapsa's and b's values
  {
    T temp(a);
   a = b;
    b = temp;
  }
}

classWidget {                    // same as above, except for the
public:                           // addition of the swap mem func
  ...
  voidswap(Widget& other)
  {
    usingstd::swap;              // the need for this declaration
                                  // is explained later in this Item
    swap(pImpl,other.pImpl);      // to swap Widgets, swaptheir
  }                               // pImpl pointers
  ...
};

namespacestd {
  template<>                      // revised specialization of
  void swap<Widget>(Widget&a,     // std::swap
                   Widget& b)
  {
   a.swap(b);                    // to swap Widgets, call their
 }                               // swap member function
}

namespaceWidgetStuff {
  ...                                    // templatized WidgetImpl, etc.
  template<typenameT>                   // as before, including the swap
  class Widget { ...};                  // member function
  ...
  template<typenameT>                   // non-member swap function;
  void swap(Widget<T>&a,                // not part of the std namespace
           Widget<T>& b)                                        
 {
    a.swap(b);
  }
}

template<typenameT>
void doSomething(T& obj1, T& obj2)
{
 using std::swap;          // make std::swap available in this function
  ...
 swap(obj1, obj2);         // call the best swap for objects of type T
  ...
}

Chapter5. Implementations

如果说对于对象模型的深刻理解是为了更好地设计和声明,那么对于实现的斤斤计较则是为了更高的效率。

Item26 - 28

条款26Postponevariable
definitions as long as possible

不要过早地定义变量,直到真正需要它;对于非build-in对象,能够用copyconstructor替代时,就不要让它用defaultconstructorcopyassignment

对于循环结构,考虑下面的两种做法哪个成本高:

//Approach A: define outside loop
// 1 constructor + 1 destructor +n copy assignments
Widget w;
for (int i = 0; i < n; ++i){
 w = some value dependent on i;
  ...
}

//Approach B: define inside loop
// n constructors + ndestructors
for (int i = 0; i < n; ++i) {
  Widgetw(some value dependent on i);
  ...
}

条款27Minimizecasting

const_cast<T>(expression): const_cast is typically used to cast away the constness of objects.It
is the only C++-style cast that can do this.

dynamic_cast<T>(expression): dynamic_cast is primarily used to perform "safe downcasting,"i.e.,
to determine whether an object is of a particular type in aninheritance hierarchy. It is the only cast that cannot be performedusing the old-style syntax. It is also the only cast that may have asignificant runtime cost.

reinterpret_cast<T>(expression): reinterpret_cast is intended for low-level casts that yieldimplementation-dependent
(i.e., unportable) results, e.g., casting apointer to an int. Such casts should be rare outside low-level code.

static_cast<T>(expression): static_cast can be used to force implicit conversions (e.g.,non-const
object to const object (as in Item 3), int to double,etc.). It can also be used to perform the reverse of many suchconversions (e.g., void* pointers to typed pointers, pointer-to-baseto pointer-to-derived), though it cannot cast from const to non-constobjects.
(Only const_cast can do that.)

感觉Scott在这一款中对于cast的讲解不是很清楚,从网上搜到一些文章,只是这些文章的内容大同小异,难以知道确切出处。

static_cast

用法:static_cast<
type-id > ( expression )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。

用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

把空指针转换成目标类型的空指针。

把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expressionconstvolitale、或者__unaligned属性。

dynamic_cast

用法:dynamic_cast<
type-id > ( expression )

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

在类层次间进行上行转换时,dynamic_caststatic_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

classB{
public:
  int m_iNum;
  virtual void foo();
};

classD:public B{
  public:
  char *m_szName[100];
};

voidfunc(B *pb){
  D *pd1 = static_cast<D *>(pb);
 D *pd2 = dynamic_cast<D *>(pb);
}

在上面的代码段中,如果pb指向一个D类型的对象,pd1pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Insidec++
object model>
)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

另外,dynamic_cast还支持交叉转换(crosscast)。如下代码所示:

classA{
public:
  int m_iNum;
  virtual void f(){}
};

classB:public A{
};

classD:public A{
};

voidfoo(){
  B *pb = new B;
  pb->m_iNum = 100;
 D *pd1 = static_cast<D *>(pb); //compile error
  D *pd2= dynamic_cast<D *>(pb); //pd2 is NULL
  delete pb;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用dynamic_cast的转换则是允许的,结果是空指针。

reinpreter_cast

用法:reinpreter_cast<type-id>(expression)

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

const_cast

用法:const_cast<type_id>(expression)

该运算符用来修改类型的constvolatile属性。除了constvolatile修饰之外,type_idexpression的类型是一样的。

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

Voiatileconst类似。举如下一例:

classB{

public:

intm_iNum;

}

voidfoo(){

constB b1;

b1.m_iNum= 100; //compile error

Bb2 = const_cast<B>(b1);

b2.m_iNum = 200; //fine
}

上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1b2是两个不同的对象。

条款28Avoidreturning
"handles" to object internals

这儿的handle,可以是pointerreference,在这种时候,我们很清晰地看到:bitwiseconstness显得多么无力。另外一点就是,返回handles可能导致danglinghandles

classGUIObject { ... };

classRectangle {
public:
  ...
  const Point&upperLeft() const { return pData->ulhc; }
  const Point&lowerRight() const { return pData->lrhc; }
  ...
};

constRectangle                            // returns a rectangle by
  boundingBox(const GUIObject&obj);        // value; see Item 3for why
                                           // return type is const
GUIObject*pgo;                            // make pgo point to
...                                        // some GUIObject
const Point *pUpperLeft =                  // get a ptr to the upper
 &(boundingBox(*pgo).upperLeft());        // left point of its
                                           // bounding box
一个对临时对象中Point对象的引用,临时对象会在语句结束后被析构,形成danglinghandle

如果出于效率的考量返回handles,你就必须保证正确地使用它们。

Chapter6. Inheritance and Object-Oriented Design

Item32 - 34

条款32Makesure
public inheritance models "is-a"

在写代码的时候,如果我用public的方式从baseclasses继承derivedclasses,那是因为别人也是这样做的,或者,是因为我在多个derivedclases中要用到baseclassse的相同接口和成员变量,而且我希望derivedclasses能像baseclasses一样使用所有这一切。

Scott提醒我们,不是所有的bird都能fly,例如penguin;如果这是可以理解的,离奇的是:squareis
a rectangle
不成立,我左思右想,难以理解,往下一看:矩形可以任意加高加宽……

Thingsto Remember

Publicinheritance means "is-a." Everything that applies to baseclasses must also apply to
derived classes, because every derivedclass object is a base class object.

条款33Avoidhiding
inherited names

baseclassesmemberfunctionsderivedclasses重载,这就意味着在derivedclasses中,被重载函数的baseclassesmemberfunctions将从此永久性地淡出你的视野。鱼与熊掌兼得的方法还是有的:

classDerived: public Base {
public:
  using Base::mf1;      // make all things in Base named mf1 and mf3
  usingBase::mf3;       // visible (andpublic) in Derived's scope

  virtualvoid mf1();
  void mf3();
  void mf4();
 ...
};

按照Scott的说法,我们必须要保证publicinheritanceis-a关系,也就必须使用using来保证baseclasses的所有函数被publicinheritance。否则,就不要使用publicinheritance。当然在privateinheritance中也有办法使用baseclasses的函数:forwardingfunctions

classDerived: private Base {
public:
  virtual voidmf1()                  // forwarding function; implicitly
  { Base::mf1();}                    // inline (see Item 30)
  ...
};

Thingsto Remember

1)Names in derived classes hide names in base classes. Under publicinheritance, this is
never desirable.

2)To make hidden names visible again, employ using declarations orforwarding functions.

条款34Differentiatebetween
inheritance of interface and inheritance of implementation

在读过GoF后,我对interfaceinheritanceimplementationinheritance的理解是:在baseclassesinterfaceinheritance
functions
声明为purevirtual,而implementationinheritance
functions
声明为non-virtual,二者兼有的声明为impurevirtual

classShape {
public:
  // have derived classes inherit afunction interface only
  virtual void draw() const = 0;
 // have derived classes inherit a function interface as well as adefault implementation
  virtual void error(conststd::string& msg);
  // have derived classes inherit afunction implementation only
  int objectID() const;
 ...
};

在看过这一款之后,我整理了以下几点:

1)pure virtual functions也可以被调用:

Shape*ps = new Ellipse;          // fine
ps->Shape::draw();                // calls Shape::draw

原来,purevirtaul
functions
必须在derivedclasses中重新声明并实现,但也可以在baseclasses中被实现。

2)impure virtual functions的最大问题在于即使derivedclasses很容易因为使用缺省impurevirtual
functions
而导致隐患,两种处理方式:

a)改作purevirtual
functions
,在baseclasses中只提供接口,并定义protectednon-virtualdefault
implementations
,以保证其不被重写,而在derivedclasses中予以实现时,直接调用baseclasses中的non-virtualdefault
implementations

b)既然purevirtaul
functions
也可以在baseclasses中被实现,这样的purevirtaulfunctions就可以被看作两部分:declaration表现的是interfacesdefinition表现的是implementations

3)没有实现的purevirtual
functions
只提供接口;有实现的purevirtual
functions
提供接口和缺省实现;non-virtualfunctions提供接口和强制实现。看来没有impurevirtual
functions
什么事儿了。

Thingsto Remember

1)Inheritance of interface is different from inheritance ofimplementation. Under public
inheritance, derived classes alwaysinherit base class interfaces.

2)Pure virtual functions specify inheritance of interface only.

Simple(impure) virtual functions specify inheritance of interface plusinheritance of a default
implementation.

3)Non-virtual functions specify inheritance of interface plusinheritance of a mandatory
implementation.

Chapter6. Inheritance and Object-Oriented Design

Item36 - 37

条款36Neverredefine
an inherited non-virtual function

classB {
public:
  void mf();
  ...
};

classD: public B {
public:
  void mf();
...
};

Dx;                             // x is an object of type D
B *pB = &x;                      // get pointer to x
pB->mf();                        // calls B::mf
D *pD = &x;                      // get pointer to x
pD->mf();                        // calls D::mf

non-virtualfunctionsstaticallybound,此处mf的调用取决于其声明类型而非所指对象;virtualfunctionsdynamicallybound,如果mfvirtualfunction,其调用将取决于其所指对象,即D

这儿,出于以下两点原因,Neverredefine
an inherited non-virtual function

1)public inheritance应保证is-a关系。

2)non-virtual function的不变性(invariant)应凌驾于特异性(specialization)。

Thingsto Remember

1)Never redefine an inherited non-virtual function.

条款37Neverredefine
a function's inherited default parameter value

redefinea function,那么这个function一定是virtualfunction了,virtualfunctions
are dynamically bound, but default parameter values arestatically bound

//a class for geometric shapes
class Shape {
public:
 enum ShapeColor { Red, Green, Blue };
  // all shapes mustoffer a function to draw themselves
  virtual voiddraw(ShapeColor color = Red) const = 0;
  ...
};

classRectangle: public Shape {
public:
  // notice thedifferent default parameter value — bad!
  virtual voiddraw(ShapeColor color = Green) const;
  ...
};

Shape*ps = new Rectangle;       // statictype = Shape*, dynamic type =Rectangle*
ps->draw(Shape::Red);           // calls Rectangle::draw(Shape::Red)
ps->draw();                     // calls Rectangle::draw(Shape::Red)!

由于psdynamictypeRectangle,所以解析为Rectangle::draw(),由于psstatictypeShape,所以解析为Rectangle::draw(Shape::Red)

因此,应将带defaultparameter
value
的函数声明为non-virtual,其实现用一个privatevirtual
function
完成:

classShape {
public:
  enum ShapeColor { Red, Green, Blue };
 void draw(ShapeColor color = Red) const          // now non-virtual
  {
   doDraw(color);                                 // calls a virtual
  }
  ...

private:
 virtual void doDraw(ShapeColor color) const = 0;  // the actualwork is
};                                                 // done in this func

classRectangle: public Shape {
public:
  ...

private:
 virtual void doDraw(ShapeColor color) const;      // note lack of a
 ...                                               // default param val.
};

Thingsto Remember

Neverredefine an inherited default parameter value, because defaultparameter values are
statically bound, while virtual functions —the only functions you should be overriding — are dynamicallybound.

Chapter7. Templates and Generic Programming

Item43 - 44

条款43Knowhow
to access names in templatized base classes

下面的code是无法通过编译的:

template<typenameCompany>
class LoggingMsgSender: public MsgSender<Company>{
public:
  ...                                   // ctors, dtor, etc.
  void sendClearMsg(const MsgInfo&info)
  {
    // write "beforesending" info to the log;
   sendClear(info);                    // call base class function; this code will not compile!
   // write "after sending" info to the log;
  }
 ...
};

因为存在所谓的totaltemplate
specialization
的情况:

template<>                                // a total specialization of
class MsgSender<CompanyZ>{               // MsgSender; the same asthe
public:                                   // general template, except
 ...                                     // sendClear is omitted
  void sendSecret(const MsgInfo&info)
  { ... }
};

解决方案:

//1) this->
  void sendClearMsg(const MsgInfo& info)
 {
    // write "before sending" info tothe log;
    this->sendClear(info);               // okay, assumes that sendClear will be inherited
   // write "after sending" info to the log;
  }

//2) using declaration
template<typename Company>
classLoggingMsgSender: public MsgSender<Company> {
public:
 using MsgSender<Company>::sendClear;   // tellcompilers to assume
  ...                                   // that sendClear is in the base class
  voidsendClearMsg(const MsgInfo& info)
  {
   ...
    sendClear(info);                  // okay, assumes that
   ...                               // sendClear will be inherited
  }
  ...
};

//3) explicitly specify:
破坏“virtualbinding”
  void sendClearMsg(const MsgInfo& info)
 {
    ...
   MsgSender<Company>::sendClear(info);     // okay, assumes that
   ...                                      // sendClear will be
 }                                          //inherited

上述三种方式不过是将早期诊断延迟至晚期(具现化时),如果在baseclass中未能将sendClear实现,编译仍将出错。

Thingsto Remember

Inderived class templates, refer to names in base class templates via a"this->" prefix,
via using declarations, or via anexplicit base class qualification.

条款44Factorparameter-independent
code out of templates

我们使用template很大程度上是因为它可以节省时间和避免重复,然而,template也可能会导致codebloat

template<typenameT,           //template for n x n matrices of
        std::size_t n>        //objects of type T; see below for info
class SquareMatrix{           // onthe size_t parameter
public:
  ...
  voidinvert();             // invert the matrix in place
};

SquareMatrix<double,5> sm1;
...
sm1.invert();                 // call SquareMatrix<double, 5>::invert

SquareMatrix<double,10> sm2;
...
sm2.invert();                 // call SquareMatrix<double, 10>::invert

如果将non-typeparameter抽离,可能变成这样:

template<typenameT>                  // size-independent base class for
class SquareMatrixBase{              // square matrices
protected:
  ...
  voidinvert(std::size_t matrixSize); // invert matrix of the given size
 ...
};

template<typenameT, std::size_t n>
class SquareMatrix: privateSquareMatrixBase<T> {
private:
  usingSquareMatrixBase<T>::invert;   // avoid hiding baseversion of invert; see Item 33
public:
  ...
 void invert() { this->invert(n); }   // make inline callto base class
};                                    // version of invert; see below for why "this->" is here

Thingsto Remember

1)Templates generate multiple classes and multiple functions, so anytemplate code not dependent
on a template parameter causes bloat.

2)Bloat due to non-type template parameters can often be eliminated byreplacing template
parameters with function parameters or class datamembers.

3)Bloat due to type parameters can be reduced by sharingimplementations for instantiation
types with identical binaryrepresentations.

Chapter7. Templates and Generic Programming

Item43 - 44

条款43Knowhow
to access names in templatized base classes

下面的code是无法通过编译的:

template<typenameCompany>
class LoggingMsgSender: public MsgSender<Company>{
public:
  ...                                   // ctors, dtor, etc.
  void sendClearMsg(const MsgInfo&info)
  {
    // write "beforesending" info to the log;
   sendClear(info);                    // call base class function; this code will not compile!
   // write "after sending" info to the log;
  }
 ...
};

因为存在所谓的totaltemplate
specialization
的情况:

template<>                                // a total specialization of
class MsgSender<CompanyZ>{               // MsgSender; the same asthe
public:                                   // general template, except
 ...                                     // sendClear is omitted
  void sendSecret(const MsgInfo&info)
  { ... }
};

解决方案:

//1) this->
  void sendClearMsg(const MsgInfo& info)
 {
    // write "before sending" info tothe log;
    this->sendClear(info);               // okay, assumes that sendClear will be inherited
   // write "after sending" info to the log;
  }

//2) using declaration
template<typename Company>
classLoggingMsgSender: public MsgSender<Company> {
public:
 using MsgSender<Company>::sendClear;   // tellcompilers to assume
  ...                                   // that sendClear is in the base class
  voidsendClearMsg(const MsgInfo& info)
  {
   ...
    sendClear(info);                  // okay, assumes that
   ...                               // sendClear will be inherited
  }
  ...
};

//3) explicitly specify:
破坏“virtualbinding”
  void sendClearMsg(const MsgInfo& info)
 {
    ...
   MsgSender<Company>::sendClear(info);     // okay, assumes that
   ...                                      // sendClear will be
 }                                          //inherited

上述三种方式不过是将早期诊断延迟至晚期(具现化时),如果在baseclass中未能将sendClear实现,编译仍将出错。

Thingsto Remember

Inderived class templates, refer to names in base class templates via a"this->" prefix,
via using declarations, or via anexplicit base class qualification.

条款44Factorparameter-independent
code out of templates

我们使用template很大程度上是因为它可以节省时间和避免重复,然而,template也可能会导致codebloat

template<typenameT,           //template for n x n matrices of
        std::size_t n>        //objects of type T; see below for info
class SquareMatrix{           // onthe size_t parameter
public:
  ...
  voidinvert();             // invert the matrix in place
};

SquareMatrix<double,5> sm1;
...
sm1.invert();                 // call SquareMatrix<double, 5>::invert

SquareMatrix<double,10> sm2;
...
sm2.invert();                 // call SquareMatrix<double, 10>::invert

如果将non-typeparameter抽离,可能变成这样:

template<typenameT>                  // size-independent base class for
class SquareMatrixBase{              // square matrices
protected:
  ...
  voidinvert(std::size_t matrixSize); // invert matrix of the given size
 ...
};

template<typenameT, std::size_t n>
class SquareMatrix: privateSquareMatrixBase<T> {
private:
  usingSquareMatrixBase<T>::invert;   // avoid hiding baseversion of invert; see Item 33
public:
  ...
 void invert() { this->invert(n); }   // make inline callto base class
};                                    // version of invert; see below for why "this->" is here

Thingsto Remember

1)Templates generate multiple classes and multiple functions, so anytemplate code not dependent
on a template parameter causes bloat.

2)Bloat due to non-type template parameters can often be eliminated byreplacing template
parameters with function parameters or class datamembers.

3)Bloat due to type parameters can be reduced by sharingimplementations for instantiation
types with identical binaryrepresentations.

Chapter7. Templates and Generic Programming

templates的一个最重要的作用在于让很多原来在运行期的工作提前到编译期间完成。

Item41templatesclasses一样都是支持interfacespolymorphics的,而且tempaltes支持implicitinterfacescompile-timepolymorphics

Item42typename可以用于标识nesteddependent
type name
,但在baseclass
lists
memberinitailization
list
中不可用作baseclass
identifier

Item43:模板化基类存在totaltemplate
specialization
情况,有可能导致baseclasses中的成员函数无法具现,只能通过this->usingdeclaration或者explicitcalling解决。

Item44:将与参数无关代码抽离templates可在一定程序上解决代码膨胀问题。

Item45:用memberfunction
templates
实现的constructorsassignmentoperations等可接受所有兼容类型,需要注意的是在实现兼容的同时不要忘记inheritance和其它类型间的约束。

Item46non-memberfunctions在混合运算的必要我们在Item24中已经领略,对于模板化的混合运算,不仅仅需要non-memberfunctions,还需要可以implicitconversionnon-memberfunctionsinlinefriend
non-member functions
可以。

Item47traitsclasses在编译期类型信息的获取和函数重载使得本来在运行时完成的工作提前至编译期完成,甚至是运行时的错误也提前在编译时被发现。

Item48TMP是一种先进的理念和方法,Scott甚至称之为一种language,可见一斑。

Item45 - 48

条款45Usemember
function templates to accept "all compatible types"

普通指针与智能指针的区别在于:

1)普通指针在资源管理上不及智能指针安全;

2)普通指针的实现是物理层面的,而智能指针的实现则是逻辑层面的;

3)普通指针支持implicitconversions,智能指针并不直接支持implicitconversions

既然智能指针本质上是objects,如果要使用模板类智能指针,并使其支持implicitconversions,就需要为其实现构造模板:

template<typenameT>
class SmartPtr {
public:
  template<typenameU>                      // member template
  SmartPtr(const SmartPtr<U>&other);        // for a"generalized
 ...                                       // copy constructor"
};

为了使之具有继承特性,可以进行如下修改:

template<typenameT>
class SmartPtr {
public:
  template<typenameU>
  SmartPtr(const SmartPtr<U>& other)        // initialize this held ptr
  : heldPtr(other.get()) { ...}            // with other's held ptr
  T* get() const { return heldPtr;}
  ...

private:                                    // built-in pointer held
  T*heldPtr;                               // by the SmartPtr
};

template<classT> class shared_ptr {
public:
  template<classY>                                    // construct from
    explicit shared_ptr(Y *p);                        // any compatible

  template<classY>                                    // weak_ptr, or
    explicit shared_ptr(weak_ptr<Y>const& r);         // auto_ptr

  template<classY>
    explicit shared_ptr(auto_ptr<Y>&r);

  shared_ptr(shared_ptrconst& r);                     // copy constructor

  template<classY>          //generalized
    shared_ptr(shared_ptr<Y>const& r);                // copy constructor

  shared_ptr&operator=(shared_ptr const& r);          // copy assignment

  template<classY>                                    // shared_ptr or
    shared_ptr&operator=(auto_ptr<Y>& r);             // auto_ptr

  template<classY>          //generalized
    shared_ptr&operator=(shared_ptr<Y> const& r);     // copy assignment
  ...
};

1)Use member function templates to generate functions that accept allcompatible types.Things
to Remember

2)If you declare member templates for generalized copy construction orgeneralized assignment,
you'll still need to declare the normal copyconstructor and copy assignment operator, too.

条款46Definenon-member
functions inside templates when type conversions aredesired

Item24中,将CRationaloperator*实现为non-memberfunction,现在来看其模板化形式:

template<typenameT>
class Rational {
public:
  Rational(const T&numerator = 0,       // see Item 20 forwhy params
          const T& denominator = 1);    // are now passed byreference
  const T numerator() const;            // see Item 28 for why return
  const T denominator()const;           //values are still passed by value,
 ...                                   // Item 3 for why they're const
};

template<typenameT>
const Rational<T> operator*(const Rational<T>&lhs,
                           const Rational<T>& rhs)
{ ... }

Rational<int>oneHalf(1, 2);            // this example is from Item24,
                                        // except Rational is now a template
Rational<int> result =oneHalf * 2;      // error! won't compile

如果将operator*声明为Rational<T>friendfunction,这样在编译器在调用operator*时将使用implicitconversions2转换为Rational<int>template实参推导将oneHalf2分开考虑:oneHalf类型为Rational<int>,所以Tintoperator*的第二个参数类型被声明为Rational<T>,但传递的2int,由于template实参推导不考虑implicitconversions,所以2不会在转换为Rational<int>后再推导T类型。

template<typenameT>
class Rational {
public:
 ...
friend                                           // declare operator*
  const Rational operator*(constRational& lhs,   // function(see
                          const Rational& rhs)   // below for details)
 { return doMultiply(lhs, rhs); }               // call helper
};

template<typenameT> class Rational;             // declare Rational template
template<typenameT>                                     // declare
const Rational<T> doMultiply(const Rational<T>&lhs,      //helper
                            const Rational<T>& rhs);     //template

template<typenameT>                                     // define
const Rational<T> doMultiply(const Rational<T>&lhs,      //helper
                            const Rational<T>& rhs)      //template in
{                                                        // header file,
  return Rational<T>(lhs.numerator() *rhs.numerator(),   // if necessary
                    lhs.denominator() * rhs.denominator());
}

ThingstoRemember以前使用friend只是为了使一个function或者class可以访问其它类中的non-public部分,而此处的friend则是为了支持所有实参的implicitconversions

Whenwriting a class template that offers functions related to thetemplate that support implicit
type conversions on all parameters,define those functions as friends inside the class template.

条款47Usetraits
classes for information about types

不知道traitsclasses会不会成为新的C++0x标准的一部分,即使不会,我相信traitsclasses的理念也必将深入人心。

先看一例,STL中有一个toolstemplate名为advance,用以将某iterator移动给定距离:

template<typenameIterT, typename DistT>       // moveiter d units forward;
void advance(IterT& iter, DistTd);           // if d < 0, move iter backward

template<typenameIterT>              // template for information about
structiterator_traits;               // iterator types

template< ... >                      // template params elided
class deque {
public:
  classiterator {
  public:
    typedefrandom_access_iterator_tag iterator_category;
   ...
  }:
  ...
};

template< ... >
class list {
public:
  class iterator {
 public:
    typedef bidirectional_iterator_tagiterator_category;
    ...
  }:
 ...
};

//the iterator_category for type IterT is whatever IterT says it is;
//see Item 42 for info on the use of "typedeftypename"
template<typename IterT>
structiterator_traits {
  typedef typename IterT::iterator_categoryiterator_category;
  ...
};

template<typenameIterT>              // partial template specialization
structiterator_traits<IterT*>        // for built-in pointer types
{
  typedefrandom_access_iterator_tag iterator_category;
  ...
};

iterator的类型在编译期间即可确定,而ifstatement的判定将在运行期间进行,为了能够在编译期间确定iterator_category的值,可以使用overloading:我们知道STL的五种iterators除了randomaccessiterator之外,其它iterators均不能随机访问。如果我们能够在编译时获取类型信息,则可根据类型信息进行讨论。traits的要求之一便是对built-intypesuser-definedtypes的表现要一样好:

template<typenameIterT, typename DistT>             // use this impl for
void doAdvance(IterT& iter, DistTd,                 // random access
              std::random_access_iterator_tag)      // iterators
{
  iter += d;
}

template<typenameIterT, typename DistT>             // use this impl for
void doAdvance(IterT& iter, DistTd,                 // bidirectional
              std::bidirectional_iterator_tag)      // iterators
{
  if (d >= 0) { while (d--) ++iter; }
 else { while (d++) --iter;        }
}

template<typenameIterT, typename DistT>             // use this impl for
void doAdvance(IterT& iter, DistTd,                 // input iterators
              std::input_iterator_tag)
{
  if (d < 0 ) {
    throw std::out_of_range("Negative distance");
  }
 while (d--) ++iter;
}

template<typenameIterT, typename DistT>
void advance(IterT& iter, DistTd)
{
 doAdvance(                                             // call the version
    iter,d,                                             // of doAdvance
   typename                                             // that is
     std::iterator_traits<IterT>::iterator_category()   // appropriate for
 );                                                     // iter'siterator
}                                                        // category

Designand implement a traits class:

1)Identify some information about types you'd like to make available(e.g., for iterators,
their iterator category).

2)Choose a name to identify that information (e.g., iterator_category).

3)Provide a template and set of specializations (e.g., iterator_traits)that contain the
information for the types you want to support.

Usea traits class:

1)Create a set of overloaded "worker" functions or functiontemplates (e.g., doAdvance) that
differ in a traits parameter.Implement each function in accord with the traits information passed.

2)Create a "master" function or function template (e.g.,advance) that calls the workers,
passing information provided by atraits class.

Thingsto Remember

1)Traits classes make information about types available duringcompilation. They're implemented
using templates and templatespecializations.

2)In conjunction with overloading, traits classes make it possible toperform compile-time
if...else tests on types.

条款48Beaware
of template metaprogramming (TMP)

Scott指出:TMPhas
two great strengths:

1)It makes some things easy that would otherwise be hard or impossible(Item 47);

2)TMPs execute during C++ compilation.

在本章中,Scott一直在强调TMPTuring-complete的,他甚至称:TMP'slargely
a functional language

//TMP : declare variables
std::list<int>::iterator iter;

//TMP : perform loops
template<unsigned n>                // general case: the value of
struct Factorial {                  // Factorial<n> is n times thevalue
                                    // of Factorial<n-1>
  enum { value = n *Factorial<n-1>::value };
};

template<>                          // special case: the value of
struct Factorial<0>{               // Factorial<0> is 1
  enum { value = 1 };
};

//TMP : write and call functions
template<typename IterT,typename DistT>
void advance(IterT& iter, DistT d)
{
 ...
}

advance(iter,10);

1)Ensuring dimensional unit correctness.
将运行时错误提前至编译时侦测。TMP的目标:

2)Optimizing matrix operations.

3)Generating custom design pattern implementations.

Thingsto Remember

1)Template metaprogramming can shift work from runtime to compile-time,thus enabling earlier
error detection and higher runtime performance.

2)TMP can be used to generate custom code based on combinations ofpolicy choices, and it
can also be used to avoid generating codeinappropriate for particular types.

Chapter8. Customizing new and delete

正如C#JAVA声称自己的内置“垃圾回收能力”一样,C++则以其高效的“手动回收”著称。Scott提到heap资源在多线程编程时的访问问题,引起了我的兴趣。

Item49 - 52

条款49Understandthe
behavior of the new-handler

operatornew失败时将执行以下操作:

1)调用指定的错误处理函数(new-handler,实际就是一个无参数、无返回值的函数)。

2)抛出异常。

//function to call if operator new can't allocate enough memory
voidoutOfMem()
{
  std::cerr << "Unable to satisfyrequest for memory ";
  std::abort();
}

intmain()
{
  std::set_new_handler(outOfMem);
  int*pBigDataArray = new int[100000000L];
  ...
}

一个设计良好的new-handler应该完成以下事情:

1)Make more memory available.

2)Install a different new-handler.

3)Deinstall the new-handler.

4)Throw an exception of type bad_alloc or some type derived frombad_alloc.

5)Not return, typically by calling abort or exit.

new_handleroperatornew可以在普通类中被实现,为了使该方案可以被复合使用,可以将其模板化:

template<typenameT>             // "mixin-style" base class for
classNewHandlerSupport{         // class-specific set_new_handler
public:                          // support
  static std::new_handlerset_new_handler(std::new_handler p) throw();
  static void *operator new(std::size_t size) throw(std::bad_alloc);
 ...                            // other versions of op. new —
                                 // see Item 52
private:
  static std::new_handlercurrentHandler;
};

template<typenameT>
std::new_handler
NewHandlerSupport<T>::set_new_handler(std::new_handlerp) throw()
{
std::new_handler oldHandler =currentHandler;
currentHandler = p;
return oldHandler;
}

template<typenameT>
void* NewHandlerSupport<T>::operator new(std::size_tsize)
  throw(std::bad_alloc)
{
  NewHandlerHolderh(std::set_new_handler(currentHandler));
  return ::operatornew(size);
}

//this initializes each currentHandler to null
template<typenameT>
std::new_handler NewHandlerSupport<T>::currentHandler= 0;

需要将new_handleroperatornew特别处理的类直接从NewHandlerSupport派生即可:

classWidget: public NewHandlerSupport<Widget> {
 ...                         // as before, but without declarationsfor
};                            // set_new_handler or operator new

有一种“不抛出异常”的operatornew的实现:

classWidget { ... };
Widget *pw1 = new Widget;                // throws bad_alloc if
                                         // allocation fails
if (pw1 == 0) ...                        // this test must fail
Widget *pw2 =new (std::nothrow) Widget;  // returns 0 if allocationfor
                                         // the Widget fails
if (pw2 == 0) ...                        // this test may succeed

需要注意的是,上述代码只能保证pw2在执行Widget*pw2
=new (std::nothrow) Widget
时不抛出异常,却不能保证pw2中的operatornew操作不抛出异常。

Thingsto Remember

1)set_new_handler allows you to specify a function to be called whenmemory allocation requests
cannot be satisfied.

2)Nothrow new is of limited utility, because it applies only to memoryallocation; subsequent
constructor calls may still throw exceptions.

条款50Understandwhen
it makes sense to replace new and delete

重写newdelete8个理由:

1)To detect usage errors.

2)To improve efficiency.

3)To collect usage statistics.

4)To increase the speed of allocation and deallocation.

5)To reduce the space overhead of default memory management.

6)To compensate for suboptimal alignment in the default allocator.

7)To cluster related objects near one another.

8)To obtain unconventional behavior.

Thingsto Remember

Thereare many valid reasons for writing custom versions of new and delete,including improving
performance, debugging heap usage errors, andcollecting heap usage information.

条款51Adhereto
convention when writing new and delete

一段operatornew的伪代码:

void* operator new(std::size_t size)throw(std::bad_alloc)
{                                     // your operator new might
  using namespacestd;                // take additional params
  if (size == 0){                    // handle 0-byte requests
    size =1;                         // by treating them as
  }                                   // 1-byte requests

  while(true) {
   attempt to allocate size bytes;
   if (the allocation was successful)
      return (a pointer to the memory);

    //allocation was unsuccessful; find out what the
   // current new-handling function is (see below)
   new_handler globalHandler = set_new_handler(0);
   set_new_handler(globalHandler);

    if(globalHandler) (*globalHandler)();
    else throwstd::bad_alloc();
  }
}
对于baseclassoperatornew操作,在其derivedclass不重写的情况下,可以这样写baseclassoperatornew

void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
 if (size != sizeof(Base))              // if size is "wrong,"
     return::operator new(size);        // have standard operator
                                         // new handle the request
 ...                                    // otherwise handle
                                         // the request here
}

item39中提过:独立对象的size不会为0operatordelete的唯一要求就是“保证deleteNULL
pointer
永远安全”:

voidoperator delete(void *rawMemory) throw()
{
  if (rawMemory== 0) return;           // do nothing if the null
                                        // pointer is being deleted
  deallocate the memory pointedto by rawMemory;
}

voidBase::operator delete(void *rawMemory, std::size_t size) throw()
{
 if (rawMemory == 0) return;          // check for null pointer
  if (size != sizeof(Base)){           // ifsize is "wrong,"
     ::operatordelete(rawMemory);      // have standardoperator
    return;                           // delete handle the request
  }

  deallocatethe memory pointed to by rawMemory;
  return;
}

Thingsto Remember

1)operator new should contain an infinite loop trying to allocatememory, should call the
new-handler if it can't satisfy a memoryrequest, and should handle requests for zero bytes. Class-specificversions should handle requests for larger blocks than expected.

2)operator delete should do nothing if passed a pointer that is null.Class-specific versions
should handle blocks that are larger thanexpected.

条款52Writeplacement
delete if you write placement new

operatornewoperatordelete要成对使用,不仅仅是指形式上要搭配,在本质上newdelete也要是对应的,这句话要从signatures来理解:

//normal forms of new & delete
void* operator new(std::size_t)throw(std::bad_alloc);
void operator delete(void *rawMemory)throw();  // normal signature at global scope

//placement version of new & delete
void* operatornew(std::size_t, void *pMemory) throw();   // "placementnew"
void operator delete(void *, std::ostream&) throw();

而且在声明他们时,注意不应该掩盖了其他形式的new&
delete

Thingsto Remember

1)When you write a placement version of operator new, be sure to writethe corresponding
placement version of operator delete. If you don't,your program may experience subtle, intermittent memory leaks.

2)When you declare placement versions of new and delete, be sure not tounintentionally hide
the normal versions of those functions.

不知道Singleton算不算用的最多的,平时用的时候,往往都是直接敲下面一段:

classCSingleton
{
public:
   static void Initial(void) { if( NULL == m_pInst ) m_pInst = newCSingleton; }
   static void Release(void) { delete m_pInst; m_pInst = NULL; }
   static CSingleton* GetInst(void) { return m_pInst; }

private:
   CSingleton() {}
    ~CSingleton() {}
   Singleton(const Singleton&);
    Singleton&operator=(const Singleton &);
    staticCSingleton* m_pInst;
};

不是不想改,就是懒,敲多了已经不觉得这么写多浪费时间了,按大家的说法,这样写至少有这么几个缺点:

1.必须在程序结束前手动释放,这不仅是RP问题,如果你借了内存不主动还,说明你RP差,但被别人搞丢了(宕机)导致你还不上,说明别人RP差?所以,这还是个问题;

2.线程同步问题,如果Singleton实例跨线程使用,上例不安全,在InitialRelease时加锁可以解决;

3.最大的问题:不能重用。

有人给出了下面的实现:

template<classT>
class CSingleton
{
public:
   static T& instance()
    {
       static T _instance;
       return _instance;
    }

private:
   CSingleton() {}
    ~CSingleton() {}
   CSingleton(const CSingleton<T>&);
   CSingleton<T>& operator=(const CSingleton<T>&);
};

除了线程同步问题之外,其他都解决了。

至于线程问题,我们可以把多线程问题转化成单线程问题:在进入main函数前初始化。Boost提供了一个这样的Singleton

template<typename T>
struct singleton_default
{
 public:
    typedef T object_type;
   static object_type & instance()
    {
     static object_type obj;
     create_object.do_nothing();       //
需要create_object的初始化
     return obj;
    }

  private:
   struct object_creator
    {
     object_creator() { singleton_default<T>::instance(); }  //
创建实例
     inline void do_nothing() const { }
    };
   static object_creator create_object;
   singleton_default();
};

template<typename T>
typenamesingleton_default<T>::object_creator
singleton_default<T>::create_object;

由于create_object将在被调用(staticobject_type
&instance()
)之前进行初始化,因此singleton_default对象的初始化被放到了main之前。

非常巧妙的一个设计。

这样一来,我们可以通过下面这样一个简单的宏完成Singleton的使用:

#defineGetInst(CLASS_NAME) \
   singleton_default<CLASS_NAME>::instance()

在后面需要使用到某个类的Singleton时,直接使用该宏即可。

Kevin之前提到关于静态变量的初始化顺序问题,今天查了一下ISOC++3.6.2Initializationof
non-local objects
)有段话:

//– File 1 –
#include "a.h"
#include "b.h"
Bb;
A::A(){
b.Use();
}
// – File 2 –
#include"a.h"
A a;
// – File 3 –
#include"a.h"
#include "b.h"
extern A a;
externB b;
int main() {
a.Use();
b.Use();
}

Itis implementation-defined whether either a or b is initialized beforemain is entered or
whether the initializations are delayed until a isfirst used in main. In particular, if a is initialized before main isentered, it is not guaranteed that b will be initialized before it isused by the initialization of a, that is, before A::A is called. If,however,
a is initialized at some point after the first statement ofmain, b will be initialized prior to its use in A::A.

大致意思是说如果在进入main之前初始化,ab的初始化顺序将不能确定,如果希望初始化顺序可以确定的话,就应该像上面的singleton一样在逻辑上为他们人为建立依赖。

以前曾经讨论过Singleton的实现,这次在对照ACEBoost代码的时候,又重新审视了一下二者对Singleton不同的实现。其间的差别也体现了不同的编程哲学:ACE的实现更加偏重多线程中的安全和效率问题;Boost的实现则偏重于使用语言自身的特性满足Singleton模式的基本需求。

oACE的实现

DouglasC. SchmidtDouble-CheckedLocking:
An Optimization Pattern for Efficiently Initializing andAccessing Thread-safe Objects
一文中对double-checklock(一般译为双检锁)进行了详细的阐述。

ACESingleton使用Adapter模式实现对其他类的适配,使之具有全局唯一的实例。由于C++标准并非明确指定全局静态对象的初始化顺序,ACE使用double-checklock保证线程安全,并使之不受全局静态对象初始化顺序的影响,同时也避免了全局静态实现方式的初始化后不使用的开销。

如果你能够准确的区分以下三种实现的弊端和隐患,对double-checklock也就有了足够的了解。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

//-------------------------------------------

classSingleton

{

public:

staticSingleton
*instance(void)

{

//Constructor of guard acquires

//lock_ automatically.

Guard&lt;Mutex&gt;guard
(lock_);

//Only one thread in the

//critical section at a time.

if(instance_==0)

instance_=newSingleton;

returninstance_;

//Destructor of guard releases

//lock_ automatically.

}

private:

staticMutex
lock_
;

staticSingleton
*instance_;

};

 

//---------------------------------------------

staticSingleton
*instance(void)

{

if(instance_==0){

Guard&lt;Mutex&gt;guard
(lock_);

//Only come here if instance_

//hasn’t been initialized yet.

instance_=newSingleton;

}

returninstance_;

}

 

//---------------------------------------------

classSingleton

{

public:

staticSingleton
*instance(void)

{

//First check

if(instance_==0)

{

//Ensure serialization (guard

//constructor acquires lock_).

Guard&lt;Mutex&gt;guard
(lock_);

//Double check.

if(instance_==0)

instance_=newSingleton;

}

returninstance_;

//guard destructor releases lock_.

}

private:

staticMutex
lock_
;

staticSingleton
*instance_;

};

更多详情,见Schmidt老师的原文和ACE_Singleton实现。

oBoost的实现

BoostSingleton也是线程安全的,而且没有使用锁机制。当然,BoostSingleton有以下限制(遵从这些限制,可以提高效率):

oThe classes below support usage of singletons, including use inprogram startup/shutdown code, AS LONG AS there is only one threadrunning
before main() begins, and only one thread running aftermain() exits.

oThis class is also limited in that it can only provide singletonusage for classes with default constructors.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

//T must be: no-throw default constructible and
no-throwdestructible

template&lt;typenameT&gt;

structsingleton_default

{

private:

structobject_creator

{

//This constructor does nothing more than ensure
that instance()

// is called before main() begins, thus creating
the static

// T object before multithreading race issues
can come up.

object_creator(){singleton_default&lt;T&gt;::instance();}

inlinevoiddo_nothing()const{}

};

staticobject_creator
create_object
;

 

singleton_default();

 

public:

typedefT
object_type
;

 

//If, at any point (in user code),singleton_default&lt;T&gt;::instance()

// is called, then the following function is instantiated.

staticobject_type
&amp;instance()

{

//This is the object that we return a reference
to.

//It is guaranteed to be created before main()
begins because of

// the next line.

staticobject_type
obj
;

 

//The following line does nothing else than force
the instantiation

// of singleton_default&lt;T&gt;::create_object,
whoseconstructor is

// called before main() begins.

create_object.do_nothing();

 

returnobj;

}

};

template&lt;typenameT&gt;

typenamesingleton_default&lt;T&gt;::object_creator

singleton_default&lt;T&gt;::create_object;

对于多数Singleton使用,Boost提供的版本完全能够满足需求。为了效率,我们有必要对其使用作出一定的限制。

而在多线程编程中,则有必要使用double-checklock降低频繁加锁带来的开销。

抱歉!评论已关闭.