Effective C++笔记:2.构造/析构/赋值运算

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

class Empty{
public:
    Empty(){...}                              //默认构造函数
    Empty(const Empty& rhs){...}              //复制构造函数
    ~Empty(){...}                             //析构函数
    Empty& operator=(const Empty& rhs){...}   //复制赋值操作符
};

编译器产出的析构函数是non-virtual,除非这个类的基类自身声明有virtual析构函数

复制构造函数和复制赋值操作符,编译器只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象

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

阻止复制构造函数,将复制构造函数或复制赋值操作符声明为private,且没有定义

class HomeForSale{
public:
    ...
private:
    HomeForSale(const HomeForSale&)         //只有声明
    HomeForSale& operator=(const HomeForSale&)
};
  • 为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现

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

factory函数:返回一个基类指针,指向新生成的派生类对象

class TimeKeeper{
public:
    TimeKeeper();
    ~TimeKeeper();
};
class AtomicClock:public TimeKeeper{...};
class WaterClock:public TimeKeeper{...};
class WristClock:public TimeKeeper{...};

TimeKeeper* getTimeKeeper();  //返回一个指针,指向一个
                              //TimeKeeper派生类是动态分配对象

TimeKeeper* ptk=getTimeKeeper(); //从TimeKeeper继承体系获得一个动态分配对象
...
delete ptk;

问题:getTimeKeeper返回的指针指向一个派生类对象(如:AtomicClock),而那个对象经由一个基类指针删除,而目前基类有个non-virtual析构函数。

解决:给基类有个virtual析构函数

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

TimeKeeper* ptk=getTimeKeeper(); 
...
delete ptk;

virtual函数的目的是允许派生类的实现得以客制化

如果类不含virtual函数,通常表示它并不意图被用做一个基类。但类不企图当做基类,令其析构函数为virtual往往是个馊主意

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

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

C++不喜欢析构函数吐出异常!

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

class DBConn{
public:
    ...
    ~DBConn()
    {
        db.close();
    }
private:
    DBConnection db;
};

只要close调用成功,一切都美好。但如果调用导致异常,DBConn析构函数会传播异常,也就是允许它离开这个析构函数

有两个办法能避免这个问题:

(1)抛出异常就结束程序

DBConn::~DBConn()
{
    try{
        db.close();
    }
    catch(...){
        std::abort();  //通过调用abort完成
    }
}

(2)吞下异常

DBConn::~DBConn()
{
    try{
        db.close();
    }
    catch(...){   }
}

较佳的策略是重新设计接口,使其客户有机会对可能出现的问题作出反应。

class DBConn{
public:
    ...
    void close()
    {
        db.close();    //客户机使用的新函数
        closed=true;
    }
    ~DBConn()
    {
        if(!closed)
        {
            try{            //关闭连接
                db.close();
            }
            catch(...){
                ...
            }
        }
    }
private:
    DBConnection db;
    bool closed;
};
  • 析构函数绝对不要吐出异常。
  • 如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么类应该提供应该普通函数执行该操作。

条款09:绝不在构造和析构过程中调用virtual函数

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

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

class SellTransaction: public Transaction{
public:
    virtual void logTransaction() const;
    ...
};

派生类对象内的基类成分会在派生类自身成分被构造之前构造妥当。

基类构造期间virtual函数绝不会下降到派生类阶层。

在派生类对象的基类构造期间,对象的类型是基类而不是派生类。

不只virtual函数会被编译器解析至基类,若使用运行期类型信息(dynamic_cast、typeid),也会把对象视为基类类型。

确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数

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信息传给基类构造函数
    {...}
    ...
private:
    static std::string createLogString(parameters);
};
  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类

条款10:令operator=返回一个reference to *this

int x,y,z;
x=y=z=1;  //赋值连锁形式

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

class Widget{
public:
    ...
    Widget& operator=(const Widget& rhs)
    {
        ...
        return *this; //返回左侧对象
    }

    Widget& operator+=(const Widget& rhs)
    {
        ...
        return *this; //返回左侧对象
    }

    Widget& operator=(int rhs)
    {
        ...
        return *this; //返回左侧对象
    }
};
  • 令赋值操作符返回一个reference to *this

条款11:在operator=中处理“自我赋值”

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

Widget& Widget::operator=(const Widget& rhs) //不安全!!!!
{
    delete pb;               //停止使用当前的bitmap
    pb=new Bitmap(*rhs.pb);  //使用rhs的bitmap副本
    return *this;            
}

operator=函数内的*this和rhs有可能是同一对象

阻止错误的方式:认同测试

Widget& Widget::operator=(const Widget& rhs) 
{
    if(this==&rhs)
        return *this;       //认同测试:如果是自我赋值,就不做任何事

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

让operator=具备“异常安全性”往往自动获得“自我赋值安全”的回报

我们只需要注意在复制pb所指东西之前别删除pb:

Widget& Widget::operator=(const Widget& rhs) 
{
    Bitmap* pOrig=pb;          //记住原先的pb  
    pb=new Bitmap(*rhs.pb);    //令pb指向*pb的一个副本
    delete pOrig;              //删除原先的pb
    return *this;            
}

operator=内手工排列语句:使用copy and swap技术

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

Widget& Widget::operator=(const Widget& rhs) 
{
    Widget temp(rhs);  //为rhs制作一份副本
    swap(temp);        //将*this数据和上述附件的数据交换
    return *this;            
}
  • 确保当对象自我赋值时operator=有良好的行为

条款12:复制对象时勿忘其每一个成分

void logCall(const std::string& funcName);
class Customer{
public:
    ...
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
    ...
private:
    std::string name;
};

Customer::Customer(const Customer& rhs):name(rhs.name) //复制rhs的数据
{
    logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)    //复制rhs的数据
{
    logCall("Customer copy operator");
    name=rhs.name;
    return *this;
}
/
class PriorityCustomer:public Customer{  //继承类
public:
    ...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs)
    ...
private:
    int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs),priority(rhs.priority)             //调用基类的复制构造函数
{
    logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    logCall("PriorityCustomer copy operator");
    Customer::operator=(rhs);          //对基类成分进行赋值操作
    priority=rhs.priority;
    return *this;
}

编写一个复制构造函数时,确保:

(1)复制所有的局部成员变量

(2)调用所有基类内的适当的复制构造函数

  • 复制构造函数应该确保复制对象内的所有成员变量及所有基类成分
  • 不要尝试以某个复制构造函数实现另一个复制构造函数。应该将共同机能放进第三个函数中,并由两个复制构造函数共同调用

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值