读书笔记 Effective C++: 02 构造析构赋值运算

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

编译器会为class创建:

1. default构造函数(前提是:没有定义任何构造函数);

  如果已经声明了一个构造函数,编译器就不会再创建default构造函数了;

2. 析构函数

3. copy构造函数;

  对于指针,只拷贝地址,并不会重建内容,所以要注意double free;

  下面是一段错误的代码:

class TestDoubleFree

{
public:
  explicit TestDoubleFree(char c)
    : pTest(new char(c))
  {
  }

  ~TestDoubleFree( )
  {
    if(pTest != NULL) {
      delete pTest;
      pTest = NULL;
    }
  }
private:
  char* pTest;
};

int main( )
{
  TestDoubleFree t1('x');  //这句本身并没有错
  TestDoubleFree t2(t1);  //但是这句错了,会引起double free
}

这是一段错误的代码,运行时会发生double free。main函数中,如果单独使用TestDoubleFree t1('x'),这并不会有问题。

问题在于TestDoubleFree t2(t1),copy构造函数会让t1.pTest和t2.pTest同时指向一块内存单元,那么t1,t2并析构的时候,就会对该内存单元发生double free。

解决这个问题:

1. 可以继承boost::noncopyable,这样就不允许使用TestDoubleFree t2(t1)语句。(例子见条款06)

2. 可以用boost::share_ptr替代裸指针;

#include <boost/shared_ptr.hpp>

class TestDoubleFree
{
public:
  explicit TestDoubleFree(char c)
    : pTest(new char(c))
  {
  }

  ~TestDoubleFree( )
  {
  }
private:
  boost::shared_ptr<char> pTest;
};

int main( )
{
  TestDoubleFree t1('x');
  TestDoubleFree t2(t1);
}

4. copy assignment操作符;

  copy构造函数一定会生成,但是copy assignment赋值符并不一定能默认生成。

  如果class中含有reference成员变量,const成员变量,那么赋值运算无法完成;

#include <cassert>

class TestNonCopyAssignment
{
public:
  TestNonCopyAssignment(char c, int i)
  : ch(c), cInt(i)
  {
  }
  void setChar(char c)
  {
    ch = c;
  }
  void testAssert(char c, int i)
  {
    assert(ch==c);
    assert(cInt==i);
  }
private:
  char& ch;
  const int cInt;
};

int main( )
{
  TestNonCopyAssignment t1('x', 123);
  TestNonCopyAssignment t2(t1);
  t1.setChar('z');
  t1.testAssert('z', 123);
  t2.testAssert('z', 123);
  t2 = t1;  //赋值符号错误
}

由于TestNonCopyAssignment类含有reference和const,operator=被禁用,如果强行赋值,编译器会报错:

错误: 使用了被删除的函数‘TestDoubleFree& TestDoubleFree::operator=(const TestDoubleFree&)’
错误: ‘TestDoubleFree& TestDoubleFree::operator=(const TestDoubleFree&)’ is implicitly deleted because the default definition would be ill-formed:
错误: non-static reference member ‘char& TestDoubleFree::pTest’, can’t use default assignment operator
错误: non-static const member ‘const int TestDoubleFree::count’, can’t use default assignment operator

 

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

方法1:

  在private中声明而不定义:copy构造函数,copy assignment操作符;

  缺点:friend函数或者成员函数调用,这个错误不是出现再编译期,而是link期;

方法2:

  继承boost::noncopyable类;

  缺点:多重继承可能会阻止empty base class optimization

  优点:醒目地标识该类不能被copy

  实现条款05的例子:

#include <boost/noncopyable.hpp>

class TestDoubleFree : private boost::noncopyable
{
public:
  explicit TestDoubleFree(char c)
    : pTest(new char(c))
  {
  }

  ~TestDoubleFree( )
  {
    if(pTest != NULL) {
      delete pTest;
      pTest = NULL;
    }
  }
private:
  char* pTest;
};

int main( )
{
  TestDoubleFree t1('x');
  TestDoubleFree t2(t1); //由于继承boost::noncopyable, 所以编译不会通过
}

 

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

1. 具有polymorphic(多态)的base class,或者带有任何virtual函数的class,应该声明virtual析构函数;

  避免局部析构;

2. 如果class设计的目的就不是作base class使用,或者不是为了具备多态,就不应该声明virtual析构函数。

  节省存储空间;

3. 有时候为了得到一个抽象类,以避免该类被实例化,就把析构函数定义为pure virtual,但是必须为这个pure virtual析构函数提供一份定义,要不然link会出错的。

#include <iostream>

class Base
{
public:
  virtual void print( )
  {
    std::cout << "Base Classes." << std::endl;
  }
  virtual ~Base( )=0;
};

