Effective C++学习笔记 Part2 构造/析构/赋值运算

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

一个空类会声明的函数:copy构造函数, copy assignment操作符、析构函数

如果没有声明构造函数:编译器声明默认的default 构造函数

编译器声明函数的目的:给编译器来放置调用基类的非静态变量的构造与析构函数(编译器产生的析构函数是non-virtual,除非该类的基类自身声明有virtual析构函数。)

不想使用编译器自动生成函数, 明确拒绝

如果不希望编译器自动生成函数,有以下方法:

  • 将其声明到private中并不定义, 缺点:member函数和friend函数依旧可以调用

  • 为阻止拷贝设计一个基类,在基类的private中声明拷贝函数

多态基类声明virtual析构函数

-带多态性质(polymorphic)的基类应该声明一个virtual的析构函数。如果任何class带有任何virtual函数,他应该拥有一个virtual析构函数。
原因:当derived class的对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,那么通常该成分的derived成分没有被销毁,可能造成资源泄露

任何类只要有virtual函数几乎都应该有一个virtual析构函数,只有类内至少含有一个virtual函数,才声明virtual析构函数

同理,如果一个类不包含基类,那么其析构函数也不应该是virtual的。

原因:一个virtual函数的实现,对象必须携带信息来决定运行期来调用拿个virtual函数,该信息由一个(vptr, virtual table pointer)指针指出,vptr指向一个由函数指针构成的数组,成为vbtl(virtual table),每一个带有virtual的类都有一个vbtl来查询决定适当的函数指针。

不分青红皂白的声明虚析构函数会降低代码的可移植性,因为vptr占存储空间。

当希望拥有抽象class, 没有纯虚函数
为希望成为抽象的那个类声明一个纯虚析构函数

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

必须为这个纯虚虚构函数提供一份定义:

AWOV::~AWOV() { }

不让异常逃离析构函数

析构函数吐出异常会导致程序过早结束或出现不明确行为,C++没有禁止析构函数抛出异常,但是不提倡。

解决办法
1.如果抛出异常就结束程序,调用abort

DBConn::~DBConn( )
{
   try { db.close(); }
   catch (...) {
      制作运转记录,记录close的调用失败;
      std::abort;
   }
}

2.吞下因调用出问题函数而产生的异常

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

3.重新设计DBConn接口,让客户有机会对可能出现的问题作出反应,这样异常的抛出就会在一个普通函数中执行此操作。

class DBConn {
public:
   ...
   void close( )
   {
      db.close();
      closed = true;
   }
   ~DBConn()
   {
      if(!closed) {
         try {
            db.close();
            }
            catch (...) {
            记录失败记录;
            }
      }
   }
private:
   DBConnection db;
   bool closed;
};

不在构造和析构过程中调用virtual函数

只是c++与java的不同之处
原因:基类会先于derived class之前被调用,导致调用的是基类里的构造函数,建立的对象就编程了一个隶属基类的类型,一旦虚的构造和析构函数开始执行,子类中的派生数据就被忽略。

确定构造函数和析构函数没有调用virtual函数

其它的解决办法
在基类中将对应的函数声明为非虚, 其后构造函数可以安全调用

class Transaction {
publicexplicit Transaction(const std::string& logInfo);
   void logTransaction(const std::string& logInfo) const;
   ...
};
Transaction::Transaction(const std::string& logInfo){
   ...
   logTransaction(logInfo);
}
class BuyTransaction: public Transaction {
public:
    BuyTransaction(parameters)
       : Transaction(creatLogString(parameters))
       {...}
       ...
private:
    static std::string createLogString(parameters);
};

operator=返回一个reference to *this

x = y = z = 15;
//等价于
x = (y = (z = 15));

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

class Widget{
public:
   ...
   Widget& operator=(const Widget& rhs)
   {
      ...
      return* this;
   }
}

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

对象可能自我赋值,例如:

a[i] = a[j] //如果i==j, 则自我复制

别名(aliasing):有一个以上的方法指代某对象。
假如尝试自行管理资源,可能“在停止使用资源之前释放了它”,可以加入“认同测试”(identity test)来解决这个问题

Widget& Widget::operator=(const Widget& rhs)
{
   if (this == &rhs) return *this;

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

具备异常安全性往往自动获得自我复制安全性。“许多时候一群精心安排的语句就可以导出异常安全”。例如,我们只需注意在复制pb所指东西之前别删除pb

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

更加聪明的解决办法:
‘认同测试会引入额外成本",可以使用“copy and swap”技术。

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

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

一个设计良好的对象系统回将对象的内部封装,只留两个函数负责对象拷贝(复制), 即copy构造函数和copy assignment操作符,也称之为copying函数。编译器会在必要时为我们的类创造copying函数,并说明这些编译器生成版的行为-将对象所有的成员变量都做一份拷贝

如果你为class添加一个成员变量, 你必须同时修改copying函数。如果忘记,编译器不太可能做出提醒。任何时候只要为“derived class”撰写copying函数, 就必须小心地复制其基类成分(往往是private)
举例

class Date {...}
class Customer {
public:
    ...
private:
    std::string name;
    Date lastTransaction;
};
class PriorityCustomer : public Customer{
public:
    ...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
    ...
private:
    int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
   : Customer(rhs), //调用基类的copy构造函数
     priority(rhs.priority)
{
     logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
     logCall("PriorityCustomer copy assignment operator");
     Customer::operator=(rhs); //对基类成分进行复制操作
     priority = rhs.priority;
     return *this;
     
}

编写一个copying函数的的注意事项:

  1. 复制所有local成员变量
  2. 调用所有基类内的适当的copying函数

不要尝试以某个copying函数实现另外一个copying函数, 应将共同机制放到第三个函数中,并由两个coping函数共同调用

不要用copy assignment操作符调用copy构造函数–试图构造一个已经存在的对象。
不要用copy构造函数调用copy assignment操作符:构造函数用来初始化新对象,而assignment操作符只施行于已初始化对象上。对一个尚未构造好的对象赋值没有意义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值