《Effective C++》学习笔记(2)

2017-03-29  本文已影响0人  暗夜望月

2 构造/析构/赋值运算

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

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


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

通常如果你不希望类支持某一特定技能,只要不说明对应函数就是了。但这个策略对拷贝构造函数和拷贝赋值操作符却不起作用。因为编译器会“自作多情”的声明它们,并在需要的时候调用它们。

由于编译器产生的函数都是public类型,因此可以将拷贝构造函数或拷贝赋值操作符声明为private。通过这个小“伎俩”可以阻止人们在外部调用它,但是类中的成员函数和友元函数还是可以调用private函数。解决方法可能是在一个专门为了阻止拷贝动作而设计的基类。(Boost提供的那个类名为noncopyable)。

class HomeForSale{
    public:
        ...
    private:
        HomeForSale(const HomeForSale&);
        HomeForSale& operator=(const HomeForSale&);
};
class Uncopyable{
    protected:
        Uncopyable();
        ~Uncopyable();
    private:
        Uncopyable(const Uncopyable&);
        Uncopyable& operator=(const Uncopyable&);
};
// 继承Uncopyable
class HomeForSale: private Uncopyable{
  ... ...
}

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


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

当基类的指针指向派生类的对象的时候,当我们使用完,对其调用delete的时候,其结果将是未有定义——基类成分通常会被销毁,而派生类的成分可能还留在堆里。这会形成资源泄漏,败坏之数据结构,在调试器上消费许多时间。

** note: **

  1. 带有多态性质的基类应该声明一个virtual析构函数。如果一个类带有任何virtual函数,它就应该拥有一个virtual析构函数。
  2. 一个类的设计目的不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。

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

C++并不禁止析构函数吐出异常,但它不鼓励你这样做。C++不喜欢析构函数吐出异常。

class DBConnection{
    public:
        static DBConnection create();
        void close();
};

class DBManager{
    public:
        ~DBManager(){
            db.close();  //析构函数关闭数据库连接
        }

    private:
        DBConnection db;
};

//调用析构函数时可能会发生异常
DBManager dbM(DBConnection::create());

如果析构函数调用的函数抛出异常:

DBManager::~DBManager(){
    try { db.close(); }
    catch(...){
        // 可以记录错误后退出程序
        std::abort();
    }
}
DBManager::~DBManager(){
    try { db.close(); }
    catch(...){
        //可以记录错误后退出程序
    }

但是,两者都无法对“导致close抛出异常”的情况做出反应。即使析构函数捕获到异常,客户也无法处理异常,客户需要对某个函数运行期间抛出的异常进行反应。因此,class应该提供一个普通函数来执行该操作。

class DBManager(){
    public:
        void close(){
            db.close();
            closed = true;
        }
        ~DBManager(){
            if (!closed){
                try { db.close(); }
                catch(...) {
                    //错误日志...
                }
            }
        }
    private:
        bool closed;
        DBConnection db;
};

这里面加了一个close函数,客户可以自己调用close函数,当发生异常时,进行异常处理,析构函数再重新关闭一次(如果这次仍然异常,就和上面的结果一样了)。如果客户没有调用close函数,则可以在析构函数中自动调用。所以,在写程序时,一定要将会发生异常的函数作为一个普通函数,这样可以提供更多的选择。

如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。

** note: **

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

上一篇下一篇

猜你喜欢

热点阅读