effective c++读书笔记(二)

Constructors, Destructors, Assignment operators

了解c++默默编写并调用哪些函数

一个empty class,编译器会为他声明一个copy constructor, copy assignment operator, 一个destructor,一个default constructor函数。所有这些函数都是public 且inline的。唯有当这些函数被需要(被调用),他们才会被编译器创建出来。除非base class自身声明为virtual否则,destructor是non-virtual的。
copy constructor 和 copy assignment operator,编译器创建的版本只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象。
如果class 的成员变量有自带的构造函数,在进行class copy constructor或constructor时调用成员变量的构造函数,没有构造函数的成员变量通过bitwise进行初始化。
如果class内的成员变量含有reference成员或者const成员。必须自己定义copy assignment operator。如果某个base class 将 copy assignment operator声明为private,编译器将拒绝为derived class生成一个copy assignment operator,因为编译器为derived class生成的copy assignment operator 想象中可以处理based class 成份

template<class T>
class NameObject{
public:
    NameObject(std::string &name, const T& value);
private:
    std::string &nameValue;
    const T objectValue;
};

std::string newDog("persephone");
std::string oldDog("satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 36);
p = s;  //false, p的成员变量发生了变化,不符合引用自身不可修改的要求。

如果不想编译器自动生成函数,就应该明确拒绝

当一些对象之间是不可以拷贝的,独一无二的。此时,应该阻止编译器为你生产copy assignment operator 和copy constructor函数。此时的实现技巧是:
1.可以将copy constructor或copy assignment operator 声明为private。

class HomeForSale{
public:
    //...
private:
    HomeForSale(const HomeForSale&);
    HomeForSale & operator=(const HomeForSale&); //declaration
};

2.为了阻止member函数或者friend函数的调用,上述方法不能很好的避免这些问题,此时可以采用private继承的方法来阻止对他们的调用。

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

class HomeForSale : private Uncopyable{
//....
};

当尝试拷贝的时候,编译器试着生成一个copy constructor 和一个copy assignment operator ,尝试调用其base class的对应兄弟,那些调用会被编译器拒绝,因为其base class 的拷贝函数是private。

为多态基类声明virtual析构函数

如果没有将base class 声明一个virtual析构函数,那么当derived class 对象经由一个base class 指针被删除的时候,其结果是未定义的。实际执行的时候,通常发生的是对象的derived成份没有被销毁,base class的成通常会被销毁。造成资源的泄漏。因为没有vptr,无法找到derived class 的析构函数,无法完成析构的动作。
1.任何class 只要带有virtual函数都几乎确定应该也有一个virtual析构函数,因为有virtual函数的时候base class 需要维护一个virtual table pointer。vptr指向一个由函数指针构成的数组,称为vtbl;每一个带有virtual函数的class都有一个相应的vbtl。当对象嗲用某一个virtual函数时,实际被调用的函数取决于该对象的vptr所指的那个vtbl,编译器在其中寻找合适的函数指针。
2.**如果你曾经企图继承一个标准容器或其他任何“带有non-virtual析构函数”的class,应该放弃这个操作。
3. 令class带有一个pure virtual析构函数,可能更便利。pure virtual导致abstract class 不能实例化。一般在定义个抽象class 的时候,必须为这个pure virtual析构函数提供一份定义;

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

AWOV::~AWOV() {} //pure virtual 析构函数的定义.

析构函数的运作方式是:最深层派生类的析构函数最先被调用,容纳后是其每个base class 的析构函数被调用。编译器会在AWOV的derived class的析构函数中创建一个对~AWOV() 的调用动作,所以必须提供一份定义。
给base class 一个virutal 析构函数,规则只适用于多态形式的base class身上。不是所有的基类的设计目的都是为了多态用途的。

别让异常逃离析构函数

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

class Widget{
public:
    ~Widget(){ //...}  //假设这里可能吐出一个异常
};

void doSomething(){
    std::vector<Widget> v;
    //...     v在这里被自动销毁
}

加入v中含有10个Widget对象,在第一个对象析构的时候发生了异常,第二个对象析构又发生了异常,如果程序不结束执行将会导致不明确行为。

//使用一个class 负责数据库连接
class DBConnection{
public:
    static DBConnection create();
    void close();
};

//利用一个class 来管理DBConnection的资源
class DBConn{
public:
    ~DBConn(){
        db.close();
    }
private:
    DBConnection db;
};

{
    DBConn dbc(DBConnection::create());
}
//如果调用导致异常,DBConn析构函数会传播异常,也就是运行它离开这个析构函数。会导致难以驾驭的麻烦。

解决方法:1. close抛出异常就结束程序,通常通过abort完成。调用abort可以抢先与不明确行为的发生。
2.吞下因调用close而发生的异常。

DBConn::~DBConn(){
    try{db.close();}
    catch(...){
        //记录对close调用失败记录
    }
}

一般而言,将异常吞掉是一个坏主意,压制了某些动作失败的重要信息。然而有时候吞下异常比负担“草率结束程序或不明确行为带来的风险好”
一个较佳的方案是重新设计DBConn接口,使其客户有机会对可能出现的问题做出反应。

绝不再构造和析构过程中调用virtual函数()

这样的调用不会带来你想要的结果。

class Transaction{
public:
    Transaction();
    virtual void logTransaction() const = 0;  //
};

Transaction::Transaction(){
    //...
    logTransaction();
}

class BuyTransaction: public Transaction{
public:
    virtual void logTransaction() const;
};
BuyTransaction b;

在BuyTransaction的构造函数调用前,首先调用Transaction的构造函数。Transaction构造函数最后一行调用的是virtual函数,他调用的是base class 的virtual 函数而不是BuyTransaction的虚函数。base class构造期间virtual函数不会下降到derived class阶层。即:base class 构造期间,virtual函数不是virtual函数.
如果此时的virtual函数下降到derived class阶层时,derived class的函数机会必然取用local成员变量,而那些成员变量尚未初始化,出现不明确行为
在derived class对象的base class构造期间,对象类型是base class,而不是derived class。编译器会把virtual函数,和运行期类型解析至base class上。

令operator返回一个reference to *this

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

x = (y = (z = 15));
class Widget{
public:
    Widget& operator=(const Widget &rhs){
        //...
        return *this;
    }
};

注意:这只是一个协议,并无强制性。

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

自我赋值的关键问题是,operator=函数内的*this和rhs可能是同一个对象。此时如果没有很好的处理自我赋值的问题,会导致销毁掉*this对象的同时也销毁了rhs的对象。
传统的做法:

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

这个版本具有“自我赋值安全性”,但是不具备“异常安全性”。一般让operator=具备“异常安全性”往往自动获得“自我赋值安全性”的回报。
一个好的方法是利用copy and swap技术来同时获得以上两个目的。

class Widget{
void swap(Widget& rhs);
};

Widget& Widget::operator=(const Widget& rhs){
    Widget temp(rhs);
    swap(temp);
    return *this;
}

复制对象时勿忘其每一个成份

当你编写一个copying函数时,请确保赋值所有local成员变量,调用所有base class内的适当copying函数。同时不应该令copy assignment operator调用 copy constructor函数。如果发现copy constructor和copy assignment operator有相似的代码,可以重新建立一个新的函数,让他们二者调用这个新的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值