(原創) 若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。
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}
執行結果
1 2 3
vector v2:
1 2 3
28行
使用vector的copy constructor進行clone。
現在我們將v1內的pointer刪除,若v2能正常顯示,則表示v2 clone成功。
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}
執行結果
1 2 3
vector v2:
-572662307 -572662307 -572662307
30~31行
delete *iter;
只要將v1中的pointer delete,v2就無法顯示,證明vector的copy constructor並非真正的clone,只是將pointer copy過去,這樣會造成v1和v2中的pointer指的是同一個object,只要v1一delete的,v2也無法正常了。
正確的寫法如下
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}
執行結果
1 2 3
vector v2:
1 2 3
44行
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的方式。
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}
執行結果
moo2:1 2 3
moo3:1 2 3
moo4:1 2 3
16行
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行
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行
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行
手動刪除所有pointer。
93行
moo1.~Moo();
呼叫了moo1的destructor,故意刪除了moo1中所有的pointer,以測試copy constructor和assignment operator是否正常。
方法二:另外加上clone() member function。
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}
執行結果
moo2:1 2 3
moo3:1 2 3
moo4:1 2 3
33行
return new Foo(*this);
}
自己加上一個clone() member function。
48行
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++)