1. 让自己习惯C++
1.1 C++ 是一个多重泛型编程语言(multiparadigm programming),支持:过程形式(procedural),面向对象形式(object-oriented),函数形式(functional),泛型式(generic),元编程(metaprogramming)。
1.2 将C++看做主语言,那么他就四种次语言组成:C,object-oriented C++,Template C++, STL。
请记住:
C++ 高效编程守则视状况而变化,取决于你使用C++的哪一部分。
2.1 用#define定义常量时,由于#define是preprocessor负责的,编译器看不到,所以编译出错时,会很麻烦。尽量以const取代之。
2.2 常量式通常被放在头文件中,以便被不同的源码引入。
2.3 #define无法定义类的常量成员,更不能提供封装,const就可以。类中的static const成员,不仅要在类内初始化,还要在类外定义。
//头文件
class A{
private:
static const int Num = 5;
int scores[Num];
}
//源文件
const int A::Num;
2.4 在2.3中提到,类内的static const成员要在类内初始化,如果编译器不允许类内初始化,可以用enum hack的做法,来变相实现。
enum{ Num = 5 };
2.5 在C++11标准以前,只允许对类的static const integral成员做in-class initialization。在C++11之后,类的所有数据成员均可做类内初始化。另外,enum hack实际上利用enumeration不是类型安全的特点(弊端),这个特点在C++11中通过scoped enumeration来弥补,所以enumerator到整形的隐式转换是不提倡做的。故,在C++11下,需要用enum来替换#define的情况几乎不存在。
2.6当#define定义类似函数的功能时,带来了很多不安全因素,通常用内联函数(模板)代替。
#define MAX(a, b) (a)>(b)?(a):(b)
int a = 5, b = 0;
MAX(++a, b);//a被累加了两次
1)对于单纯常量,最好以const对象或者enums替换掉#defines。
2)对于形似函数的宏(macros),最好用inline函数替换掉#defines。
3.1 在能用const的地方就使用const有利于编译器对对象进行处理,而且可以避免错误的使用。
3.2 作为成员函数,是否有const qualifier区别,可以被重载。
3.3 const成员函数的const的意义,有两大流行概念:bitwise constness(又称physical constness)和logical constness。
3.4 使用mutable可以释放掉non-static成员变量的bitwise constness约束。
3.5 当同时定义一个功能的const版本和nonconst版本时,注意代码重用:nonconst版本调用const版本,不要用反。
public:
const char& operator[](size_t index) const{
/*...*/
return text[index];
char& operator[](size_t index){
const_cast<char&>( static_cast<const A&>(*this)[index] );
};
1)将某些东西声明为const可帮助编译器侦测出错用法,const被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体。
2)编译器强制实施bitwise constness,但我们在编写程序时应该使用logical constness。
3)当const合nonconst成员函数有着实质等价的实现时,令nonconst版本调用const版本可以避免代码重复。
4.1 C++中default initialization和value initialization的区别。
4.2 类数据成员的初始化是发生在constructor body执行之前的(通过initialization list和in-class initializer),构造函数体内是赋值,不是初始化。
4.3 类的数据成员的初始化顺序总是固定的,若initialization list和in-class initializer都没有对某成员执行初始化,那么该成员执行default initialization。
4.4 注意在用new动态申请时new type()和new type的区别。
4.5 若object的lifetime从构造出来开始,一直到程序结束,那么就称之为static对象(未必被static修饰,如全局变量),包括global对象、定义于namespace作用域内的对象,以及在class内、函数内、文件作用域内的被声明为static的对象;他们都存放于静态存储区(或者称作全局区)。在函数内的static对象被称作local static对象(用static修饰),其余均是非local的。C++对于定义于不同编译单元的non-local static对象的初始化次序没有明确规定。当一个non-local static对象的初始化要依赖于另一个编译单元内的non-local static对象时,往往结果是不可预测的。解决办法,将非local static对象,放到函数内,即转为local static。这就是singleton模式的做法。
1)为built-in对象手工初始化,因为C++不保证初始化。
2)构造函数最好使用成员初始化列表(member initialization list),而不要在构造函数体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同。
3)为免除“跨编译单元之初始化次序”问题,请以local static对象代替non-local static对象。
2. 构造,析构,赋值运算
5.1 编译器合成的函数,只有在被需要(被调用)的时候才会被编译器创建出来。
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。补充:在C++11标准下,编译器还会合成move-copy构造函数和move-copy assignment操作符。
6.1 针对将成员函数声明为private并且不予实现的方法。补充:做了测试,发现不适用于非纯虚函数。当非纯虚函数没有定义时,只要执行构造函数,就会出现链接错误。
为了驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明成private并且不予实现。使用像Uncopyable这样的base class也是一种做法。补充:在C++11中可以直接使用=delete限定符来明确驳回。
7.1 由于STL容器的析构函数都不是virtual的,所以我们不应该(不是不能)继承他们。
7.2 析构函数可以声明为纯虚函数,但必须定义。
1)polymorphic(带多态性质的)base classes 应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。
2)Classes的设计目的如果不是为了作为base classes使用,或不是为了具备多态性(polymorphically),就不应该声明virtual析构函数。
1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下他们(不传播)或结束程序。
2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
9.1 间接调用也不可以。
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。
10.1 由于右结合性,x=y=z=15等价于a=(y=(z=15))。
令赋值(assignment)操作符返回一个reference to *this。
11.1 通过this==&val来判断是否是自赋值。
11.2 即使已经处理了自我赋值,operator=往往不具有“异常安全”性。异常安全可以通过精心安排的语句来保证,还可以使用copy and swap技术。C++11中还要考虑move assignment operator 情况。
A& A::operator=(A ai){
swap(ai);
return *this;
1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2)确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
1)Copying函数应该确保复制“对象的所有成员变量”及所有base class成分。
2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
3. 资源管理
13.1 以对象管理资源,即“资源取得时便是初始化时机”(Resource Acquisition Is Initialization;RAII),获得资源即用来初始化(或赋值)管理对象。
13.2 管理对象运用析构函数确保资源被释放。
1)为防止资源泄露,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源。
2)两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。。。 。补充:在C++11中,提供了std::shared_ptr,std::unique_ptr和std::weak_ptr来管理资源。
1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。
1)APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法。
2)对原始资源的访问尅经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。
16.1 尽量不要对数组形式做typedef动作。typedef int IntArra[10]; IntArra *p = new IntArra;应该使用delete[] p。
如果你在new表达式中使用[],必须在相应的delete表达式中使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。
17.1 当将newed对象置入智能指针的同时,还有多条不能确定执行顺的语句,是很危险的。补充:在C++11下面,用make_ptr是最安全的用法。
//危险的用法
foo(shared_ptr<A>(new A), foo2());
//正确的用法
shared_ptr<int> p(new A);
foo(p, foo2());
以独立语句将newed对象存储于(置入)只能指针内。如果不这样做,一旦异常抛出,有可能导致难以察觉的资源泄露。
4. 设计与声明
1)好的借口很容易被正确使用,不容易被吴用。你应该在你的所有的接口中女里达成这些性质。
2)“促进正确使用”的办法包括接口的一致性,以及与内置内的行为兼容。
3)“阻止无用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
4)shared_ptr支持定制型删除器(custom deleter)。这可防范Dll问题,可被用来自动解除互斥锁等等。
1)尽量以pass-byreference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。
2)以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对他们而言,pass-by-value往往比较合适。
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或者返回pointer或者reference指向一个local static对象而有可能同时需要多个这样的对象。
22.1 原因:1)语法的一致性,2)对成员变量的处理有更精确的控制,3)封装。
1)切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
2)protected并不比public更具封装性。
宁可拿non-member、non-friend函数替换掉member函数,这样做可以增加封装性、包裹弹性(packaging flexibility)和技能扩展性。
24.1 这样就成处理string str; "abc"+str; str + "abc"的(隐式转换)问题。
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。
25.1 在pimpl(pointer to implementation)类型的类中,如果采用深拷贝进行初始化、赋值,但在swap时,就该使用浅拷贝。这就需要自己定义swap函数。补充:在C++11中,这更应该定义move constructor和move assignment。
25.2 客户可以全特化std内的templates,但不可以添加新的templates(或者class或functions或其他任何东西)到std里头。
25.3 注意name lookup规则中的argument-dependent lookup。
1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
2)如果你提供一个member swap,也应该提供一个non-member swap用来调用前者。对于classes(而非templates),请特化std::swap。
3)调用swap时应针对std::swap使用using declaration,然后调用swap并且不带任何命名空间修饰符。
4)为”用户定义类型“进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。
5. 实现
26.1 我们不仅应该延后变量的定义,知道非得使用的前一刻为止,甚至应该尝试延后这份定义直到能够给他初值实参为止。
26.2 这样可以避免构造(和析构)非要对象,还能够避免无意义的default构造行为。更深一层:以有意义的initializer来初始化,并附有说明变量的目的。
26.3 下面两种形式,做法A:1构造 + 1析构 + n赋值;做法B:n构造 + n析构。除非1)你知道赋值成本比“构造+析构”成本低,2)你正在处理代码中效率高度敏感(performance-sensitive)的部分,否则你应该使用做法B。
//做法A:定义于循环外
Widget w;
for(int i = 0; i < n; ++i){
w = 取决于i的某个值;
//做法B:定义于循环内
Widget w(取决于i的某个值);
27.1 在C++中,单一对象可能拥有一个以上的地址,尤其是在多继承时(单一继承也可能发生),所以,应避免做出“对象在C++如何布局”的假设。
1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
2)如果转型是必要的,试着将他隐藏与某个函数背后。客户随后可以调用该函数,而不需要将转型放到他们自己的代码内。
3)宁可使用C++-style(新式)转型,不要使用旧式转型。前者容易辨识出来,而且也比较有着分门别类的职掌。
28.1 handle主要指:reference、pointer和iterator。
28.2 返回handle不仅有可能违反logical constness,而且就相当于公开内部成员,降低了封装性,另外还有dangling handle的可能。
避免返回handles(包括reference、pointer、iterator)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。
29.1 当抛出异常时,异常安全的函数会:1)不泄露任何资源,2)不允许数据败坏。
29.2 异常安全函数提供一下三种保证之一:1)基本保证:抛出后,状态是正常的,但处于哪个状态不可预料;2)强烈保证:前后状态保持一直;3)不抛掷保证。
29.3 声明不抛出异常,并不是说起不会抛出异常,而是说如果抛出异常将是严重错误,会有意想不到的函数被调用(unexpected)。
29.4 在强烈保证的函数中,常使用copy and swap技术(通常效率不高);如果一个函数调用多个(强烈保证的)函数,他就很难保证是强烈保证。
29.5 由于强烈保证在效率、复杂度上的成本太高,对许多函数而言,“基本保证“一个更好的选择。
1)异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛出异常型。
2)“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
3)函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
30.1 类内定义的成员函数,隐含是inline。在类内定义的friend函数,也隐含为inline。
30.2 inline是请求,编译器可能忽略之。对于一个编译器愿意inlining的某个函数,编译器还是可能为之生成一个函数本体(当通过inline函数的地址调用时)。
30.3 构造函数和析构函数往往是inlining的糟糕候选人。
30.4 inline的缺点:1)代码量增大,2)程序库升级不便,3)调试不便
1)将大多数inlining限制在小型、被频繁调用的函数身上。这可是日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会变大。
2) 不要只因为function templates出现在头文件,就将他们声明为inline。
31.1 设计原则:1)如果使用object references或object pointers可以完成任务,就不要使用objects。2)如果能够,尽量以class声明式替换class定义式。3)为声明式和定义式提供两个不同的头文件。
31.2 对于参数或者返回类型是object的函数,声明时不需要object的定义式。
31.3 当声明式和定义式提供两个不同的头文件时,主要保持一致性。如头文件<iosfwd>。本条款也适用于templates。
31.4 通常使用pimlp(pointer to implementation)技术来实现。这样的类叫做Handle classes。
31.5 另外一个实现方式是通过interface classes。
31.5 Handle classes和interface classes实现,都会有一定程度的成本。
1)支持“编译依存性最小化”的一般构想是:相依于声明,不要相依于定义式。基于此构想的两个手段是Hande Classes和Interface class。
2)程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。
6. 继承与面向对象设计
32.1 当Penguin继承Bird时,Bird中不宜有fly接口。Square继承rectangle不是好的设计。
“public 继承”意味“is-a"。适用于base classes身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。
33.1 转交函数,就是在private继承的derived类中,将virtual函数的override定义写在public标签下。
1)derived classes内的名称会遮盖base classes内的名称。在public继承下从来没有人希望如此。
2)为了让被遮盖的名称重见天日,可使用using declaration或转交函数(forwarding functions)。
34.1 pure virtual函数、impure virtual函数、non-virtual函数分别为:只继承接口、继承接口和一份缺省实现、继承接口和一份强制实现。
34.2 当pure virtual函数也有实现时,继承接口+可以使用一份(默认)实现。
1)接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
2)pure virtual函数只具体指定接口继承。
3)普通impure virtual函数具体指定接口继承及缺省实现继承。
4)non-virtual函数具体指定接口继承以及强制性实现继承。
1)virtual函数的替代方案包括NVI(non-virtual interface)手法以及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
3)tr1::function(在C++11中已经成为std::function)对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)
绝不重新定义继承而来的non-virtual函数。
37.1 virtual函数系动态绑定,而缺省参数值确实静态绑定。
37.2 当virtual函数有默认值时,父类和派生类的默认值要一样,这无疑加重的程序员的负担,这是可以考虑virtual的替代设计。
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。
38.1 public继承带有is-a的意义,而复合(composition,组合)意味着has-a或is-implemented-in-terms-of。
1)复合(composition)的意义和public继承完全不同。
2)在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。
39.1 private继承不能做derived-to-base的转换。
39.2 private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。private继承意味着只有实现部分继承,接口部分应略去。
39.3 对于空白类,他的大小不为0,复合到其他类中时,也要占用一定空间;但是当作为其他类的基类时,并不占用空间。这就是所谓的EBO(empty base optimization)
1)private继承意味着is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2)和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
1)多重继承比单一继承复杂。他可能导致行的歧义性,以及对virtual继承的需要。
2)virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具有使用价值的情况。
3)多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。
7. 模板编程与泛型编程
1)classes和templates都支持接口(interfaces)和多态(polymorphism)
2)对classes而言接口是显示的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。
3)对template参数而言,接口是隐式(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。
42.1 在template编程中,typename必须作为嵌套从属类型的前缀名,但是也有两点例外:在base class list或member initialization list中。
template<typename T>
class Derived : public Base<T>::Nested{
Derived(int x):Base<T>::Nested(x){
typename Base<T>::Nested temp;
42.2 由于每次用到嵌套从属类型都需要加typename,通常做法是定义别名。
void f(T t){
typedef typename std::iterator_traits<T>::value_type value_type;
value_type val(*t);
...
1)声明template参数时,前缀关键字class和typename可互换。
2)请使用关键字typename标示嵌套从属类型名称;但不得在base class list(基类列表)或member initialization list(成员初始化列表)内以它作为base class 修饰符。
43.1 当基类为模板类时,并不能直接使用来自基类的名称。基类模板的通用版本,与基类模板针对某个类型的特化版本,可能在内部name、接口上完全不一样;所以编译器无法判断要使用的name是否来自基类模板。
43.2 三个办法使用基类的名字:1)名字前加sthis->;this->name()。2)使用前先using declaration一下name;using Base<T>::name;但是有对name修改访问权限的副作用。3)名字前直接加基类域限定符;Base<T>::name()。当name是virtual函数名时,这样就会关闭virtual绑定行为,这是很糟糕的。当然,这三个方法的一个公共前提是:派生类对基类的这个名字有访问权限。
可在derived class templates内通过“this->"指涉base class templates内的成员名称,或藉由一个明白写出的”base class 资格修饰符“完成。
1)Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
2)因非类型模板参数(non-type template parameter)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
3)因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具体类型(instantiation types)共享代码。
45.1 在class内声明泛化copy构造函数(member template)并不会阻止编译器生成他们自己的copy构造函数。如果不希望使用合成的copy构造函数,就需要定义正常的copy构造函数。相同规则同样适用于赋值(assignment)操作。注意:定义了泛化的构造函数,编译器就不会再合成默认构造函数了。
1)请使用member function templates(成员函数模板)生成“可接受所有兼容类型”的函数。
2)如果你声明member template用于“泛化copy构造”或“泛化assignment操作”,你还需要声明正常的copy构造函数和copy assignment操作符。
46.1 当用模板实现“条款24”的功能时,往往存在很多错误。下面的方法1中,模板参数无法匹配,编译无法通过;方法2中,根据oneHalf实例化了Rational<int>,friend函数operator*就别声明出来了,现在operator*就不再是函数模板,是函数了,支持实参的隐式转换了,所以编译可通过,但找不到定义(因为没有定义operator*(const Rational<int>&, Rational<int>&),所以链接不通过。方法3中,分析同上,区别在于,实例化类的时候operator*也被定义了。将friend函数定义在类内,就隐含为inline,但如果想避开inline的弊端(条款30),可以按照做法4实现。
//============做法1============
class Rational{
Rational(const T& n, const T& d);
/* ... */
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
Rational<int> result = oneHalf * 2;//错误!无法编译通过。
//============做法2============
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational<int> result = oneHalf * 2;//编译通过,链接错误!
//============做法3============
friend const Rational operator*(const Rational& lhs, const Rational& rhs){
Rational<int> result = oneHalf * 2;//编译通过,链接通过!
//============做法4(非inline做法)============
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs){
doMultiply(lhs, rhs);
当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
1)Traits classes使得“类型相关信息”在编译期可用。他们以templates和“templates特化”完成实现。
2)整合重载技术(overloading)后,traits classes有可能在编译期对类型执行if...else测试。
48.1 上一条款中基于iterator_traits来做重载(条件选择)也是template metaprogramming(TMP)的体现。
48.2 某些东西在TMP比在“正常的”C++容易。若将上一条款中的重载方式,替换成如下代码,对于不支持 iter += d的类型就会编译错误。因为编译期必须确保所有源代码有效,纵使是不会执行起来的代码!所以模板编程不宜与RTTI一起使用。
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
if(typid(typename std::iterator_traits<IterT>::iterator_category)
==typeid(std::random_access_iterator_tag))
iter += d;
else
1)Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和跟高的执行效率。
2)TMP可被用来生成“基于策略选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
8. 定制new和delete
49.1 new_handler在std中定义:
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
49.1 一个设计良好的new-handler函数,应该做一下事情:1)让更多内存可被使用;2)安装另一个new-handler;3)卸除new-handler;4)抛出bad_alloc,这样的异常不会被operator new捕捉,因此会被传播到内存索求处;5)不返回,通常调用abort或exit。
1)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
2)Nothrow new是一个颇为局限的工具,因为它只适用于内存分配 ;后续的构造函数调用还是可能抛出异常。
有许多理由需要写一个自定的new和delete,包括改善效能,对heap运用错误进行调试、收集head的使用信息。
1)operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0bytes申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”。
2)operator delete应该在收到null指针时不做任何事。class专属版本则还应该处理“比正确大小更大的(错误)申请。
1)当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有这么做,你的程序可能发生隐微而时断时续的内存泄露。
2)当你声明placement new和placement delete,请确定不要无意识(非故意)地掩盖了他们的正常版本。
9. 杂项讨论
1)严肃对待编译器器发出的警告信息。努力在你的编译器的最高(最苛刻)警告级别下争取”无任何警告“的荣誉。
2)不要过度依赖编译器的报警能力,因为不同的编译器对待事物的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。
1)C++标准程序库的主要机能有STL、iostreams、locales组成。并包含C99标准库。
2)略。。。(注:应该参考C++11的新特性)
3)TR1自身只是一份规范。为获得TR1提供的好处,你需要一份礼物。一个好的事物来源是Boost。
1)Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化的过程中扮演深具影响力的角色。
2)Boost提供许多TR1组件实现品,以及其他许多程序库。