读书笔记之:More Effective C++ (2007)[+]

条款1:指针与引用的区别

条款2:尽量使用C++风格的类型转换

条款3:不要对数组使用多态

条款4:避免误用的缺省构造函数

条款5:谨慎定义类型转换函数

隐式转换的实现方式:单参数构造函数和隐式类型转换操作符。

条款6:自增,自减操作符前缀形式和后缀形式的区别

条款7:不要重载&&,||或,

条款8:理解各种不同含义的new和delete

new 操作符(new operator)和new 操作(operator new)的区别。

new操作符调用一个函数来完成必要的内存分配,你能够重写或重载这个函数来改变这个行为。new操作符为分配内存所调用的函数名字是operator new

函数 operator new 通常这样声明:

void * operator new(size_t size);

你一般不会直接调用 operator new,但是一旦这么做,你可以象调用其它函数一样调用它:

void *rawMemory = operator new(sizeof(string));

操作符 operator new 将返回一个指针,指向一块足够容纳一个 string 类型对象的内存。

就象 malloc 一样,operator new 的职责只是分配内存。它对构造函数一无所知。operator new 所了解的是内存分配。把 operator new 返回的未经处理的指针传递给一个对象是 new 操作符的工作。当你的编译器遇见这样的语句:

string *ps = new string("Memory Management");

它生成的代码或多或少与下面的代码相似:

void *memory =operator new(sizeof(string)); // 得到未经处理的内存,为 String 对象
call string::string("Memory Management") on *memory; //初始化内存中的对象
string *ps = static_cast<string*>(memory); // 是 ps 指针指向新的对象

注意第二步包含了构造函数的调用,你做为一个程序员被禁止这样去做。你的编译器则没有这个约束,它可以做它想做的一切。因此如果你想建立一个堆对象就必须用 new 操作符,不能直接调用构造函数来初始化对象。

看看 new 操作符(new operator)与 operator new的关系,你想在堆上建立一个对象,应该用 new 操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用 operator new 函数;它不会调用构造函数。如果你想定制自己的在堆对象被建立时的内存分配过程,你应该写你自己的 operator new 函数,然后使用 new 操作符,new 操作符会调用你定制的 operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该用 placement new。

条款9:使用析构函数防止资源泄漏

条款10:在构造函数中防止资源泄漏

C++拒绝为没有完成构造操作的对象调用析构函数是有一些原因的,而不是故意为你制造困难。原因是:在很多情况下这么做是没有意义的,甚至是有害的。如果为没有完成构造操作的对象调用析构函数,析构函数如何去做呢?仅有的办法是在每个对象里加入一些字节来指示构造函数执行了多少步?然后让析构函数检测这些字节并判断该执行哪些操作。这样的记录会减慢析构函数的运行速度,并使得对象的尺寸变大。C++避免了这种开销,但是代价是不能自动地删除被部分构造的对象。

条款11:禁止异常信息传递到析构函数外

在有两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地 delete。第二种是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象。

条款12:理解"抛出一个异常"与"传递一个参数"或"调用一个虚函数"间的差异

从语法上看,在函数里声明参数与在 catch 子句中声明参数几乎没有什么差别:

class Widget { ... };
//一个类,具体是什么类
// 在这里并不重要
void f1(Widget w); // 一些函数,其参数分别为
void f2(Widget& w); // Widget, Widget&,或
void f3(const Widget& w); // Widget* 类型
void f4(Widget *pw);
void f5(const Widget *pw);
catch (Widget w) ... //一些 catch 子句,用来
catch (Widget& w) //捕获异常,异常的类型为
...
catch (const Widget& w) ... // Widget, Widget&, 或
catch (Widget *pw) ... // Widget*
catch (const Widget *pw) ...

你因此可能会认为用 throw 抛出一个异常到 catch 子句中与通过函数调用传递一个参数两者基本相同。这里面确有一些相同点,但是他们也存在着巨大的差异。

