(原創) 若class中的data member有container,而且內含pointer時,也一定要big three!! (C/C++)

Abstract
當class的data member含pointer時,我們知道此時一定要big three(copy constructor,assignment operator,destructor),若是container內含pointer時呢?答案是也需big three。

Introduction
首先做個實驗,有一個vector為v1,內含Foo*,我們希望clone出一個vector v2,使用vector所提供的copy constructor進行clone。

 1#include <iostream>
 2#include <vector>
 3
 4using namespace std;
 5
 6class Foo {
 7private:
 8  int _value;
 9
10public:
11  Foo(int n = 0) : _value(n) {}
12  
13  int getValue() {
14    return _value;
15  }

16}
;
17
18int main() {
19  vector<Foo*> v1;
20  v1.push_back(new Foo(1));
21  v1.push_back(new Foo(2));
22  v1.push_back(new Foo(3));
23  
24  cout << "vector v1:" << endl;
25  for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter)
26    cout << (*iter)->getValue() << " ";
27    
28  vector<Foo*>v2(v1);
29  
30  cout << endl << "vector v2:" << endl;
31  for(vector<Foo*>::iterator iter = v2.begin(); iter != v2.end(); ++iter)
32    cout << (*iter)->getValue() << " ";
33    
34  cout << endl;
35}


執行結果

vector v1:
1 2 3
vector v2:
1 2 3


28行

vector<Foo*>v2(v1);


使用vector的copy constructor進行clone。

現在我們將v1內的pointer刪除,若v2能正常顯示,則表示v2 clone成功。

 1#include <iostream>
 2#include <vector>
 3
 4using namespace std;
 5
 6class Foo {
 7private:
 8  int _value;
 9
10public:
11  Foo(int n = 0) : _value(n) {}
12  
13  int getValue() {
14    return _value;
15  }

16}
;
17
18int main() {
19  vector<Foo*> v1;
20  v1.push_back(new Foo(1));
21  v1.push_back(new Foo(2));
22  v1.push_back(new Foo(3));
23  
24  cout << "vector v1:" << endl;
25  for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter)
26    cout << (*iter)->getValue() << " ";
27    
28  vector<Foo*>v2(v1);
29  
30  for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter)
31    delete *iter;
32  
33  cout << endl << "vector v2:" << endl;
34  for(vector<Foo*>::iterator iter = v2.begin(); iter != v2.end(); ++iter)
35    cout << (*iter)->getValue() << " ";
36    
37  cout << endl;
38}


執行結果

vector v1:
1 2 3
vector v2:    
-
572662307 -572662307 -572662307


30~31行

for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter)
    delete 
*iter;


只要將v1中的pointer delete,v2就無法顯示,證明vector的copy constructor並非真正的clone,只是將pointer copy過去,這樣會造成v1和v2中的pointer指的是同一個object,只要v1一delete的,v2也無法正常了。

正確的寫法如下

 1/* 
 2(C) OOMusou 2007 http://oomusou.cnblogs.com
 3
 4Filename    : ContainerWithPointer_Clone.cpp
 5Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
 6Description : Demo how to clone container with pointer
 7Release     : 05/18/2007 1.0
 8*/

 9#include <iostream>
10#include <vector>
11#include <algorithm>
12#include <functional>
13
14using namespace std;
15
16struct printElem {
17  template <typename T>
18  void operator()(T*& iter) const {
19    cout << iter->getValue() << " ";
20  }

21}
;
22
23class Foo {
24private:
25  int _value;
26
27public:
28  Foo(int n = 0) : _value(n) {}
29  
30  int getValue() {
31    return _value;
32  }

33}
;
34
35int main() {
36  vector<Foo*> v1;
37  v1.push_back(new Foo(1));
38  v1.push_back(new Foo(2));
39  v1.push_back(new Foo(3));
40  
41  cout << "vector v1:" << endl;
42  for_each(v1.begin(), v1.end(), printElem());
43  
44  vector<Foo*> v2;  
45  for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter) {
46    v2.push_back(new Foo((*iter)->getValue()));
47  }

48  
49  for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter)
50    delete *iter;
51  
52  cout << endl << "vector v2:" << endl;
53  for_each(v2.begin(), v2.end(), printElem());
54    
55  cout << endl;
56}


執行結果

vector v1:
1 2 3
vector v2:
1 2 3


44行

vector<Foo*> v2;  
  
for(vector<Foo*>::iterator iter = v1.begin(); iter != v1.end(); ++iter) {
    v2.push_back(
new Foo((*iter)->getValue()));
  }


