Effective C++读书笔记
目录
条款01:View C++ as a federation of languages.
条款02:Prefer consts,enums,and inlines to #define.
条款03:Use const whenever possible.
条款04:Make sure that objects are initialized before they’re used.
条款05:Know what functions C++ silently writes and calls.
条款06:Explicitly disallow the use of compiler-generated functions you do not want.
条款07:Declare destructors virtual in polymorphic base classes.
条款08:Prevent exceptions from leaving destructors.
条款09:Never call virtual functions during construction or destruction.
条款10:Have assignment operators return a reference to *this.
条款11:Handle assignment to self in operator=.
条款12:Copy all parts of object.
条款13:Use objects to manage resources.
条款14:Think carefully about copying behavior in resource-managing classes.
条款15:Provide access to raw resources in resource-managing classes.
条款16:Use the same form in corresponding of new and delete.
条款17:Store newed objects in smart pointers in standalone statements.
条款18:Make interfaces easy to use correctly and hard to use incorrectly.
条款19:Treat class design as type design.
条款20:Prefer pass-by-reference-to-const to pass-by-value.
条款21:Don’t try to return a reference when you must return an object.
条款22:Declare data members private.
条款23:Prefer non-member non-friend functions to member functions.
条款24:Declare non-member functions when type conversions should apply to all parameters.
条款25:Consider support for a non-throwing swap.
条款26:Postpone variable definitions as long as possible.
条款28:Avoid returning “handles” to object internals.
条款29:Strive for exception-safe code.
条款30:Understand the ins and outs of inlining.
条款31:Minimize compilation dependencies between files.
条款32:Make sure public inheritance models “is-a”.
条款33:Avoid hiding inherited names.
条款34:Differentiate between inheritance of interface and inheritance of implementation.
条款35:Consider alternatives to virtual functions.
条款36:Never redefine an inherited non-virtual function.
条款37:Never redefine a function’s inherited default parameter value.
条款38:Model “has-a” or “is-implemented-in-terms-of” through composition.
条款39:Use private inheritance judiciously.
条款40:Use multiple inheritance judiciously.
条款41:Understand implicit interfaces and compile-time polymorphism.
条款42:Understand the two meanings of typename.
条款43:Know how to access names in templatized base classes.
条款44:Factor parameter-independent code out of templates.
条款45:Use member function templates to accept “all compatible types”.
条款46:Define non-member functions inside templates when type conversions are desired.
条款47:Use traits classes for information about types.
条款48:Be aware of template metaprogramming.
条款49:Understand the behavior of the new-handler.
条款50:Understand when it makes sense to replace new and delete.
条款51:Adhere to convention when writing new and delete.
条款52:Write placement delete if you write placement new.
谢凌云
2011年12月8日
条款01:View C++ as a federation of languages.
条款02:Prefer consts,enums,and inlines to #define.
(1) class GamePlayer{
private:
static const int NumTurns = 5;//常量声明式
static const int Numbers;
int scores[NumTurns];
};
const int Numbers = 0;
如果你要取某个class专属常量的地址,或编译器坚持要看到一个定义式,你就要在其实现文件中添加如下语句:const int GamePlayer::NumTurns;因为class常量已在声明时获得初值,因此定义时不可以在设初值。
也可以用这种方法:在类体中声明而不赋初值,然后在头文件(类体外)或实现文件再赋初值,注意不用在写关键字static,static函数也是。
如果有些编译器不支持上面几种方法,但又在类体内需要用到,例如上例的NumTurns,可以在类体中用如下定义:enum {NumTurns = 5;}; 这有点像#define,因为不可以取enum的地址。
(2) 如果宏看起来像一个函数,可以用inline模板函数代替。
例如:#define MAX(a,b) f((a) > (b) ? (a) : (b))
改为
template<typename T>
inline void Max(const T& a, const T& b)
{
f(a > b ? a : b);
}
条款03:Use const whenever possible.
(1) 如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量。
(2) const std::TYPE<type>::iterator iter;iter的作用像个T* const;std::TYPE<type>::const_iterator cIter; cIter的作用像个const T*。
(3) 返回值是const的函数:const type Fun();,const成员函数:type Fun() const;这种函数不能改变成员变量的值。
(4) 如果两个成员函数只是常量性不同,可以被重载。例如void Fun();和void Fun() const
(5) 参数传递时,内置类型的passed by value比passed by reference-to-const效率更高
条款04:Make sure that objects are initialized before they’re used.
(1) 为内置型对象进行手工初始化,因为C++不保证初始化它们
(2) 构造函数最好使用成员初始列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初始化顺序为定义时的顺序,与初始化列顺序无关。
(3) 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。看下面的例子。
FileSystem.h文件
class FileSystem{
public:
……
std::size_t numDisks() const;
};
extern FileSystem tfs;
Directory.h文件,用到了tfs。
class Directory{
public:
Directory(params){
std::size_t disks = tfs.numDisks();
}
};
因为要用到tfs,所以必须先确定tfs先于Directory初始化,但C++对于“定义于不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义,利用Singleton模式来解决这个问题。
FileSystem.h文件
class FileSystem{
public:
……
std::size_t numDisks() const;
};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
Directory.h文件,用到了tfs。
class Directory{
public:
Directory(params){
std::size_t disks = tfs().numDisks();
}
};
条款05:Know what functions C++ silently writes and calls.
(1) 如果你声明了一个构造函数,编译器就不会生成default构造函数。但如果没有定义default copy构造函数和copy assignment操作符,编译器还是会为其生成一个default copy构造函数以及copy assignment操作符。
(2) 如果你的类体中有reference和const成员,编译器拒绝为其生成operator =。如果某个base classes将其copy assignment操作符声明为private,编译器也将拒绝为其derived classes生成一个copy assignment操作符。
条款06:Explicitly disallow the use of compiler-generated functions you do not want.
为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种方法。Boost库提供了名为noncopyable的class。
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class YourClass : private Uncopyable{
…
};
条款07:Declare destructors virtual in polymorphic base classes.
(1) C++明白指出,当derived class 对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的derived成分没有被销毁。
(2) polymorphic(带多肽性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数。
条款08:Prevent exceptions from leaving destructors.
(1) 析构函数绝对不要吐出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序(abort)。
(2) 如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个普通函数而非在析构函数中执行该操作。
条款09:Never call virtual functions during construction or destruction.
base classes构造函数中调用的virtual函数的版本是base classes中的那个版本,也就是在构造函数中没有多态性质。通俗一点讲就是,在base class构造期间,virtual函数不是virtual函数。因此,在构造和析构期间 不要调用virtual函数,这类调用从不下降至derived class。
条款10:Have assignment operators return a reference to *this.
条款11:Handle assignment to self in operator=.
有两个版本:
CLASS& CLASS::operator=(const CLASS& rhs)
{
if(this == &rhs) return *this;
delete ptr;//ptr为类体的一个new出来的指针
ptr = new pointer();
return *this;
}
CLASS& CLASS::operator=(const CLASS& rhs)
{
pointer *pOrig = ptr;
ptr = new pointer();
delete pOrig;
return *this;
}
一种是进行自我检查,另一种是在复制ptr所指的东西之前别删除ptr
条款12:Copy all parts of object.
(1) Copying 函数应该确保复制“对象内的所有成员变量”以及base class的初始化(即调用base class的copying函数)
(2) 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
条款13:Use objects to manage resources.
(1) auto_ptrs有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成NULL,而复制所得的指针将取得资源的唯一拥有权。tr1::shared_ptr则不会
(2) auto_ptrs和tr1::shared_ptr都不适用于数组
条款14:Think carefully about copying behavior in resource-managing classes.
当我们想为某个对象执行的不是删除指针的操作的时候,可以用tr1::shared_ptr,tr1::shared_ptr有两个参数,第二个参数就是函数指针
条款15:Provide access to raw resources in resource-managing classes.
auto_ptr和tr1::shared_ptr都提供一个get成员函数,用来执行显示转换,也就是它会返回智能指针内部的原始指针的复件。
条款16:Use the same form in corresponding of new and delete.
条款17:Store newed objects in smart pointers in standalone statements.
C++核算被传递的各个实参的顺序不确定,为了确保不出现下面类似的情况:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priotity());
因为不确定new Widget、shared_ptr构造函数、priority的顺序,有可能先执行new再执行priority,执行priority是抛出异常,导致资源没有被释放。所以应该在单独语句内以智能指针存储newed所得对象。
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
条款18:Make interfaces easy to use correctly and hard to use incorrectly.
限制类型内什么事可做,什么事不能做。除非有好理由,否则应该尽量令你的types的行为与内置types一致。
条款19:Treat class design as type design.
(1) 新type的对象应该如何被创建和销毁
(2) 对象的初始化和对象的赋值该有什么样的差别
(3) 新type的对象如果被passed by value,意味着什么
(4) 什么是新type的“合法值”
(5) 你的新type需要配合某个继承图系吗
(6) 你的新type需要什么样的转换
(7) 什么样的操作符和函数对此新type而言是合理的
(8) 什么样的标准函数应该驳回
(9) 谁该取用新type的成员
(10) 什么是新type的“未声明接口”
(11) 你的新type有多么一般化
(12) 你真的需要一个新type吗
条款20:Prefer pass-by-reference-to-const to pass-by-value.
pass-by-reference-to-const可以阻止对象切割。
条款21:Don’t try to return a reference when you must return an object.
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。
一个“必须返回新对象”的函数的正确写法是:就让那个函数返回一个新对象吧。
条款22:Declare data members private.
条款23:Prefer non-member non-friend functions to member functions.
类中的函数是绝大部分客户需要用到的,其他的特殊功能的函数可以放在跟类同一个命名空间,函数参数一般为类对象,即要调用类的public函数。
条款24:Declare non-member functions when type conversions should apply to all parameters.
只有当参数被列于参数列内,这个参数才是隐式类型转换的合格的参与者。
条款25:Consider support for a non-throwing swap.
(1) C++只允许对class templates偏特化,在function templates身上偏特化是行不通的。
(2) 客户可以全特化std内的templates,但不可以添加新的templates(或classes或functions或其他任何东西)到std里头。
(3) 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes(非templates),也要特化std::swap。
(4) 调用swap是应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰符”
条款26:Postpone variable definitions as long as possible.
条款27:Minimize casting.
(1) const_cast通常被用来将对象的常量性转除
(2) dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某个对象是否归属继承体系中的某个类型。
(3) reinterpret_cast意图执行低级转型,实际动作可能取决于编译器,这也就表示它不可移植
(4) static_cast用来强迫隐式转换。
条款28:Avoid returning “handles” to object internals.
条款29:Strive for exception-safe code.
copy and swap.
条款30:Understand the ins and outs of inlining.
条款31:Minimize compilation dependencies between files.
(1) 如果使用object references或object pointers可以完成任务,就不要使用objects
(2) 如果能够,尽量以class声明式替换class定义式。当你声明一个函数而它用到某个class时,并不需要该class的定义,即返回值或参数为某class。但当用户调用此函数时,就要出现某class的定义式。
(3) 为声明式和定义式提供不同的头文件。
看下面这个例子:
定义式头文件Person.h
#include <string>
class Personfwd{
public:
Personfwd(const std::string& name);
std::string name() const;
private:
std::string theName;
};
声明式头文件Personfwd.h (声明式文件中不包含任何其他成员变量,只有一个指针指向定义式的类)
#icnude <string>
class Person{
public:
Person(const std::string& name);
std::string name() const;
private:
std::tr1::shared_ptr<Personfwd> pImpl;
};
Personfwd.cpp
#include “Person.h”
#include “Personfwd.h”
Person::Person(const std::string& name)
:pImpl(new PersonImpl(name))
{}
std::string Person::name() const
{
return pImpl->name();
}
这样,在使用的时候,就可以include声明式头文件Personfwd.h。
条款32:Make sure public inheritance models “is-a”.
“public继承”意味着is-a。适用于base classes身上的每一件事情也使用于derived classes身上。
条款33:Avoid hiding inherited names.
在derived classes不应该重新定义base classes中的public-non-virtual函数,如果重新定义了,将会覆盖base classes中的函数,想要“重现”那个函数,可以用using声明式,例如using Base::function;语句。
条款34:Differentiate between inheritance of interface and inheritance of implementation.
(1) 成员函数的接口总是会被继承
(2) 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
(3) 声明impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现
(4) 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。
条款35:Consider alternatives to virtual functions.
(1) typedef int (*Func) (const int&);Func是参数为int的const引用,返回值为int的函数指针。typedef std::tr1::function<int (const int&)> Func;tr1::function意味着该函数行为像一般函数指针,但是这个定义比上面那个定义的应用更广泛,这个可调用物的参数可被隐式转换为const int&,而其返回值类型可被隐式转换为int。
(2) 使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式。也就是让non-virtual调用private virtual函数。
(3) 将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式
条款36:Never redefine an inherited non-virtual function.
class B{
public:
void fun1();
virtual fun2();
};
class D:public B{…};
D x;
B *pB = &x;
pB->fun1();//调用B
pB->fun2();//调用D
D *pD = &x;
pD->fun1();//调用D
pD->fun2();//调用D
non-virtual是静态绑定的,即与指针相关。
条款37:Never redefine a function’s inherited default parameter value.
所有的函数的缺省参数值都是静态绑定的。
条款38:Model “has-a” or “is-implemented-in-terms-of” through composition.
条款39:Use private inheritance judiciously.
private意味着根据某物实现出。
条款40:Use multiple inheritance judiciously.
(1) 多重继承中,如果继承的函数中有同名的,与C++用来解析重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用之言是最佳匹配,找到最佳匹配函数后才检验其可取用性。
(2) 如果一个类是virtual base class,请尽量不要有成员变量
条款41:Understand implicit interfaces and compile-time polymorphism.
条款42:Understand the two meanings of typename.
(1) 声明template参数时,前缀关键字class和typename可互换
(2) 请使用关键字typename标识嵌套从属类型名;但不得在base class lists或member initialization list内以它作为base class修饰符
条款43:Know how to access names in templatized base classes.
处理模板化基类的名称,有三种方法:(1)在base class函数调用之前加上“this->”,相当于强制告诉编译器有这个函数。(2)使用using声明式。using TEMPLATE<T>::func;(3)明白指出被调用函数位于base class内,跟this->差不多
条款44:Factor parameter-independent code out of templates.
(1) Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
(2) 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
条款45:Use member function templates to accept “all compatible types”.
(1) 请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数
(2) 如果你声明member templates用于“泛化copy函数”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符
条款46:Define non-member functions inside templates when type conversions are desired.
(1) 在一个class template内,template名称可被用来作为“template和其参数”的简略表达式,即rational<T>内我们可以只写rational。
(2) template实参推导过程中从不将隐式类型转换函数纳入考虑
(3) template class内的friend声明式可以指涉某个特定函数
(4) 为了让类型转换可能发生于所有实参身上,我们需要一个non-member函数;为了令这个函数被自动具现化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一方法就是:令它成为一个friend。
条款47:Use traits classes for information about types.
如何使用一个traits class
(1) 建立一组重载函数或函数模板,彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受之traits信息相应和
(2) 建立一个控制函数或函数模板,它调用上述那些“劳工函数”并传递traits class所提供的信息
条款48:Be aware of template metaprogramming.
模板元编程的一个“Hello world”:
template<unsigned n>
struct Factorial{
enum { value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1};
};
要输出10的阶乘就可以这样:std::cout<<Factorial<10>::value;
条款49:Understand the behavior of the new-handler.
读懂下面这个例子:
class Widget{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
stdtic std::new_handler currentHandler;
};
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
class NewHandlerHolder{
public:
explicit NewHandlerHolder(std::new_handler nh)
:handler(nh) {}
~NewHandlerHolder()
{std::set_new_handler(handler);}
private:
std::new_handler handler;
NewHandlerHolder(const NewHanderHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
那么Widget的operator new的实现就如下:
void* operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler))
return ::operator new(size);
}
于是引申出了下面这个模板:
template<typename T>
class NewhandlerSupport{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
stdtic std::new_handler currentHandler;
};
template<typename T>
std::new_handler NewhandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<typename T>
void* NewhandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler))
return ::operator new(size);
}
那么定义class Widget: public NewHandlerSupport<Widget>{ };不用声明set_new_handler和operator new。这样的实现就是class专属之set_new_handler。
条款50:Understand when it makes sense to replace new and delete.
(1) 为了检测运用错误
(2) 为了收集动态分配内存之使用统计信息
(3) 为了增加分配和归还的速度
(4) 为了降低缺省内存管理器带来的空间额外开销
(5) 为了nibu缺省分配器中的非最佳齐位
(6) 为了将相关对象成簇集中
(7) 为了获得非传统的行为
条款51:Adhere to convention when writing new and delete.
条款52:Write placement delete if you write placement new.
(1) 当你写一个placement operator new,请确定也写出对应的placement operator delete
(2) 当你声明placement new和placement delete,请确定不要遮掩了它们的正常版本。解决这个问题可以建立一个base class,内含正常形式的new和delete,然后继承base class,在用using声明式使其可见。