Effective C++之构造、析构、赋值运算

1.编译器自动创建函数
在C++的class中,会有一个或多个构造函数,一个析构函数,一个赋值运算符。
在class中,若你未编写它们,那么编译器也会自动为你声明一个default函数(public和inline的)
若只写:class Empty{ };
编译器替你做的工作:

class Empty{
public:
    Empty() { }                              //default构造函数
    Empty(const Empty& rhs) { }              //拷贝构造函数
    ~Empty() { }                             //析构函数

  Empty& operator=(const Empty& rhs) { }     //赋值运算符
};

2.拒绝编译器自动创建函数
若不想让编译器自动生成这几个函数,则应该自己动手去创建一个。
若想阻止这些函数被创建,最方便的一种做法是将拷贝构造函数或赋值运算符声明为private就可以阻止后序对它们的调用。即“将成员函数声明为private而且故意不去实现它们
还可以使用Uncopyable这样的base class的方法。在声明我们自己的类时继承Uncopyable类即可。
3.析构函数与virtual 析构函数
虚函数只有在调用时才会被解析,确定使用哪一版本。
用作基类的类的析构函数一般是虚函数
(1)若base class是带多态性质的,则应该声明一个virtual析构函数。
如果class带有任何的virtual函数,它就应该拥有一个virtual析构函数。
(2)class的设计目的如果不是作为base class使用,或者不是为了具备多态性,就不该声明virtual析构函数。
派生类中的虚函数
基类中的虚函数在派生类中隐含的也是一个虚函数。(形参类型严格一致)。
在具有多态的基类中使用虚析构函数的原因是:若为一个派生类的指针赋值一个基类的指针,然后将该派生类指针delete的话,如果没有虚析构函数,则不会执行派生类中的析构函数,会造成派生类中的资源泄漏
eg:

class ClBase{
public:
    ClBase() { };
    virtual ~ClBase() { };

    virtual void Dosomething() {cout <<  "Do Something in class ClBase! " << endl; };
};
class ClDerived : public ClBase{
public:
    ClDerived() { };
    ~ClDerived() { cout << "OutPut from the destructor of class ClDerived! " << endl;};

    void Dosomething() {cout << "Do Something in class ClDerived! " << endl; };
};

代码:
ClBase *pTest = new ClDerived;
pTest->Dosomething();
delete pTest;

输出结果是:

Do Something in class ClDerived!
OutPut from the destructor of class ClDerived!

如果把基类ClBase中析构函数的virtual去掉,输出结果就是:

Do Something in class ClDerived!

对比分析可以发现:若基类型的指针指向它的派生类的对象的话,后序执行还是执行的派生类中的Dosomething(),delete时也会首先调用派生类中的析构函数。
若删掉virtual后,再delete的话派生类中的析构函数就没法用到
所以析构函数设置为虚函数的作用就是:当用一个基类的指针删除一个派生类的对象时,派生类的析构函数也被调用。
但是并非所有的析构函数都要是虚函数,因为类里面有虚函数时,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间,所以尽量只有基类,才把析构函数定义为虚函数。
4.析构函数中的异常
尽量避免在析构函数中吐出异常,否则可能在调用析构函数释放一系列相同的类的资源时(如vector v),第一个类在析构时抛出异常之后,若继续对剩余的类析构,继续抛出第二个。
这时有两个同时作用的异常,这时,程序不是结束执行就是导致不明确行为
解决办法:
如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或者结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
eg:

class DBConnection{
public:
    ...
    static DBConnection create();

    void close();                     //关闭联机,失败则抛出异常。
};
//为确保客户不忘记在DBConnection对象身上调用close(),可以用一个类来管理DBConnection的资源。
class DBConn{
public:
    void close()               //供客户使用的新函数
    {
        db.close();
        closed = true;
    }
    ~DBConn()
    {
        if(!closed){                   //未关闭连接的话
            try{
                bd.close();
            }
            catch(...){               //如果关闭动作失败
                制作运转记录,记下对close的调用失败
                ...
            }
        }
    }
private:
    DBConnection db;
    bool closed;
};

5.不在构造函数和析构函数中调用虚函数
因为这类调用从不下降至其派生类中,即使是在派生类中调用。

6.令赋值运算符 operator=返回一个reference to *this

7.在operator=中处理 自我赋值问题

class Bitmap {  };
class Widget{
    ...
private:
    Bitmap* pb;  //指向一个从heap分配而得到的对象
};

确保的技术有:比较“来源对象”和“目标对象”的地址(证同测试)、精心周到的语句顺序、以及copy-and-swap。
(1).比较“来源对象”和“目标对象”的地址(证同测试)

Widget& Widget::operator=(const Widget& rhs)
{
    if(this == &rhs) return *this;//证同测试:如果是自我赋值,就不做任何事。    
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

(2).精心周到的语句顺序:在复制pb所指的东西之前别删除pb

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);  //令pb指向*pb的一个复件
    delete pOrig;              //删除原来的pb
    return *this;
}

(3).copy-and-swap

class Bitmap {  };
class Widget{
    ...
    void swap(Widget &rhs);  //交换*this和rhs的数据

private:
    Bitmap* pb;  //指向一个从heap分配而得到的对象
};
Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);  //为rhs数据制作一份复件
    swap(temp);        //将*this数据和上述复件的数据交换
    return *this;
}

8.复制对象时,勿忘其每一个成分
copying函数一般有两个:1、拷贝构造函数,2、赋值运算符

case1:复制了所有的local成员变量
自己定义了copying函数,在后面如果新加入成员变量之后,先前的copying函数执行的都是局部拷贝,但是对于新加入的变量却没有复制,这时候就要自己重新修改所有的copying函数,以及任何非标准形式的operator=函数。

case2:调用所有base class内的适当的copying函数
在继承类中,若直接调用copying函数的话,只是对子类中声明的变量,但是继承来的基类中的成员变量都没有复制过来。
这时,必须也复制基类中的成分,由于那些成分一般是private,所以无法直接访问,应该让子类的copying函数也调用相应的基类函数。

PriorityCustomer::PriorityCustomer(const Priority& rhs)
:Customer(rhs),
 priority(rhs.priority)
{
    logCall("PriorityCustomer copy constructor");
};
PriorityCustomer& PriorityCustomer::operator=(const Priority& rhs)
{
    logCall("PriorityCustomer copy assignment operator");
    Customer::operator=(rhs);
    priority = rhs.priority;
    return *this;
};

注意:
这两个copying函数往往有近似的实现本体,但是:
1、不应该在赋值运算符中调用拷贝构造函数,
2、同样的,令拷贝构造函数调用赋值运算符也是无意义的。
所以,若发现拷贝构造函数和赋值运算符有相似的代码,消除重复代码的方法:建立一个新的成员函数给两者调用。(一般为private,且命名为init)。

你可能感兴趣的:(C++,Effective,C++)