唯有重新new過,這樣才叫做真正的clone。

以下介紹兩種方法,達成big three的要求。

1.方法一:使用一般big three的方式。

  1/* 
  2(C) OOMusou 2007 http://oomusou.cnblogs.com
  3
  4Filename    : ContainerWithPointer_BigThree.cpp
  5Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
  6Description : Demo how to use big three for pointer in container
  7Release     : 05/18/2007 1.0
  8*/

  9#include <iostream>
 10#include <vector>
 11#include <algorithm>
 12#include <functional>
 13
 14using namespace std;
 15
 16struct DeletePointer {
 17  template<typename T>
 18  void operator()(const T* ptr) const {
 19    if (ptr) {
 20      delete ptr;
 21      ptr = 0;
 22    }

 23  }

 24}
;
 25
 26class Foo {
 27private:
 28  int _value;
 29
 30public:
 31  Foo(int n = 0) : _value(n) {}
 32  
 33  int getValue() const {
 34    return _value;
 35  }

 36}
;
 37
 38class Moo {
 39private:
 40  vector<Foo*> _foos;
 41  
 42public
 43  Moo() {}
 44  // copy constructor
 45  Moo(Moo& otherMoo) {
 46    for(vector<Foo*>::iterator iter = otherMoo._foos.begin(); iter != otherMoo._foos.end(); ++iter) {
 47      _foos.push_back(new Foo((*iter)->getValue()));
 48    }

 49  }

 50  
 51  // Assignment operator
 52  Moo& operator=(Moo& otherMoo) {
 53    for(vector<Foo*>::iterator iter = otherMoo._foos.begin(); iter != otherMoo._foos.end(); ++iter) {
 54      _foos.push_back(new Foo((*iter)->getValue()));
 55    }

 56    
 57    return *this;
 58  }

 59  
 60  // Destructor
 61  ~Moo() {
 62    for_each(_foos.begin(), _foos.end(),DeletePointer());
 63  }

 64  
 65  void insert(Foo* foo) {
 66    _foos.push_back(foo);
 67  }

 68  
 69  void printElem() const {
 70    transform(_foos.begin(), _foos.end(), ostream_iterator<int>(cout, " "), mem_fun(&Foo::getValue));
 71    
 72    cout << endl;
 73  }

 74}
;
 75
 76int main() {
 77  Moo moo1;
 78  moo1.insert(new Foo(1));
 79  moo1.insert(new Foo(2));
 80  moo1.insert(new Foo(3));
 81  cout << "moo1:";
 82  moo1.printElem();
 83  
 84  // use copy constructor
 85  Moo moo2(moo1);
 86  Moo moo3 = moo1;
 87  
 88  // use assign operator
 89  Moo moo4;
 90  moo4 = moo1;
 91  
 92  // call moo1's destuctor to delete all pointer
 93  moo1.~Moo();
 94  
 95  cout << "moo2:";
 96  moo2.printElem();
 97  cout << "moo3:";
 98  moo3.printElem();
 99  cout << "moo4:";
100  moo4.printElem();
101}


執行結果

moo1:1 2 3
moo2:
1 2 3
moo3:
1 2 3
moo4:
1 2 3


16行

struct DeletePointer {
  template
<typename T>
  
void operator()(const T* ptr) const {
    
if (ptr) {
      delete ptr;
      ptr 
= 0;
    }

  }

}
;


是effective STL item 7所介紹刪除pointer的方法,花了二頁的篇幅介紹為什麼不用class template而用function object的function template。

44行

// copy constructor
Moo(Moo& otherMoo) {
  
for(vector<Foo*>::iterator iter = otherMoo._foos.begin(); iter != otherMoo._foos.end(); ++iter) {
  _foos.push_back(
new Foo((*iter)->getValue()));
  }

}


重新改寫copy constructor,避免compiler只是做pointer的copy,而非值的copy。

51行

// Assignment operator
Moo& operator=(Moo& otherMoo) {
  
for(vector<Foo*>::iterator iter = otherMoo._foos.begin(); iter != otherMoo._foos.end(); ++iter) {
    _foos.push_back(
new Foo((*iter)->getValue()));
  }

    
  
return *this;
}


重新改寫assign operator,避免compiler只是做pointer的assign,而非值的assign。

62行

for_each(_foos.begin(), _foos.end(),DeletePointer());


手動刪除所有pointer。

93行

