effective c++学习笔记

 

将类的构造函数申明为explicit,可以阻止他们被用来执行隐式类型转换,但是任然可以被用来进行显式类型转换

class A
{
    public:
    explicit A(int n);
}


除非有一个很好的理由允许构造函数被用于隐式类型转换,否则都应该把他们申明为explicit

 

拷贝构造函数比用来“以同类型对象来初始化自我对象”
=语法也可以用来调用拷贝构造函数
拷贝构造很容易和拷贝赋值有所区别。如果有一个新的对象被定义,一定有个构造函数被调用,不可能调用赋值操作,如果没对象被定义,就不会有构造函数的调用,赋值操作被调用。

 

 

条款1:让自己习惯c++


c++的主要次语言
c:区块、语句、预处理器、内置数据类型、数组、指针等
object-oriented c++:类、封装、继承、多态等
template c++:泛型编程部分,template metaprogramming(模板元编程)
STL:非常特殊的一个template程序库

 

 

条款2:尽量以const enum incline替换#define


宁可以编译器替换处理器
define可能在预处理的时候就被预处理器移走了,因此编译器不能看到定义的值。
而用const定义的常量一定会被编译器看到,一旦报错就会知道是该变量出错

 

不能用define创建一个class专属变量,因为define没有作用域的概念
用static const代替

 

当用define实现宏的时候尽量用inline函数替代

template<typename T>
inline void max(const &T a, const &T b)
{
     f(a>b?a:b);//较大值调用f
}

 

 

条款3:尽可能使用const


如果const出现在*左边,表示被指物为常量
如果出现在*右边,表示指针自身是常量
如果出现在量表,表示两者都是常量

 

声明一个迭代器为const和声明一个指针为const一样,表示这个迭代器不得指向不同的东西,但他所指的值是可以改变的
希望迭代器所指的东西不可以被改动就需要一个const_iterator

 

在一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联
const成员函数的目的,是为了确认该成员函数可以作用域const对象身上。他们使class接口比较容易被理解。使操作const对象成为可能。
改善c++编程的最根本办法是以传递常引用的方式传递对象,而此技术可行的前提是我们有const成员函数可以用来处理取得的const对象。

 

两个函数如果只是常性不同,可以被重载
如果函数的返回值是一个内置类型,那么改变函数的返回值从来就是不合法的。


成员函数是const的时候
bitwise constness:成员函数不可以更改对象内的任何一个字节。
侦测违反点的方法:编译器只需要寻找成员变量的赋值动作即可。编译器强制执行
logical constness:const成员函数可以修改它所处理的对象内的某些bits,但是只有在客户端侦测不出的情况下才得如此。
可以使用另一个非常量的指针来修改原对象的内容,在编程时可以使用。

 

c++的一个与const相关的摆动场:mutable 释放掉non-static成员变量的bitwise constness约束
指明这些成员变量可能总是可以被更改,即使是在const成员函数内。

 

为了避免代码重复,可以使用non-const函数调用const版本的函数,但是不能反过来调用,因为const函数必须保证对象在函数内不被改变。

 

 

条款4:确定对象被使用之前已经被初始化


自定义类型初始化:总是使用初始化列表,有时候绝对必要,又往往比赋值更加高效。
当在初始化列表中列举各个成员时,最好总是以其声明次序为次序。

 

不同变异单元内定义的non-local static 对象的初始化次序
决定初始化次序相当困难,基本无解。
用设计来解决这个问题:将每个非本地对象搬到自己的专属函数内(该对象在此函数中被申明为static)。这些函数返回一个reference指向他所含的对象,然后用户调用这个函数而不是直接调用这些对象。非本地对象被本地对象替换了。设计模式中的单子模式。

 

 

///////////////////////////////////////////////////////////////////////////////////////////////

构造、析构、赋值运算

 

 

条款5:了解c++默默编写并调用了哪些函数


当c++处理过空类之后,会自动生成一个拷贝构造函数,一个拷贝赋值函数,一个析构函数,构造函数,所有这些都是public并且是inline的。
唯有当这些函数被调用时,他们才会被编译器创建出来。


default构造函数和析构函数主要是给编译器一个地方用来放置幕后的代码,像是调用基类和非静态成员变量的构造函数和析构函数。
编译器产生的析构函数不是虚函数,除非这个类的基类自身声明有虚析构函数。

 

拷贝构造函数和拷贝赋值函数,编译器创建的版本只是单纯的将来源对象的每一个非静态成员变量拷贝到目标对象。

 

 


条款6:若不想使用编译器自动生成的函数,就应该明确拒绝


所有编译器自动生成的函数都是public,如果要组织这些函数被创造出来,就将他们申明为private,阻止人们调用。
在private中只写函数申明,没有实现。
如果在成员函数或者友元函数中不慎拷贝对象,连接器就会报错。

 

 

条款7:为多态基类声明virtual析构函数


当继承类的对象经由一个基类指针被删除,而该基类带着一个非虚拟析构函数,其结果未有定义。实际执行时通常发生的是对象的继承部没有被销毁。
只有当class内至少含一个virtual函数,才为他声明virtual析构函数
这个规则只适用于带多态性质的基类身上,这种基类设计的目的就是为了用来通过基类接口处理派生类的对象。

 

 

 

条款8:别让异常逃离析构函数


c++并不禁止析构函数吐出异常,但是不鼓励这样做。
析构函数绝对不要吐出任何异常,应该捕捉所有异常,可以选择结束程序或者吞下异常。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数而不是在析构函数中执行该操作

 

 

条款9:绝不在构造和析构过程中调用virtual函数

 

在基类构造期间,虚函数不是虚函数,基类构造期间虚函数不会下降到派生类阶层。
同样道理适用于析构函数。一旦派生类的析构函数开始执行,对象内的派生类成员变量便呈现未定义值,仿佛就不存在,进入基类析构函数后对象就变成一个基类对象。
在构造期间,可以令派生类将必要的构造信息向上传递至基类构造函数替换。

 

 

 条款10:令operator=返回一个reference to *this


为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参。

widget &operator = (const widget& rhs)
{
 .......
 return *this;
}

同时也适用于所有赋值相关的操作。

 

 

条款11:在operator中处理“自我赋值”

 

“自我赋值”发生在对象被赋值给自己时
如果两个变量名碰巧指向同一个东西时,这也是自我赋值。这是“别名”带来的结果。
证同测试:

widget& widget::operator = (const widget& rhs)
{
    if(this == &rhs)
        return *this;
    delete pb;
    pb = new bitmap(*rhs.pb);
    return *this;
}

确保当对象自我赋值时operator=有良好行为。其中技术包括比较来源对象和目标对象的地址、精心周到的语句顺序、以及copy-and-swap
确定任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时,其行为仍然正确。

 


条款12:复制对象时勿忘其每一个成分


拷贝函数应该确保复制对象内的所有成员变量及所有基类成分
确保复制所有local成员变量以及调用所有基类内适当的拷贝函数
不应该让拷贝赋值函数调用拷贝构造函数,反过来也一样;如果两者之间包含同样的代码,应该创建第三个函数来让两者一起调用。

 

priorityCustomer:priorityCustomer(const priorityCustomer& rhs)
:customer(rhs), priority(rhs.priority)
{
    ....
}

priorityCustomer &
priorityCustomer::operator=(const priorityCustomer& rhs)
{
    customer::operator=(rhs);
    priority = rhs.priority;
    return *this;
}

 

 

 

posted @ 2012-08-17 15:58  w0w0  阅读(234)  评论(0编辑  收藏  举报