这边好书应该早点看的。总结一下,方便日后查阅。//for_wind
1、尽量以const,enum,inline替换#define,或说尽量以编译器取代预处理器
#define可能并不进入符号表(symbol table)
const:注意:A、常量指针;B、class专属常量。注:为了确保class专属常量至多只有一份实体,必须让它成为static成员。
2、尽量以<iostream>代替<stdio.h>
3、尽量以new和delete取代malloc和free
差别在构造函数、析构函数
4、尽量以C++风格的注释形式
即尽量用//,避免采用/* */在内嵌注释时,造成注释块过早结束
5、使用相同形式的new和delete
A、指的是new,delete和new[],delete[]配对使用。
B、当你使用new动态生成一个对象,有两件事发生:内存被分配;针对此内存会有一个(或更多)构造函数被调用,然后内存才被释放(delete).
C、尽量不要对数组形式做typedef动作。
总结:当你用new生成对象时,如果用new type-object[] ,则要使用 delete []type-object ,否则使用 delete
6、记得在destructor中以delete对付pointer members
A、当存在pointer members时,
每个constructor中将该指针初始化或或为0;
在assignment运算符中,将该指针原有的内存删除,重新配置一块;
在destructor中删除这个指针。
B、谁new,谁delete。
C、注意到smart pointers。
以独立语句将newed对象置入智能指针 Store newed objects in smart pointers in standalone statements.
以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
7、为内存不足的状况预先做准备
set_new_handler。
8、撰写operator new 和 operator delete时应遵循的公约
指需要和缺省的operator new 保持一致(正确的返回值,内存不足时调用错误处理函数,准备应付“no memory”的需求(申请0内存)(实际上视申请0bytes为1bytes));
需要和 operator delete保持一致(保证删除一个null指针是安全的)。
更具体地说,operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理 0 bytes申请。Class专属版本则还应该处理“比正确大小更大的(错误)申请”。
operator delete应该在收到null指针时不做任何事,Class专属版本则还应该处理“比正确大小更大的(错误)申请”。
9、避免遮掩了new的正规形式
和8条一样,需要定义class专属的operater new并调用缺省的operater new
10、如果你写了一个operator new,请对应写一个operator delete。
将这两个一并写出,使它们能够共享相同的假设。(重载它们大多是为了效率,一般可采用链表之类的POOL来管理)
11、如果class内动态配置有内存,请为此class声明一个copy constructor 和一个assignment运算符
避免:内存泄露问题和指针别名问题(如,重复删除以及未定义问题)。
如果不想用或没必要,不如:将这些函数声明为private,并且不要定义(实现)之,这可以阻止clients调用它们,也可以避免编译器产生它们。
12、在constructor中尽量以initiation动作(即member initiation list)取代assignment动作
好处:A、满足const members 和 reference members必需通过member initiation list初始化;B、提高data members初始化的效率(减少函数调用)
13、initialization list中的members初始化次序应该和其在class内的声明次序相同
注意到:class members是以它们在class内的声明次序来初始化的,而和member initialization list中出现的次序完全无关。
(只有nonstatic data members的初始化才使用这条规则。)
14、总是让base class拥有virtual destructor。
避免 nonvirtual destructor产生的“未定义行为问题”。
C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义----实际执行时通常发生的是对象的derived 成分没被销毁。
方法:给base classes 定义一个 virtual 析构函数。
任何class只要带有virtual 函数都几乎确定应该有一个virtual析构函数。
欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数被调用。这份信息通常由一个所谓vptr(virtual table pointer)指针指出。
令class带一个pure virtual(纯虚)析构函数会导致abstract(抽象)classes ---也就是不能被实体化(instantiated)的class.
总结:
Polymorphic(带多态性质的)base classes 应该声明一个 virtual析构函数。如果 class带有任何virtual函数,它就应该拥有一个virtual析构函数。
Classes 的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphic),就不该声明Virtual析构函数。
15、令operator = 传回“ *this 的reference”
形式:C&
C::operator=(const C&)
{ ..........
return *this;
}
assignment运算符总是必须传回一个reference,指向其左侧引数,即*this。否则会妨碍assignment串链或(和)妨碍调用端的隐式型别转换。
16、在operator=中为所有data members设定(赋值)内容。
当你编写一个copying 函数,请确保(1)复制所有local成员变量,(2)调用所有base classes内的适当的copying函数。
Copying 函数应该确保复制“对象内的所有成员变量”及“所有base class成员”。
注意:Derived的copy constructor必须确保调用base 的copy constructor而不是base的default constructor。
方法:在Derived copy constructor的成员初始列中为base指定初值。注意加粗的部分,不应该遗漏
如:class Derived:public Base{
public:
Derived(const Derived& rhs):
Base(rhs),y(rhs,y) { }
};
不要尝试以某个copying函数实现另一个copying函数。应该将共同功能放进第三个函数中,并由两个copying函数共同调用。
17、在operator=中检查是否“自己赋值给自己”
为了确保正确性和效率。
别名问题(aliasing)和对象同等问题(object identity)出现的场合:不限于operator=函数内(只要出现references和pointers,任何两个代表兼容型别的对象名称都可能实际指向同一对象)。必须格外注意避免此类问题的发生。
如何判断是否相等:通过值的比较或通过地址的比较,还可以设计并通过函数返回对象识别码。
18、努力让接口完满(complete)且最小化
完成合理的工作;尽量少,不至于有重复重叠功能
19、区分member function,non-member function和friend function三者
虚函数必须是class members;
绝对不要让operator>>和operator<<成为members。应该设为non-member functions或friend(如果用到non-public members的话)。
只有non-member functions 才能在其最左端引数(argument)身上实施型别转换。应该设为non-member functions或friend(如果用到non-public members的话)。
上述情况外,设计成member function。
注:宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性、包裹弹性和技能扩充。
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。
20、避免将data members放在公开接口中
不如设计成函数,从而获得“一致性”和优良的存取控制,(利用“函数抽象性”)。
21、尽可能使用const
A、如果关键字const出现在星号左边,表示被指类型是常量;
如果出现在星号右边,表示指针自身是常量;
如果在星号两边,表示被指类型和指针两者都是常量。
B、Const最具有代表性的是函数声明时的应用。Const可以和函数返回值、各参数、函数自身产生关联。
C、Const 成员函数:
1)它们使class接口比较容易理解。
2)它们使“操作const对象”成为可能。
D、如果函数的返回类型是个内置类型,那么修改函数返回值不合法。
E、Bitwise constness(physical constness)
成员函数只有在不更改对象的任何成员变量(static除外)时才可以说是const。也就是说它不更改对象内任何一个位(bit)。
F、Logical constness
一个const成员函数可以修改它所处理的对象内某些bits,但只有在客户端侦测不出的情况下才可以。
Mutable(可变的)关键字可以释放掉non-static成员变量的bitwise constness约束;
G、在const和non-const成员函数中避免重复,如果可以,让后者调用前者
Const成员函数调用non-const成员函数是一种错误行为,因为对象有可能因此被改动。
22、尽量使用pass-by-reference(传址),少用pass-by-value(传值)
尽量以pass by reference 替换 pass by value 。前者通常比较高效,并可避免
切割问题(slicing problem)。
不过并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言 pass by value 往往比较适当。
如果你有个对象属于内置类型(例如 int),pass by value 往往比 pass by reference的效率高些。
此外,pass by reference 你需要格外注意避免别名问题。
23、当你必须传回object,不要尝试传回reference
牢记:reference是一个既有对象的名称。注意可能产生的内存泄露问题。
24、在函数重载和参数缺省化之间谨慎选择
考虑:A、是否有恰当的值可以用来当作缺省参数;B、有多少个算法。
25、避免对指针型别和数值型别进行重载
模凌两可。
26、防卫潜伏的ambiguity状态
“存取限制”不能解除“因多继承而来的members”的模凌两可状态。(理由:改变某个class member的可存取性绝不应该连带改变程序的意义。)
27、如果不想使用编译器暗自产生的member functions,就应该明白地拒绝它。
private,空。
可以自定义编译器会默认生成的函数,手动定义成private;或者使用空基类(empty base class)声明空函数来继承。
28、尝试切割global namespace
using
29、避免传回内部数据的handles
问题:违反抽象性,出现dangling handles和别名问题。
30、避免写出member functions,传回一个non-const pointer 或 reference并以之指向较低存取层级的members
问题:破坏存取保护,易出现别名问题。
31、千万不要传回“函数内local对象的reference”或“函数内以new获得的指针所指的对象”
问题:未定义行为,内存泄露
32、尽可能延缓变量定义式的出现
即需要时才定义,避免构造(和析构)非必要的对象,还可以避免无意义的default constructors。此外,可增加程序的清晰度并改善程序效率。
33、明智地使用inlining
A、inline,只是对编译器的一种提示,并
非是强制命令。意味着,在编译阶段将调用动作以被调用函数的本体取代之,是否真正inline,视编译器而定。
大部分的编译器会
拒绝将
复杂的(也就是内含循环或递归调用的)函数inline化,而所有(除了最平凡、几乎什么也没做的)的
虚拟函数都会阻止inlining的进行。
B、权衡调用函数的成本,和程序代码体积增加的成本。
好处:inline函数的函数体很小,较小的目标代码和较高cache命中率;
代价:免除函数调用的成本,但可能会增加目标代码的大小。inline行为所造成的程序代码膨胀会导致病态的换页行为(thrashing现象)。
inline函数无法随着程序库的升级而升级;如果函数有static对象,有反直观的行为,因此通常避免将其声明为inline;此外大部分除错器对inline函数束手无策
C、将大多数inlining限制在
小型、被频繁调用的函数上。这可使日后的调试过程和二进制 升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。不要只因为function templates出现在头文件,就将它们声明为inline。
34、将文件之间的编译依赖关系降至最低
支持“编译依存性最小化”的一般构想是:依赖于声明式,不要依赖于定义式。基于此构想的两个手段是Handle classes和Interface classes。
程序库头文件应该以“完全且仅有声明式”(full and declearation-only forms)的形式存在。这种做法不论是否涉及templates都适用
35、确定你的public inheritance,模塑出“is a”的关系
C++进行(OOP)面向对象编程,最重要的一个规则是:public inheritance (公开继承)意味“is - a”(是一种)关系。
如果你令class D(“derived”)以public形式继承class B(“Base”),你便是告诉编译器: 每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。
“pubilc继承”意味is-a。适用于Base classes身上的每一件事情一定也是用于 Derived classes 身上,因为每一个Derived class对象也都是一个Base Class对象。
36、区分接口继承和实现继承 differentiate between inheritance of interface and inheritance of implementation
纯虚函数、一般(非纯)虚函数、非虚函数之间的差别,允许你精确地指定你希望derived class继承的东西:只继承接口,或是继承接口和缺省行为,或是继承接口和一份实现代码。
接口继承和实现继承不同。在pubilc继承下,derived classes总是继承Base class 的接口。
Pure virtual函数只具体指定接口继承。
Impure virtual 函数具体指定接口继承及缺省实现继承。
Non-virtual函数具体指定接口继承以及强制性实现继承。
37、绝不要重新定义继承而来的非虚拟函数。
38、绝对不要重新定义继承而来的缺省参数值。
因为虚拟函数(你唯一应该复写的东西)是动态绑定(取决于指针或引用指向的类型),而缺省参数值却是静态绑定(取决与指针或引用的静态类型)。
39、避免在继承体系中做向下转型
向下转型downcast:从一个“base class 指针”转为一个“derived class 指针”。问题:代码难以理解,且难以维护。解决方法:将转型动作以虚拟函数的调用取代,并且让每一个虚拟函数有一个无任何动作的缺省实现代码,以便应用在并不想要实行该虚拟函数的任何classes升上。
40、通过laying技术来模塑 has-a 或 is-implemented-in-terms-of的关系
复合(composition)是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。
注意区别两者的关系。复用。
41、区分inheritance 和 templates
inheritance用来产生一群classes,其中对象型别会影响class 的函数属性。
templates用来产生一群classes,其中对象型别不会影响class的函数属性。
42、明智地运用private inheritance
如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个Base class对象。
Private base class继承而来的所有成员,在derived class中都会变成private属性。
43、明智地使用多继承
多继承引发的问题:模凌两可ambiguous。
多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
Virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。
解决:可以尝试共同继承一个新加的公共的base class。
多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相结合
44、说出你的意思并了解你所说的每一句话
共同的base class意味着共同的特性。如果class D1和D2都声明class B为base,则D1和D2从B继承了共同的data members和member functions。
public inheritance(公开继承)意味着“是一种(isa)”。如果class D以public方式继承class B,则每个D对象便是一个B对象,反之并不成立。
private inheritance(私有继承)意味着“根据某物实现(is-implemented-in-terms-of)”。如果class D以private方式继承了class B,D对象便是根据D实现的;B对象和D对象之间并没有任何概念上的关系。
Layering意味着“有一个(has-a)”或“根据某物实现(is-implemented-in-terms-of)”。如果class A内含一个型别为B的data member,那么A对象之中便有一个型别为B的成分,或者说A对象是根据B对象实现。
----只有当牵涉到public inheritance时,一下数点才成立:---见36.
纯虚函数意味着:只有其函数接口会被继承。如果class C声明了一个纯虚函数mf,则C的subclasses必须继承mf的接口,而C的具象subclasses必须提供自己的实现代码。
一般(非纯)虚函数意味着:函数接口及缺省实现代码都被继承。如果class C声明了一个一般(非纯)虚函数mf,C的subclasses必须继承mf的接口,它们可以继承mf的缺省实现代码(如果它们决定这么做的话)。
非虚函数意味着:此函数的接口和其实现代码都会被继承。如果class C声明了一个非虚函数mf,那么C的subclasses必须同时继承mf的接口和实现代码。事实上可以说,mf为C定义了一个“不变性凌驾于变异性之上”的性质。
45、了解C++默默编写并调用哪些函数
C++会为默认的空类(empty class)添加
Default constructor默认构造函数Copy constructor构造函数destructor 析构函数assignment 赋值运算符和一对address-of地址取值运算符
注:唯有这些函数被调用时,它们才会被编译器创建出来。
对于assignment 赋值运算符,以下情况拒绝产生一个缺省的operator=,你必须自行定一个assignment运算符。
A、“内含reference member”;B、内含const members;C、base class中将assignment运算符声明为private。
46、宁愿编译和连接时出错,也不要执行时才错
47、使用non-local static objects之前线确定它已有初值
采用Singleton pattern技术:以函数内static objects取代non-local static objects。在此函数调用期第一次遇到此对象的定义时,进行初始化。
48、不要对编译器的警告信息视如不见。
49、尽量让自己熟悉C++标准库
50、加强自己对C++的理解
以下是第三版中新加的,理解一下。“??”处没有完全理解
--21 more--
Const_cast
用法:const_cast<type_id>(expression)
该运算符用来修改类型的const或volatile属性。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;
常量对象被转换成非常量对象;
Static_cast
用法:static_cast<type_id>(expression)
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
C++ primer 里说明在进行隐式类型转换都用
Int I = Static_cast<int>f; // float f = 1.42f;
---
条款04:确定对象被使用前已被初始化 Make sure that object are initialized befor they’re used.
读取未初始化的值会导致不明确的行为。
对象的初始化何时一定发生,何时不一定发生。
对于无任何成员的内置类型,必须手工完成此事。
对于内置类型以外的任何其他东西,初始化责任落在构造函数(constructors)身上。确保每一个构造函数都将对象的每一个成员初始化。
C++规定,
对象的成员变量的初始化动作发生在进入构造函数本体之前。
构造函数的最佳写法是,
使用 member initialization list(成员初始化表)如:
ABEntry::ABEntry(char &name,char& address,list &phones)
: theName(name),theAddress(address),thePhones(phones)
{
…….
}
编译器会为用户自定义类型(user-defined types)之成员变量自动调用default构造函数 --- 如果那些成员变量在“成员初始化列表”中没有被指定初值的话。
成员变量是 const 或 references,它们就一定需要初值,不能被赋值。
C++有着十分固定的“成员初始化次序”。Base classes 更早于其derived classes 被初始化,而class的成员变量总是以其声明次序被初始化。
Static 对象,其有效时间从被构造出来直到程序结束为止,因此stack和heap-based对象被排除。
C++对于“定义于不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义。
小方法:将每一个non-local static对象放到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所含的对象。
总结:
为内置型对象进行手工初始化,因为C++不保证初始化它们。
构造函数最好使用成员初始化列表(member initialization list),而不要再构造函数本体内使用赋值操作(assignment).
为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
-------
条款09:绝不在构造和析构过程中调用Virtual函数
Never call virtual functions during construction or destruction
Base class构造期间 virtual 函数绝不会下降到 derived classes阶层。
(在base class构造期间,virtual函数不是virtual函数)
---
条款13:以对象管理资源 Use object to manage resources.
把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放。
STL标准程序库提供的auto_ptr正是针对这种形式而设计的特制产品。Auto_ptr是个“类指针(pointer-like)对象”,也就是“智能指针”,其析构函数自动对其所指对象调用delete。
Std::auto_ptr<Investment>pInv(CreateInvesment());
上面的例子示范“以对象管理资源”的两个关键想法:
获得资源后立刻放进管理对象(managing object)内。
管理对象(managing object)运用析构函数确保资源被释放。
使用auto_ptrs有一个性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成NULL,而复制所得的指针将取得资源的唯一拥有权!
Auto_ptr的替代方案是“引用计数型智慧指针”(reference-counting smart pointer;RCSP)。RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。
TR1的tr1::shared_ptr就是个RCSP,你可以这么写
Std::tr1::shared_ptr<Investment>pInv(CreateInvestment());
Auto_ptr 和 shared_ptr 都是在其析构函数上调用delete ,而不是 delete []动作。那意味着在动态分配而得的array上使用,是错误的。
总结:
为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII classes 分别是tr1::
shared_ptr 和 auto_ptr。
----------------
条款14:在资源管理类中小心copying行为
Think carefully about copying behavior in resource-managing classes.
RAII守则:资源在构造期获得,在析构期被释放;
类似Mutex的互斥对象(mutex object)时,因为有lock,unlock两种状态,可以采用以下方法,确保释放;
A、禁止复制。Auto_ptr创建
B、引用计数(reference-count)。Shared_ptr创建
总结:
复制RAII对象必须一并复制它所管理的资源,所以资源的copying 行为决定RAII对象的copying行为。
普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。
-----------------
条款15:在资源管理类中提供对原始资源的访问
Provide access to raw resources in resourece-managing classes
所有智能指针,如tr1::shared_ptr 和 auto_ptr 也重载了指针取值(pointer dereferencing)操作符(operator->)和(operator*),它们允许隐式转换至底部原始指针
总结:
API 往往要求访问原始资源(raw resources),所有每一个RAII class应该提供一个“取得其所管理之资源”的办法。
对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。
-----------
条款17:以独立语句将newed对象置入智能指针
Store newed objects in smart pointers in standalone statements.
以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。
----------
条款19:设计 class 犹如设计 type
Treat class design as type design.
设计高效类型(types)类(classes)的方法:
新type的对象应该如何被创建和销毁?(构造和析构函数以及内存分配和释放)对象的初始化和对象的赋值该有什么样的差别?(构造函数和赋值(assignment)操作符的行为)新type的对象如果被passed by value (以值传递),意味着什么?(Copy函数用来定义一个type的passed by value)什么是新type 的“合法值”?<异常处理>你的新type需要配合某个继承图系(inheritance graph)吗?<是否需要虚函数>你的新type需要什么样的转换?什么样的操作符和函数对此新type而言是合理的?什么样的标准函数应该驳回?谁该取用新type的成员?什么是新type的“未声明接口”(undeclared interface)?你的新type有多么一般化?你真的需要一个新type吗?
Class 的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条款覆盖的所有主题。
-----------
条款27:尽量少做转型动作
Minimize casting.
Const_cast 通常被用来将对象的常量性转除(cast away the constness)。
Dynamic_cast用来执行“安全向下转型”,用来决定某对象是否归属继承体系中的某个类型。
Reinterpret_cast执行低级转型,取决于编译器,表示它不可移植。
Static_cast 强迫隐式转换。
如果可以尽量避免转型。
如果转型是必要的,试着将它隐藏于某个函数背后。
宁可使用C++style(新式)转型,不要使用旧式转型。
---------------
条款41:了解隐式接口和编译期多态
Understand implicit interfaces and compile-time polymorphism.
通常显示接口由
函数的签名式(函数名称、参数类型、返回类型)构成。
隐式接口并不基于函数签名式,而是由有效表达式(valid expressions)组成。
Classes 和 template 都支持接口(interfaces)和多态(polymorphism)。
对
classes而言接口是显示的(explicit),以函数签名为中心。多态则是通过
virtual函数发生于
运行期。
对
template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过
template具现化和函数重载解析(function overloading resolution)发生于
编译期。
-----------
???条款42:了解typename的双重意义
Understand the two meanings of typename.
声明template参数时,不论使用关键字class或typename,意义完全相同。
请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初始列表)内以它作为Base class修饰符。
------------------
????????条款43:学习处理模板化基类内的名称
Know how to access names in templatized base classes.
可在derived class templates 内通过“this->”指向base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。
--------------
条款44:将与参数无关的代码抽离templates
Factor parameter-independent code out of templates.
Templates 生成多个 classes 和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码。
??????条款45:运用成员函数模板接受所有兼容类型
Use member function templates to accept “all compatible types.”
使用member function templates(成员函数模板)生成“可接受所有兼容类型” 的函数。
如果你声明 member template 用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符
??????条款46:需要类型转换时请为模板定义非成员函数
Define non-member functions inside templates when type conversions are desired.
当我们编写一个class template ,而它所提供的“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
??????条款48:认识template元编程
Be aware of template metaprogramming.
Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译器,因而得以实现早期错误侦测和更高的执行效率。
TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。