让我们先从相同点谈起。你传递函数参数与异常的途径可以是传值、传递引用或传递指针,这是相同的。但是当你传递参数和异常时,系统所要完成的操作过程则是完全不同的。产生这个差异的原因是:你调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。

条款13:通过引用捕获异常

通过引用捕获异常(catch-by-reference)能使你避开上述所有问题。不象通过指针捕获异常,这种方法不会有对象删除的问题而且也能捕获标准异常类型。也不象通过值捕获异常,这种方法没有 slicing problem,而且异常对象只被拷贝一次。

条款14:审慎使用异常规格

条款15:了解异常处理的系统开销

条款16:牢记80/20准则

条款17:考虑使用lazy evaluation(懒惰计算法)

引用计数,区别对待读取和写入,懒惰提取(lazy fetching),

条款18:分期摊还期望的计算

条款19:理解临时对象的来源

首先考虑为使函数成功调用而建立临时对象这种情况。当传送给函数的对象类型与参数类型不匹配时会产生这种情况。仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。

建立临时对象的第二种环境是函数返回对象时。

条款20:协助完成返回值优化

以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的。

这种技巧是返回 constructor argument 而不是直接返回对象,你可以这样做:

// 一种高效和正确的方法,用来实现
// 返回对象的函数
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}

仔细观察被返回的表达式。它看上去好象正在调用 Rational 的构造函数,实际上确是这样。你通过这个表达式建立一个临时的 Rational 对象,

Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() *rhs.denominator());

并且这是一个临时对象,函数把它拷贝给函数的返回值。

返回 constructor argument 而不出现局部对象,这种方法还会给你带来很多开销,因为你仍旧必须为在函数内临时对象的构造和释放而付出代价,你仍旧必须为函数返回对象的构造和释放而付出代价。但是你已经获得了好处。C++规则允许编译器优化不出现的临时对象(temporary objects out of existence)。

因此如果你在如下的环境里调用 operator*:

Rational a = 10;
Rational b(12);
Rational c = a * b;// 在这里调用 operator*

编译器就会被允许消除在 operator*内的临时变量和 operator*返回的临时变量。它们能在为目标 c 分配的内存里构造 return 表达式定义的对象。如果你的编译器这样去做,调用 operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数――建立 c 时调用的构造函数。而且你不能比这做得更好了,因为 c 是命名对象,命名对象不能被消除。不过你还可以通过把函数声明为inline 来消除 operator*的调用开销:

// the most efficient way to write a function returning an object
inline const Rational operator*(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}

 

这种特殊的优化――通过使用函数的 return 位置(或者在函数被调用位置用一个对象来替代)来消除局

部临时对象――是众所周知的和被普遍实现的。它甚至还有一个名字:返回值优化(return value optimization)(WQ 加注:在《深度探索 C++物件模型》中有更多更详细的讲述,它叫之为 named return value optimization。但注意,这种优化对普通的赋值运算无效,编译器不能够用拷贝构造函数取代赋值运算动作,最终结论是:在确保语意正确的前题下没有更好的优化可能了)。实际上这种优化有自己的名字本身就可以解释为什么它被广泛地使用。寻找 C++编译器的程序员会问销售商编译器是否有返回值优化功能。

条款21:通过重载避免隐式类型转换

条款22:考虑用运算符的赋值形式(op=)取代其单独形式(op)

条款23:考虑变更程序库

条款24:理解虚函数,多重继承,虚基类和RTTI所需的代价

http://www.cnblogs.com/xkfz007/archive/2012/08/19/2646356.html

条款25:将构造函数和非成员函数虚拟化

条款26:限制某个类所能产生的对象数量

条款27:要求或禁止在堆中产生对象

http://www.cnblogs.com/xkfz007/archive/2012/08/19/2646382.html

条款28:智能指针

条款29:引用计数

条款30:代理类

条款31:让函数根据一个以上的对象决定怎么虚拟

 

 

 

 

posted @ 2012-08-19 21:49  Mr.Rico  阅读(303)  评论(0编辑  收藏  举报