Effective C++ 笔记 第二部分 构造/析构/赋值运算

来源:互联网 发布:认识数据库管理 编辑:程序博客网 时间:2024/06/11 09:23

5.了解C++默默编写并调用哪些函数(know what functions C++ silently writes and calls)


编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

编译器拒绝提供copy assignment操作符的2种情况:
1.有reference成员变量
2.有const成员变量
因为reference/const成员变量是不可赋值的,所以需要手动提供copy assignment操作符。


6.若不想使用编译器自动生成的函数,就改明确的拒绝(Explicitly disallow the use of compiler-generated functions you do not want)


为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。像使用Uncopyable这样的base class也是一种做法

拒绝编译器提供自动生成函数的两种方法
将要拒绝提供的函数声明为private并不予实现(因为friend和member函数可以调用private函数)。

class HomeForSale{private:    HomeForSale(const HomeForSale&);    HomeForSale& operator=(const HomeForSale&);};

使类继承自一个使用上述方法基类,使编译器无法提供要被拒绝的函数

class Uncopyable{private:    Uncopyable(const Uncopyable&);    Uncopyable& operator=(const Uncopyable&);};class HomeForSale: private Uncopyable{};

7.为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes.)


polymorphic(带有多态性质的)base class 应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

如果derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义。
解决方法是在base class中添加virtual析构函数。使得通过base class指针可调用derived class的析构函数。
同理我们的base class中如果有virtual function也应该声明virtual析构函数。因为derived class中vptr删除等操作应客制化。
Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。因为会安插vptr增大类的体积,丧失可移植性。


8.别让异常逃离析构函数(Prevent exceptions from leaving destructors)


析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。


9.绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction.)


在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)

若base class在构造期间调用virtual函数,则其derived class在构造期间调用的base class构造函数中调用的该virtual函数是base class版本的,因为在构造期间,base class部分先于derived class部分构造完成,当base class的构造函数调用virtual函数时,derived class尚未形成,所以调用的是base class版本的virtual函数。析构函数同理。

class Base{public:    Base(){        virFunc();    }    virtual void virFunc() const {        printf("base class virtual function\n");    }};class derived: public Base{public:    virtual void virFunc() const {        printf("derived class virtual function\n");    }};int main(int argc, const char * argv[]) {    derived d; //输出:base class virtual function    return 0;}

10.另operator= 返回一个reference to *this(Have assignment operators return a reference to *this)

返回一个reference to *this将支持连续赋值:例如a = b = c;

class A{public:    A(int val):val(val){}    A& operator=(const A& rhs){//返回reference to *this        this->val = rhs.val;        return *this;    }    int val;};int main(int argc, const char * argv[]) {    A a1(1);    A a2(2);    A a3(3);    a1 = a2 = a3;    std::cout<<"a1="<<a1.val<<" a2="<<a2.val<<" a3="<<a3.val<<std::endl;    return 0;}输出:a1=3 a2=3 a3=3

同理,不止operator=需要返回reference to *this,例如+=,-=等操作符也该返回reference to *this。


11.在operator=中处理”自我赋值”(Handle assignment to self in operator=)


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

自我赋值应考虑异常安全性和自我赋值安全性。如下版本的自我赋值是不安全的。

class Data{};class A{public:    A& operator=(const A& rhs){        delete data;//如果rhs与this是同一个对象,this将指向已删除对象。        data = new Data(*rhs.data);//如果此处发生了异常,this将指向已删除的对象。        return *this;    }private:    Data* data;};

解决方法是将this做一个副本,再将传入参数copy给this,在删除副本。

    A& operator=(const A& rhs){        Data* temp = data;        data = new Data(*rhs.data);//如果此处发生异常,this的data不会消失。即使this与rhs指向同一对象也不会发生错误。        delete temp;        return *this;    }

12.赋值对象时勿忘其每一个成分(Copy all parts of an object)


Copying函数应该确保赋值“对象内的所有成员变量”及”所有base class成分”
不要尝试以某个copying函数实现另一个copying函数。应该将共有机能放进第三个函数中,并由两个copying函数共同调用。
(不要以copy assignment调用copy constructor,反之亦然。但可以用copy constructor调用copy constructor,copy assignment同样)

自定义copying函数(包含copy构造函数和operator=和类似的函数)时要考虑每一个成员的操作,编译器不会产生警告信息或为你添加copy成员的语句。
在derived class中的自定义copying函数中要调用base class的对应copying函数(由编译器合成copying函数的会由编译器调用),因为编译器同样不会为你复制继承自base class的成员变量。写法应类似下边这样:

    Derived(const Derived& rhs)    :Base(rhs)    {        //do something;    }

用copy assignment调用copy 构造函数是不合理的,因为这就像试图构造一个已经存在的对象。用copy构造函数调用copy assignment也是无意义的,若两个copying函数有共同点应把共同之处写在第三个函数中,再由两个copying函数调用。

0 0
原创粉丝点击