(原創) 若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。
#include <iostream>2
#include <vector>3

4
using namespace std;5

6
class Foo {7
private:8
int _value;9

10
public:11
Foo(int n = 0) : _value(n) {}12
13
int getValue() {14
return _value;15
}16
};17

18
int 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成功。
#include <iostream>2
#include <vector>3

4
using namespace std;5

6
class Foo {7
private:8
int _value;9

10
public:11
Foo(int n = 0) : _value(n) {}12
13
int getValue() {14
return _value;15
}16
};17

18
int 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也無法正常了。
正確的寫法如下
/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : ContainerWithPointer_Clone.cpp5
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++6
Description : Demo how to clone container with pointer7
Release : 05/18/2007 1.08
*/9
#include <iostream>10
#include <vector>11
#include <algorithm>12
#include <functional>13

14
using namespace std;15

16
struct printElem {17
template <typename T>18
void operator()(T*& iter) const {19
cout << iter->getValue() << " ";20
}21
};22

23
class Foo {24
private:25
int _value;26

27
public:28
Foo(int n = 0) : _value(n) {}29
30
int getValue() {31
return _value;32
}33
};34

35
int 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的方式。
/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : ContainerWithPointer_BigThree.cpp5
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++6
Description : Demo how to use big three for pointer in container7
Release : 05/18/2007 1.08
*/9
#include <iostream>10
#include <vector>11
#include <algorithm>12
#include <functional>13

14
using namespace std;15

16
struct 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

26
class Foo {27
private:28
int _value;29

30
public:31
Foo(int n = 0) : _value(n) {}32
33
int getValue() const {34
return _value;35
}36
};37

38
class Moo {39
private:40
vector<Foo*> _foos;41
42
public: 43
Moo() {}44
// copy constructor45
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 operator52
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
// Destructor61
~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

76
int 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 constructor85
Moo moo2(moo1);86
Moo moo3 = moo1;87
88
// use assign operator89
Moo moo4;90
moo4 = moo1;91
92
// call moo1's destuctor to delete all pointer93
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。
/* 2
(C) OOMusou 2007 http://oomusou.cnblogs.com3

4
Filename : ContainerWithPointer_BigThree2.cpp5
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++6
Description : Demo how to use big three for pointer in container by clone()7
Release : 05/18/2007 1.08
*/9
#include <iostream>10
#include <vector>11
#include <algorithm>12
#include <functional>13

14
using namespace std;15

16
struct 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

26
class Foo {27
private:28
int _value;29

30
public: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

42
class Moo {43
private:44
vector<Foo*> _foos;45
46
public: 47
Moo() {}48
// copy constructor49
Moo(Moo& otherMoo) {50
transform(otherMoo._foos.begin(), otherMoo._foos.end(), back_inserter(_foos), mem_fun(&Foo::clone));51
}52
53
// Assignment operator54
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
// Destructor61
~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

76
int 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 constructor85
Moo moo2(moo1);86
Moo moo3 = moo1;87
88
// use assign operator89
Moo moo4;90
moo4 = moo1;91
92
// call moo1's destuctor to delete all pointer93
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++)


Foo(
浙公网安备 33010602011771号