// 析构函数先声明成pure virtual再定义
// 1. 如果不在外面定义,link会出错的
// 2. 这是一个小技巧,能阻止Base被实例化
Base::~Base( )
{
}

class DerivedA : public Base
{
};

class DerivedB : public Base
{
public:
  void print( )
  {
    std::cout << "Derived Classes." << std::endl;
  }
};

int main( )
{

  Base* p = new DerivedA( );
  p->print( );
  delete p;
  p = NULL;

  p = new DerivedB( );
  p->print( );
  //由于p是Base*,所以如果Base的析构函数不是virtual的,就可能会导致Base的那部分析构了而Derived的那部分没能被析构
  delete p;
  p = NULL;

}

 

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

1. 析构函数绝对不要吐出异常。

  如果某个语句在运行期间可能抛出异常,class应该提供普通函数执行该操作,而且该函数需要在析构函数之前被用户手动调用,而析构函数中包含这个普通函数只是起到双重保险的作用。

class DBConn

{

public:

  //提供给客户使用,异常由客户处理

  //这里谈论的是db.close()吐出异常的处理,不是db.close()关闭失败的处理,关闭失败是db.close()程序本身的错误

  void close(){

    db.close();

    closed = true;

  }

 

  ~DBConn(){

    //如果客户没有处理,这里将代为处理,只是起到双保险的作用;但是原则上,是建议客户程序员处理。

    if(!closed){

      try{

        db.close();

      }catch(...){

      // 默认的处理一般是直接结束程序,以避免错误传播

      }

    }

  }

private:

  DB db;

  bool closed;

};

 

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

  如果class中含有virtual函数,在构造函数中调用该函数,而此时派生类还没能创建,调用virtual函数不能正确指向派生类的函数。

  析构函数也一样,派生类先析构,基类的析构函数调用virtual函数,此时vpt指向的函数已经析构了。

#include <iostream>

class Base
{
public:
  Base( )
  {

    // 此时Derived Class还没有构建出来,所以调用的是Base Classes的print函数
    print("Constructor");
  }
  virtual ~Base( )
  {

    // 此时Derived Class已经析构,所以调用的是Base Classes的print函数
    print("Destructor");
  }
  virtual void print(std::string str)
  {
    std::cout << "Base Classes: " << str << std::endl;
  }
};

class Derived : public Base
{
public:
  Derived( )
  {
    print("Constructor");
  }
  ~Derived( )
  {
    print("Destructor");
  }
  void print(std::string str)
  {
    std::cout << "Derived Classes: " << str << std::endl;
  }
};

int main( )
{
  Derived dd;
}

运行结果:

Base Classes: Constructor
Derived Classes: Constructor
Derived Classes: Destructor
Base Classes: Destructor

 

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

  class Widget{

  public:

    // 也适用于+=,-=, *=

    Widget& operator=(const Widget&){

      ......

      return *this;

    }

  };

  这样做的好处是可以连续赋值;

  如:x = y = z = 15;

  会被正确的解析为:x = (y = (z = 15));

 

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

版本1:

Widget& Widget::operator=(const Widget& rhs){

  if(this == &rhs)return *this;

  delete pb;

  try{

    pb = new Bitmap(*rhs.pb);

  }catch(...){

  }

  return *this;

}

版本1的缺点是不具备异常安全性:如果new失败了(内存不足或者Bitmap的copy构造函数出现异常),而旧的pb又已经delete了,这个对象就成了一颗地雷(一踩就崩;即使不踩,析构它也可能会因为delete pb而崩;就算使用pb之前判断pb!=NULL,程序也会进入不稳定的状态)。

版本2:

Widget& Widget::operator=(const Widget& rhs){

  if(this == &rhs)return *this;  //可加可不加,需要权衡。加了,如果自我赋值少,影响效率;不加,如果自我赋值多,由于后面要重建,也会影响效率。

  Bitmap* pOrig = pb;

    try{

      pb = new Bitmap(*rhs.pb);

      delete pOrig;

    }catch(...){

      throw "赋值失败了";

    }

  return *this;

}

版本2可以解决异常安全性,如果new失败了,此时pb依然指向原来的对象,这样程序还是可以继续运行的,只是进入赋值失败的异常分支,而不是像版本1那样只能退出了。

版本3:

class Widget{

  void swap(Widget& rhs);

  Widget& Widget::operator=(const Widget& rhs){

    Widget tmp(rhs);

    swap(tmp);

    return *this;

  }

};

使用copy and swap技术,能有效处理异常安全性。

版本4:

把版本3的operator=改为value传递

Widget& Widget::operator=(Widget rhs){

  swap(rhs);

  return *this;

}

版本4是版本3的简化版,而且可能会让编译器生成更高效的代码,但是牺牲了清晰性。

 