// call moo1's destuctor to delete all pointer
moo1.~Moo();


呼叫了moo1的destructor,故意刪除了moo1中所有的pointer,以測試copy constructor和assignment operator是否正常。

方法二:另外加上clone() member function。

  1/* 
  2(C) OOMusou 2007 http://oomusou.cnblogs.com
  3
  4Filename    : ContainerWithPointer_BigThree2.cpp
  5Compiler    : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
  6Description : Demo how to use big three for pointer in container by clone()
  7Release     : 05/18/2007 1.0
  8*/

  9#include <iostream>
 10#include <vector>
 11#include <algorithm>
 12#include <functional>
 13
 14using namespace std;
 15
 16struct DeletePointer {
 17  template<typename T>
 18  void operator()(const T* ptr) const {
 19    if (ptr) {
 20      delete ptr;
 21      ptr = 0;
 22    }

 23  }

 24}
;
 25
 26class Foo {
 27private:
 28  int _value;
 29
 30public:
 31  Foo(int n = 0) : _value(n) {}
 32  
 33  virtual Foo* clone() const {
 34    return new Foo(*this);
 35  }

 36  
 37  int getValue() const {
 38    return _value;
 39  }

 40}
;
 41
 42class Moo {
 43private:
 44  vector<Foo*> _foos;
 45  
 46public
 47  Moo() {}
 48  // copy constructor
 49  Moo(Moo& otherMoo) {
 50    transform(otherMoo._foos.begin(), otherMoo._foos.end(), back_inserter(_foos), mem_fun(&Foo::clone));
 51  }

 52  
 53  // Assignment operator
 54  Moo& operator=(Moo& otherMoo) {
 55    transform(otherMoo._foos.begin(), otherMoo._foos.end(), back_inserter(_foos), mem_fun(&Foo::clone));
 56    
 57    return *this;
 58  }

 59  
 60  // Destructor
 61  ~Moo() {
 62    for_each(_foos.begin(), _foos.end(),DeletePointer());
 63  }

 64  
 65  void insert(Foo* foo) {
 66    _foos.push_back(foo);
 67  }

 68  
 69  void printElem() const {
 70    transform(_foos.begin(), _foos.end(), ostream_iterator<int>(cout, " "), mem_fun(&Foo::getValue));
 71    
 72    cout << endl;
 73  }

 74}
;
 75
 76int main() {
 77  Moo moo1;
 78  moo1.insert(new Foo(1));
 79  moo1.insert(new Foo(2));
 80  moo1.insert(new Foo(3));
 81  cout << "moo1:";
 82  moo1.printElem();
 83  
 84  // use copy constructor
 85  Moo moo2(moo1);
 86  Moo moo3 = moo1;
 87  
 88  // use assign operator
 89  Moo moo4;
 90  moo4 = moo1;
 91  
 92  // call moo1's destuctor to delete all pointer
 93  moo1.~Moo();
 94  
 95  cout << "moo2:";
 96  moo2.printElem();
 97  cout << "moo3:";
 98  moo3.printElem();
 99  cout << "moo4:";
100  moo4.printElem();
101}


執行結果

moo1:1 2 3
moo2:
1 2 3
moo3:
1 2 3
moo4:
1 2 3


33行

virtual Foo* clone() const {
  
return new Foo(*this);
}


自己加上一個clone() member function。

48行

Moo(Moo& otherMoo) {
  transform(otherMoo._foos.begin(), otherMoo._foos.end(), back_inserter(_foos), mem_fun(
&Foo::clone));
}


因為已經有了clone(),copy constructor變的非常精簡,只需用transform()複製vector即可,注意此處不能用copy(),因為copy()只能針對vector內的iterator做copy,而不能呼叫iterator的member function,所以在此可以發現trnasform()比copy()功能強大。

雖然使用clone()看似方便,程式非常精簡,但clone()有個缺點,就是得另外增加clone(),若使用component無法修改source code時,則無法達成。

若在inheritance hierarchy下,使用方法一將非常麻煩,方法二可獲得相當漂亮的解法。

Conclusion
當container內裝pointer時,也必須乖乖的big three,才能保證執行結果正確,我承認這是C++很麻煩的地方,但C++的style就是如此,class內不用pointer則已,一旦用了pointer就得big three。

See Also
(原創) 若class中data member的container,含的是polymorphism的pointer,該如何big three? (高級) (C++) (OO C++)

posted on 2007-05-18 22:28  真 OO无双  阅读(1118)  评论(0编辑  收藏  举报

导航