1.对内置类型和STL的迭代器和函数对象而言pass-by-value通常比pass-by-reference高效。但是对于模板和类而言,相反。要把C++视为一个联邦,在一个邦国里,要遵守一个邦国的规则,才能获得高效合规的编程。
2.尽量以const, enum, inline替换#define,因为#define记号名称没有进入符号表,导致难以调试。const常量比#define有更小的代码量。第一是定义常量指针( constant pointers) 。由于常量定义式通常被放在头文件内(以便被不同的源码含入) , 因此有必要将指针(而不只是指针所指之对象)声明为const。第二定义类的专属常量时要定义为static。如:
class GamePlayer {
private:
static const int NumTurns = 5;//注意是个声明式
int scores[NumTurns];//使用该常量
};
NumTurns 是声明式而非定义式。通常C++ 要求你对你所使用的任何东西提供一个定义式,但如果它是个class 专属常量又是static 且为整数类型(integral type,例如ints, chars, bools) ,则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式(一定要求是常量吗??????????)。但如果你取某个class 专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下。#define无法提供类的专属常量。如果编译器不支持在类定义体类提供初始值,那么可以使用the enum hack。enum更加想#define,不能取一个enum的地址。
3.#define CALL_WITH_MAX (a, b) f ((a) > (b) ? (a) : (b))
int a = 5 , b = 0;
CALL_WITH_MAX(++a, b);// 被累加二次
CALL_WITH_MAX(++a, b+l0);//被累加一次
所以要尽量以inline函数代替宏。
4.const Widget* pw;Widget const* pw;效果相同
5.令函数返回const常量一般能降低编码的意外错误,比如对于乘号操作符,Rational a, b, c; (a * b) = c;这是不好的习惯,再比如可能导致这样的错误if (a * b = c)原意假若为比较的话。
6. 编译器强制实施bitwise constness ,但你编写程序时应该使用"概念上的常量性"conceptual constness) 。
当const 和non-const 成员函数有着实质等价的实现时,令non-const 版本调用const 版本可避免代码重复。编码的时候要注意两次强制转换。
7.在C part of C++,最好不要初始化,否则会导致运行期成本,但是在非C部分,最好初始化,所以内置类型和数组不保证初始化。
8.const成员和引用类型成员唯一的初始化机会就是初始化列表。
9.static对象有global对象和定义于namespace作用域,class和函数以及在file作用域内且被声明为static的对象。不包括stack和heap-base对象。函数内的static对象时local static对象,其他事非局部static对象。C++对定义于不同编译单元内的非局部static对象的初始化次序没有明确定义,原因是无法决定顺序。但是C++保证函数内的local static对象会在该函数被调用期间首次遇上该对象定义式的时候被初始化。所以为了避免跨编译单元的初始化次序问题,要以local static对象替换non-local static对象。
10.合成的构造函数已经其他合成函数,只有被调用时才会被编译器创建出来。编译器合成的析构函数默认是非virtual,但是如果基类的析构函数是virtual,那么也为virtual。编译器有时候会拒绝合成某些函数,比如下面这段代码
template<typename T>
class NameObject
{
public:
NameObject(std::string& name, const T& value)
private:
std::string& nameValue;
const T objectValue;
};
std::string newDog("persephone");
std::string oldDog("Satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 36);
p = s;
11.为了避免编译器自动合成某些函数,可以将该函数声明为private并且不实现。或者继承自Boost的noncopyable类。
12.如果类不当做基类,则不应该令其析构函数为virtual。因为这样就变成了面向对象部分,导致增加一个vptr指针。
13.由于STL中的类都没有虚析构函数,所以不能继承自它们。
14.析构函数不应该抛出任何异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后确保不再次抛出或者直接让程序结束。如果需要对某个函数运行期间的异常做出反应,应该提供一个普通函数而非析构函数中执行该操作。
15.在构造析构期间不要调用virtual函数。要想在构造和析构期间实现多态,只能从下至上使用非virtual函数近似。.
16.注意构造函数之间不能相互调用,这个时候不能重复利用代码。构造函数也不能调用赋值操作符来实现功能。理由。。。。
17.typedef std::string AddressLines[4];
std::string* pal = new AddressLines;
delete pal;//wrong
delete [] pal;//right;
18.以独立语句将new创建的对象存储于智能指针,否则有可能导致内存泄露,比如对于这种情况,·processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
因为这个语句分为三个子句:调用priority,执行 new Widget 调用tr1::shared_ptr构造函数,而这三者的执行顺序是不确定的,so。。。解决这个问题是使用分离语句。
19.绝对不要返回指针或者引用指向一个local stack对象,或者返回指向一个heap-allocated对象,或返回指向一个local static对象的指针或者引用,但是同时需要多个这样的local static 对象,一个是可以的。
20.protected并不比public更具封装性。protected一旦发生改变,和public一样,需要修改大量代码。
21.成员函数带来的封装性比非成员函数带来的封装性要低,此外非成员函数可带来较大的包装弹性,降低编译相依度。
22.如果某些东西被封装,它就不可见。越多的东西被封装,越少的人可以见到它,就有越大的弹性去改变它(类的设计者的角度)以及机能扩充性(比如可以给一个namespace增加更多的功能函数),但是不能对类增加更多的功能函数(当然可以通过继承)。
23.如果需要为某个函数的所有参数(包括this指针所指那个隐参)进行类型转换,那么这个函数必须是个non-member。
24. 通常不能(不被允许)改变std命名空间的任何东西,但是可以(被允许)为标准template制造特化版本。
25.C++只允许类模板偏特化,函数模板不能偏特化(attention, 标准问题。。。。check????????)注意下面两段代码的不同:
26.
template<typename T>
class WidgetImpl { ... }
template<typename T>
class Widget { ... }
namespace std {
template<typename T>
void swap< Widget<T> >(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}
namespace std {
template<typename T>
void swap(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}
第一个swap是企图对函数模板进行偏特化,第二个是重载了std中swap函数模板。条款25有点不理解。
27.尽可能延后变量定义式的出现。变量的定义是否出现在for循环里面还是外面要看情况一般当循环次数比较大时,尽量放外面。
28.单一对象可能拥有一个以上的地址。因为派生类部分可能和基类部分不连续。
29.注意这段代码:
class Window { // base class
public:
virtual void onResize() { ... } // base onResize impl
...
};
class SpecialWindow : public Window { // derived class
public:
virtual void onResize() { // derived onResize impl;
static_cast<Window>(*this).onResize(); // cast *this to Window,
// then call its onResize;
// this doesn't work!
... // do SpecialWindow-
} // specific stuff
...
};
static_cast<Window>(*this).onResize(); // cast *this to Window,这句话并没有更改当前对象,而是改变当前对象的基类部分的临时副本。应该这么写window::onResize();
30.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。
31.避免返回handles(引用,指针,迭代器)指向对象内部,可以增加封装性,帮助const成员函数行为像个const,并将发生dangling handles的可能性降至最低,后面这句话回想下场景。
32.强烈保证"往往能够以copy-and-swap 实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义。
33.大部分编译器会拒绝比较复杂的inline函数,比如包括了循环和递归,以及virtual函数(理由是因为virtual直到运行了才决定函数,而inline在编译期间替换掉)。
34.编译器通常不对"通过函数指针而进行的调用"实施inlining ,这意味对inline 函数的调用有可能被inlined,也可能不被inlined,取决于该调用的实施方式(这个指的是在同一程序里面两种情况可以同时出现),比如若将该inline函数赋值给一个函数指针,则不实施inline。
35.将大多数inlining 限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)(修改inline函数将导致链接库全部重新编译) 更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。不要只因为function templates 出现在头文件,就将它们声明为inline。
36.支持"编译依存性最小化"的→般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes 和Interface classes 。程序库头文件应该以"完全且仅有声明式" (full and declaration-only forms) 的形式存在。这种做法不论是否涉及templates 都适用。
37.为了让被派生类屏蔽的名字重现可以使用using声明或者转交函数(就是在派生类定义一个成员函数,然后调用基类的成员函数)。
38.可以给纯虚函数提供实现,也可以调用该实现。
39.使用non-virtual interface (NVI) 手法,那是Template Method 设计模式的一种特殊形式。它以public non-virtual 成员函数包裹较低访问性(private 或protected)的virtual 函数。
40.将virtual 函数替换为"函数指针成员变量",这是Strategy 设计模式的一种分解表现形式。
41.以trl::function 成员变量替换virtual 函数,因而允许使用任何可调用物(callable entity) 搭配一个兼容于需求的签名式。这也是Strategy 设计模式的某种形式。
42.将继承体系内的virtual 函数替换为另一个继承体系内的virtual 函数。这是Strategy 设计模式的传统实现手法。
43.绝对不要重新定义继承而来的non-virtual 函数。
44.注意即使对于virtual函数,其默认参数是静态绑定的,而不是动态绑定的,就说基类给一个virtual函数设定了默认参数,派生类重新定义该函数,并且改变了默认参数,当用一个基类指针指向一个派生类对象时,调用该函数时是动态的,但是参数不是动态的。这样做是为了效率。可以考虑使用NVI。
45.C++中可以模拟实现java中的final,使用继承加复合,嵌套类。
46.主要是当protected 成员或者virtual 函数牵扯进来的时候应该使用private继承,否则尽量使用复合。Private 继承意味is-implemented-in-terms of (根据某物实现出)。它通常比复合(composition) 的级别低。但是当derived class 需要访问prot饵ted base class 的成员,或需要重新定义继承而来的virtual 函数时,这么设计是合理的。和复合(composition) 不同, private 继承可以造成empty base 最优化。这对致力于"对象只寸最小化"的程序库开发者而言,可能很重要。
47..C++ 裁定凡是独立(非附属)对象都必须有非零大小,所以空类的对象也有大小,一般官方是插入一个char到空对象,有些编译器为了内存对齐,会插入更多内容。注意class HoldsAnlnt: private Empty {
private:
int X;
几乎可以确定sizeof(HoldsAn lnt)==sizeof(int)。这是空白基类优化,只能在单一继承下可行,多重继承不可行。
48.使用virtual继承速度要慢些,virtual 继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes 不带任何数据,将是最具实用价值的情况。
49.template<typename C> //允许使用"typenarne" (或"class")
void f(const C& container, //不允许使用可严name"
typename C::iterator iter); //一定要使用"typename"
50.typenarne 不可以出现在base classes list 内的嵌套从属类型名称之前,也不可在member initialization list (成员初值列)中作为base class 修饰符。例如:
template<typename T>
class Derived : public Base<T>::Nested {//base class list中不允许"typename"
public:
explicit Derived(int x)
: Base<T>::Nested(x) //member initialization list不允许typename
{
typename Base<T>::Nested temp; //嵌套从属类型名称,既不在base class list中
//也不在member initialization list要加上typername
...
}
....
};
51.可在derived class templates 内通过"this->" 指涉base class templates 内的成员名称,或藉由一个明白写出的"base class 资格修饰符"完成。第三个做法就是使用作用域符号,明确告知在基类作用域。这些做法都是对编译器承诺base class template 的任何特化版本都将支持其一般(泛化〉版本所提供的接口,但是假如事实上的特化版本并没有这个成员时,会导致编译错误。如:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
sendClear(info); // if Company == CompanyZ,
// this function doesn't exist
write "after sending" info to the log;
}
...
};
template<> // a total specialization of
class MsgSender<CompanyZ> { // MsgSender; the same as the
public: // general template, except
... // sendCleartext is omitted
void sendSecret(const MsgInfo& info)
{ ... }
};
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
write "before sending" info to the log;
this->sendClear(info); // okay, assumes that
// sendClear will be inherited
write "after sending" info to the log;
}
...
};
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // tell compilers to assume
... // that sendClear is in the
// base class
void sendClearMsg(const MsgInfo& info)
{
...
sendClear(info); // okay, assumes that
... // sendClear will be inherited
}
...
};
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
...
MsgSender<Company>::sendClear(info); // okay, assumes that
... // sendClear will be
} //inherited
...
};
52.可以使用member function templates (成员函数模板〉生成"可接受所有兼容类型"的函数。
53如果你声明 member templates 用于"泛化 copy构造函数或者泛化assignment操作"你还是需要声明正常的copy构造函数和copy assignemt操作符。
54.当我们编写一个class template,而它所提供之"与此template 相关的"函数支持"所有参数之隐式类型转换"时,请将那些函数定义为"class template 内部的friend函数",假如需要考虑inline问题,则可以定义一个模板外的辅助函数。
55.Traits classes 使得"类型相关信息"在编译期可用。它们以templates 和"templates特化"完成实现。整合重载技术后,traits classes 有可能在编译期对类型执行if...else 测试。
struct input_iterator_tag { };
struct output iterator tag { };
struct forward_iterator_tag: public input_iterator_tag { };
struct bidirectional_iterator_tag: public forward_iterator_tag { };
struct random_access_iterator_tag: public bidirectional iterator_tag { };
针对通用指针步进计算的一个函数模板
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (i ter is a random access iterator) {
iter += d; //针对random access 迭代器使用途代器算术运算
else {
if (d >= 0) { while (d--) ++iter; } //针对其他迭代器分类反复调用++或--
else { while (d++) - -iter; }
}
下面这段代码就是所需功能,但是有编译错误,并且也不合理
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (typeid(typename std: :iterator_traits<工terT>::iterator_category)
==type工d(std::random_access_iterator_tag))
...
}
理由在于
下面就是个例子:
std::list<int>::iterator iter;//移动iter 向前走10 个元素:上述实现无法通过编译。
下面这一版advance 便是针对上述调用而产生的。将template 参数IterT 和DistT 分别替换为iter 和10 的类型之后,我们得到这些:
void advance (std: :list<int>: :iterator& iter, int d)
{
if (typeid(std::iterator_traits<sbd::list<int>::iterator>::iteratorcategory)
== typeid(std::random access iterator tag}) {
iter += d; II错误!
else {
if (d >= 0) { while (d--) ++iter;}
else { while (d++) --iter;}
}
}
问题出在我所强调的那一行代码使用了+=操作符,那便是尝试在一个list<int>::iterator 身上使用卡,但list<int>::iterator 是bidirectional 迭代器(见条款47) ,并不支持+=。只有
random access 迭代器才支持+=。此刻我们知道绝不会执行起+=那一行,因为测试typeid 的那一行总是会因为list<int>::iterators 而失败,但编译器必须确保所有源码都有效,纵使是不
会执行起来的代码!而当iter 不是random access 迭代器时"iter += d" 无效。与此对比的是traits-based TMP 解法,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用
的操作(操作符)都可施行于该函数所对付的类型。
template<typename IterT, typename DistT>
void doAdvance(IterT, T& iter, DistT d ,
std::random access iterator tag)
{
iter += d;//这份实现用于random access迭代器
}
template<typename IterT, typename DistT> //这份实现用于bidireetional迭代器
void doAdvance(IterT& iter, DistT d ,
std::bidirectional iterator tag)
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d ,
std::input iterator tag)//这份实现用于input法代器
{
if (d < 0 ) {
throw std: lout of range ("Negative distance");
}
while (d--) ++iter;
}
template<typename IterT, typename DistT>
void advance{IterT& iter, DistT d)
{
doAdvance( //调用的doAdvance版本对iter 之迭代器分类而言必须是适当的。
iter, d ,
typename std::iterator tra工ts<工terT>::iterator category()
);
}
56.set new handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。Nothrow new 是-个颇为局限的工具,因为它只适用于内存分配:后继的构造函数调用还是可能抛出异常。
57.operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理o bytes 申请。Class专属版本则还应该处理"比正确大小更大的(错误)申请"。operator delete应该在收到null 指针时不做任何事。
58.当你写一个placement operator new ,请确定也写出了对应的placementoperator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏。当你声明placementnew 和placementdelete,请确定不要无意识(非故意)地遮掩了它们的正常版本。