完整代码:

#include <iostream>
#include <exception>

class BitMap
{
public:
  explicit BitMap(int ibm)
    : ibm(ibm)
  {
  }
  BitMap(const BitMap& bm)
    : ibm(bm.ibm)
  {
    throw std::string("Test Exception");
  }
  int get( )
  {
    return ibm;
  }
private:
  int ibm;
};

class Widget
{
public:
  explicit Widget(int ibm)
    : pb(new BitMap(ibm))
  {
  }
  ~ Widget( )
  {
    delete pb;
  }

  Widget(const Widget& wg)
    : pb(new BitMap(*wg.pb))
  {
  }

  Widget& operator+=(const Widget& wg)
  {
    Widget tmp(wg);
    std::swap(tmp.pb, pb);
    return *this;
  }

  Widget& operator-=(Widget wg)
  {
    std::swap(wg.pb, pb);
    return *this;
  }

  Widget& operator*=(const Widget& wg)
  {
    if(this == &wg) return *this;
    BitMap* pOrig = pb;
    pb = new BitMap(*wg.pb);
    delete pOrig;
    return *this;
  }

  // 错误的代码
  Widget& operator/=(const Widget& wg)
  {
    if(this == &wg) return *this;
    delete pb;
    pb = new BitMap(*wg.pb);
    return *this;
  }

  void print( )
  {
    std::cout << pb->get( ) << std::endl;
  }

private:
  BitMap* pb;
};

int main( )
{
  Widget wg1(1);
  Widget wg2(2);
  wg1.print( );
  wg2.print( );

  try {
    wg1 += wg2;
  } catch(std::string& str) {
    //赋值失败,但是原来的值没有修改
    std::cout << str << std::endl;
    wg1.print( );
    wg2.print( );
  }

  try {
    wg1 -= wg2;
  } catch(std::string& str) {
    //赋值失败,但是原来的值没有修改
    std::cout << str << std::endl;
    wg1.print( );
    wg2.print( );
  }

  try {
    wg1 *= wg2;
  } catch(std::string& str) {
    //赋值失败,但是原来的值没有修改
    std::cout << str << std::endl;
    wg1.print( );
    wg2.print( );
  }

  try {
    wg1 /= wg2;
  } catch(std::string& str) {
    //赋值失败,原来的值却成了个地雷
    //不仅状态不稳定,而且会造成内存泄漏
    std::cout << str << std::endl;
    wg1.print( );
    wg2.print( );
  }
}

 

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

1. 不要copy构造函数和copy assignment函数应该确保copy“对象中的所有成员变量”,以及base classes成分

2. 不要用copy构造函数去实现copy assignment函数,也不要用copy assignment函数去实现copy构造函数;

    copy构造函数是,创建并初始化一个新的对象,copy assignment函数是,在已初始化的对象上做处理;

    如果两者有相同的代码,可以抽出来定义private函数;

class PriorityCustomer : public Customer{

public:

  PriorityCustomer(const PriorityCustomer& rhs) : Custormer(rhs), priority(rhs.priority){

  }

  PriorityCustomer& operator=(const PriorityCustomer& rhs){

    Customer::operator=(rhs);

    priority = rhs.priority;

    return *this;

  }

private:

  int priority;

};

例: 

#include <iostream>
#include <sstream>
#include <cassert>

class Base
{
public:
  Base(int a, int b, int c)
    : a(a), b(b), c(c)
  {
  }
protected:
  int toInt( )
  {
    int tmp;
    std::stringstream ss;
    ss << a << b << c;
    ss >> tmp;
    return tmp;
  }
private:
  int a;
  int b;
  int c;
};

class Derived : public Base
{
public:
  Derived( )
    : Base(1, 1, 1), d(1), e(1)
  {
  }
  Derived(int a, int b, int c, int d, int e)
    : Base(a, b, c), d(d), e(e)
  {
  }
  Derived(const Derived& derived)
    : Base(derived), d(derived.d), e(derived.e)
  {
  }
  Derived& operator=(const Derived& derived)
  {
    Base::operator=(derived);
    d = derived.d;
    e = derived.e;
    return *this;
  }
  int toInt( )
  {
    int tmp;
    std::stringstream ss;
    ss << Base::toInt( ) << d << e;
    ss >> tmp;
    return tmp;
  }
private:
  int d;
  int e;
};

int main( )
{
  Derived dd(1, 2, 3, 4, 5);
  assert(dd.toInt( ) ==12345);
  Derived ee(dd);
  assert(ee.toInt( ) ==12345);
  Derived ff;
  assert(ff.toInt( ) ==11111);
  ff = ee;
  assert(ff.toInt( ) ==12345);
}

转载于:https://www.cnblogs.com/leagem/archive/2013/03/20/2972200.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值