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

Abstract
C++中一旦用到pointer,就得自己管理memory,若功力不夠,不是當機就是memory leak,所以能避免就避免,不過若要在container中放polymorphism object,就只能使用pointer,此時該如何big three呢?

Introduction
(原創) 若class中的data member有container,而且內含pointer時,也一定要big three!!(C++) 中已經討論了container中放pointer中該如何處理,但別忘了我們為什麼會在container中放pointer呢?就是為了polymorphism,所以一定會遇到一個inheritance hierarchy,而非之前討論的只有一層class。

我們知道處理big three中,主要就是要在copy constructor和assignment operator中重新new過,而非只是copy pointer,但因為container中放的是polymorphism object,我該怎麼針對適當的type去new呢?

我的第一個想法,就是用RTTI去判斷該polymorphism object的type為何,然後去new。

  1#include <iostream>
  2#include <vector>
  3#include <algorithm>
  4#include <functional>
  5
  6using namespace std;
  7
  8struct DeletePointer {
  9  template<typename T>
 10  void operator()(const T* ptr) const {
 11    if (ptr) {
 12      delete ptr;
 13      ptr = 0;
 14    }

 15  }

 16}
;
 17
 18class Base {
 19protected:
 20  int _val;
 21  
 22public:
 23  Base(int val = 0) : _val(val) {}
 24  
 25  int getValue() {
 26    return _val;
 27  }

 28  
 29  virtual void print() const {
 30    cout << "Base's print:" << _val << endl;
 31  }

 32}
;
 33
 34class Derived1 : public Base {
 35public:
 36  Derived1(int val = 0) : Base(val) {}
 37  
 38  virtual void print() const {
 39    cout << "Derived1's print:" << _val << endl;
 40  }

 41}
;
 42
 43class Derived2 : public Base {
 44public:
 45  Derived2(int val = 0) : Base(val) {}
 46  
 47  virtual void print() const {
 48    cout << "Derived2's print:" << _val << endl;
 49  }

 50}
