码迷,mamicode.com
首页 > 编程语言 > 详细

【Effective C++ 笔记】构造/析构/赋值

时间:2015-03-13 22:29:29      阅读:187      评论:0      收藏:0      [点我收藏+]

标签:effective c++   c++   笔记   

     编译器的自动机能

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


     为驳回编译器自动提供的机能,可将成员函数声明为 private 并且不予实现。

     例如,如果你打算在一个内含 reference 成员或者 const 成员的 class 内支持赋值操作,必须自己定义 copy assignment 操作符,因为 reference 和 const 变量不可修改。

     另外,如果某个 base classes 将 copy assignment 操作符声明为 private, 编译器将拒绝为其 derived classes 生成一个 copy assignment 操作符。

class HomeForSale {
    public:
        ...;
    private:
        ...;
        HomeForSale(const HomeForSale&);  // 只有声明
        HomeForSale& operator=(const HomeForSale&);
};
      为了能在编译期就检测出所有的拷贝动作,例如来自 member 函数或 friend 函数,可以设计一个专门为了阻止 copying 动作而设计的 base class 内。例如下面的 Uncopyable:
class Uncopyable {
    protected:
        Uncopyable(){}
        ~Uncopyable(){}
    private:
        Uncopyable(const Uncopyable&);
        Uncopyable& operator=(const Uncopyable&);
};


class HomeForSale: private Uncopyable { //直接继承
    public:
        HomeForSale(int i){
            x = i;
            cout<<x<<endl;
        }
        ~HomeForSale(){
            cout<<x<<endl;
        }
    private:
        int x;
};

      为什么 Uncopyable 的函数声明为 protected, 继承关系为 private 呢?你可以联系下面进行思考。


      析构函数

      C++ 明确指出,当 derived class 对象经由一个 base class 指针被删除,而该 base class 带着一个 non-virtual 析构函数,其结果未有定义——实际执行时通常发生的是对象的 derived 成分没被销毁。

     解决方法:给 base class 一个 virtual 析构函数。


      对是否需要析构函数的判断:polymorphic(带多态性质的)base class 应该声明一个 virtual 析构函数。如果 class 带有任何 virtual 函数,都几乎确定应该也有一个 virtual 析构函数。而 Classes 的设计目的如果不是作为 base classes 使用,或不是为了具备多态行,就不该声明 virtual 析构函数。


      有时希望实现拥有抽象 class,但手上没有任何 pure virtual 函数, 可以为希望它成为抽象的那个 class 声明一个 pure virtual 析构函数。

class AWOV {
     public:
          virtual ~AWOV() = 0;
}


AWOV::AWOV(){}    // 必须提供一份定义,没有的话会出错的
      析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。如下面这个程序:
DBConn::~DBConn()
{
    try{ db.close(); }
    catch(...){
        //制作运转记录,记下对 close 的调用失败。
        std::abort(); //  结束程序
    }
}


DBConn::~DBConn()
{
    try{ db.close(); }
    catch(...){
        // 制作运转记录,记下对 close 的调用失败,吞下错误
    }
}
      但如果客户需要自己对某个操作函数运行期间的异常做出反应,那么 class 应该提供一个普通函数(而非在析构函数中)执行该操作。
class DBConn {
    public:
        ...;
        void close()  // 供客户使用的函数
        {
            db.close();
            closed = true;
        }
        ~DBConn()
        {
            if(!closed){
                try{  // 客户没有关闭的时候进行关闭
                    db.close();
                }
                catch(...) {
                    //制作运转记录,记下对 close 的调用失败
                    ...;
                }
            }
        }
    private:
        DBConnection db;
        bool closed;
}


     注意,在 base class 构造期间,virtual 函数不是 virtual 函数。所以在构造和析构函数期间不要调用 virtual 函数,这类调用从不下降至 derived class(比起当前执行构造函数和析构函数的那层)。

      如果想实现类似虚函数的功能,使构造期间每次都有适当版本的函数被调用,可以令 derived classes 将必要的构造信息向上传递至 base class 构造函数。

class Transaction {
    public:
        explicit Transaction(const std::string& logInfo);
        void logTransaction(const std::string& logInfo) const; //成为 non-virtual 函数
        ...;
};


Transaction::Transaction(const std::string& logInfo)
{
    ...;
    logTransaction(logInfo);  // non-virtual 调用
}


class BuyTransaction: public Transaction{
    public:
        BuyTransaction(parameters)
            : Transaction(createLogString(parameters))  // 将 log 信息传给 base class 构造函数
        {
            ...;
        }
    private:
        static std::string createLogString(parameters);
};
      上面的 createLogString 声明为 static 很重要,使得不可能意外指向“初期未成熟之 BuyTransaction 对象内尚未初始化的成员变量”。(在此想问一下大家,为什么基类中的 logTransaction 函数不需要声明为 static ?)

     Copying 函数

     赋值操作符必须返回一个 reference 指向操作符的左侧实参,这是一个应该遵循的协议,同样适用于所有赋值相关运算。如 +=、=。

     要在 operator= 中处理“自我赋值”。

     对此,传统方法是在 operator= 中进行“证同测试”以进行检验:

Widget& Widget::operator=(const Widget& rhs)
{
     if(this == &rhs) return *this;  // 证同测试


     delete pb;
     pb = new Bitmap(*rhs.pb);
     return *this;
}

     但以上可能存在异常安全性,若因分配内存不足等 new Bitmap 出现异常, Widget 会有一个指向被删除的 Bitmap 对象的指针。

     我们可以考虑通过让 operator= 具备“异常安全性”,自动获得“自我赋值安全”。而许多时候一群精心安排的语句就可以到处异常安全(以及自我赋值安全)的代码。

Widget& Widget::operator=(const Widget& rhs)
{
     Bitmap* pOrig = pb;   // 先保存原来的指针
     pb = new Bitmap(*rhs.pb); // 令 pb 指向 *pb 的一个副本
     delete pOrig;   // 删除原来的 pb
     return *this;
}
      所以,可以总结,确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。


      Copying 函数应该确保复制“对象内的所有成员变量”及“所有 base class 成分": 复制所有 local 成员变量;调用了所有 base classes 内的适当的 copying 函数


      在你为了消除重复代码时,不要尝试以某个 copying 函数实现另一个 copying 函数。应该将共同机能放进第三个函数中,并由两个 coping 函数共同调用,这样的函数往往是 private 而且常被命名为 Init。

【Effective C++ 笔记】构造/析构/赋值

标签:effective c++   c++   笔记   

原文地址:http://blog.csdn.net/jcjc918/article/details/44227761

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!