日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

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

發布時間:2024/6/14 c/c++ 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 读书笔记 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

總結

以上是生活随笔為你收集整理的读书笔记 Effective C++: 02 构造析构赋值运算的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。