;
 51
 52class Foo {
 53protected:
 54  vector<Base*> _bases;
 55  
 56public:
 57  Foo() {}
 58  Foo(Foo& otherFoo) {
 59    for(vector<Base*>::const_iterator iter =  otherFoo._bases.begin(); iter != otherFoo._bases.end(); ++iter) {
 60      if (dynamic_cast<Derived1*>(*iter))
 61        _bases.push_back(new Derived1((*iter)->getValue()));  
 62      else if (dynamic_cast<Derived2*>(*iter))
 63        _bases.push_back(new Derived2((*iter)->getValue()));
 64      else 
 65        _bases.push_back(new Base((*iter)->getValue()));
 66    }

 67  }

 68  
 69  Foo& operator=(Foo& otherFoo) {
 70    for(vector<Base*>::const_iterator iter =  otherFoo._bases.begin(); iter != otherFoo._bases.end(); ++iter) {
 71      if (dynamic_cast<Derived1*>(*iter))
 72        _bases.push_back(new Derived1((*iter)->getValue()));  
 73      else if (dynamic_cast<Derived2*>(*iter))
 74        _bases.push_back(new Derived2((*iter)->getValue()));
 75      else 
 76        _bases.push_back(new Base((*iter)->getValue()));
 77    }

 78    
 79    return *this;
 80  }

 81  
 82  ~Foo() {
 83    for_each(_bases.begin(), _bases.end(), DeletePointer());
 84  }

 85  
 86  void insert(Base* base{
 87    _bases.push_back(base);
 88  }

 89  
 90  void printElem() const {
 91    for_each(_bases.begin(), _bases.end(), mem_fun(&Base::print));
 92  }

 93}
;
 94
 95int main() {
 96  Foo foo;
 97  foo.insert(new Base(1));
 98  foo.insert(new Derived1(2));
 99  foo.insert(new Derived2(3));
100  
101  // call copy constructor
102  Foo foo2 = foo;
103  
104  // call assignment operator
105  Foo foo3;
106  foo3 = foo;
107  
108  // call foo's destructor to delete pointer
109  foo.~Foo();
110  
111  foo2.printElem();
112  foo3.printElem();
113}


執行結果

Base's print:1
Derived1's print:
2
Derived2's print:
3
Base's print:
1
Derived1's print:
2
Derived2's print:
3


執行結果如預期,但copy contructor和assignment operator非常的醜

Foo(Foo& otherFoo) {
  
for(vector<Base*>::const_iterator iter =  otherFoo._bases.begin(); iter != otherFoo._bases.end(); ++iter) {
  
if (dynamic_cast<Derived1*>(*iter))
    _bases.push_back(
new Derived1((*iter)->getValue()));  
  
else if (dynamic_cast<Derived2*>(*iter))
    _bases.push_back(
new Derived2((*iter)->getValue()));
  
else 
    _bases.push_back(
new Base((*iter)->getValue()));
  }

}


由於使用了RTTI,須不斷的去判斷type,這種寫法明顯的違反了OCP,若有新的type,就得改copy constructor和assignment operator的判斷式,算是很差的寫法。

再苦思許久之後,大陸的袁老弟給我一個建議,使用clone(),在Modern C++ Design 8.7節有個範例就是用這種方式,我試了之後,結果非常令我滿意。

Sample Code

  1/* 
  2(C) OOMusou 2007 http://oomusou.cnblogs.com
  3
  4Filename    : ContainerHierarchyWithPointer_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() in class hierarchy
  7Release     : 03/16/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 Base {
 27protected:
 28  int _val;
 29  
 30public:
 31  Base(int val = 0) : _val(val) {}
 32  
 33  int getValue() {
 34    return _val;
 35  }

 36  
 37  virtual Base* clone() const {
 38    return new Base(*this);
 39  }

 40  
 41  virtual void print() const {
 42    cout << "Base's print:" << _val << endl;
 43  }

 44}
;
 45
 46class Derived1 : public Base {
 47public:
 48  Derived1(int val = 0) : Base(val) {}
 49  
 50  virtual Derived1* clone() const {
 51    return new Derived1(*this);
 52  }

 53  
 54  virtual void print() const {
 55    cout << "Derived1's print:" << _val << endl;
 56  }

 57}
;
 58
 59class Derived2 : public Base {
 60public:
 61  Derived2(int val = 0) : Base(val) {}
 62
 63  virtual Derived2* clone() const {
 64    return new Derived2(*this);
 65  }

 66    
 67  virtual void print() const {
 68    cout << "Derived2's print:" << _val << endl;
 69  }

 70}
;
 71
 72class Foo {
 73protected:
 74  vector<Base*> _bases;
 75  
 76public:
 77  Foo() {}
 78  
 79  Foo(Foo& otherFoo) {
 80    transform(otherFoo._bases.begin(), otherFoo._bases.end(), back_inserter(_bases), mem_fun(&Base::clone));
 81  }

 82  
 83  Foo& operator=(Foo& otherFoo) {
 84    transform(otherFoo._bases.begin(), otherFoo._bases.end(), back_inserter(_bases), mem_fun(&Base::clone));
 85    
 86    return *this;
 87  }

 88  
 89  ~Foo() {
 90    for_each(_bases.begin(), _bases.end(), DeletePointer());
 91  }

 92  
 93  void insert(Base* base{
 94    _bases.push_back(base);
 95  }

 96  
 97  void printElem() const {
 98    for_each(_bases.begin(), _bases.end(), mem_fun(&Base::print));
 99  }

100}
;
101
102int main() {
103  Foo foo;
104  foo.insert(new Base(1));
105  foo.insert(new Derived1(2));
106  foo.insert(new Derived2(3));
107  
108  // call copy constructor
109  Foo foo2 = foo;
110  
111  // call assignment operator
112  Foo foo3;
113  foo3 = foo;
114  
115  // call foo's destructor to delete pointer
116  foo.~Foo();
117  
118  foo2.printElem();
119  foo3.printElem();
120}


執行結果

Base's print:1
Derived1's print:
2
Derived2's print:
3
Base's print:
1
Derived1's print:
2
Derived2's print:
3


37行

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


在base class多定義了clone member function,專門負責做clone。

50行

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


重點來了,derived class override了clone(),但return type變成Derived1*,而不是Base*,這和我們一般polymorphism寫法不一樣,Modern C++ Design 8.7節稱此為covariant return type,讓你override virtual時,可以不使用Base *,利用這個特色,我們就能clone出完整的object。

79行

Foo(Foo& otherFoo) {
  transform(otherFoo._bases.begin(), otherFoo._bases.end(), back_inserter(_bases), mem_fun(
&Base::clone));
}


copy constructor就變的非常的簡單,而且mem_fun(&Base::clone)還支援polymorphism,相當漂亮,程式只要一行就解決,而且符合OCP,若未來有增加type,也不用再修改程式。

Conclusion
Modern C++ Design 8.7節指出這種使用clone的方式有兩個缺點:
1.必須在base class增加virtual clone(),若使用的是component而無法修改程式時,將無法使用這種方法。

2.每個derived class一定要去override base class的virtual clone(),若忘記override clone(),程式結果將有錯誤,而且compiler也不會提醒你。

Modern C++ Design因此提出了Clone Factory Pattern來解決這個問題,不過我還沒看懂這個pattern,但目前看起來使用clone()的方式還算不錯。

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

Reference
Andrei Alexandrescu,Modern C++ Design,Addison Weseley,2001

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

导航