今天來介紹 C++ 的一個重要的概念「The Rule of Three」,他是一個對於 C++ 物件導向的一個實作上的通則,會成為通則,是因為太多 programmer 犯了相同的錯誤,卻難以發現自身所犯的錯誤。至於為何麽要了解這個東西呢?就算不能在把妹的時候拿出來說嘴,也能在同事犯了這種錯誤時,說出「The Rule of Three」,會顯得有些你高深莫測,讓人對你刮目相看。或許當有天有人問說你們 Team 誰最了解 C++ 的時候,你的臉會浮在你同事的腦海中,把寫著你的名字的字條丟入火盃,僅此如此而已。
What’s 「The Rule of Three」?
The Rule of Three says that there are three member functions that go together: the destructor, the copy constructor, and the assignment operator. A class that defines a destructor should almost always define the other two members. Moreover, a class that defines a copy constructor or assignment operator should usually define the other two members as well.
簡單說就是「destructor」、「copy constructor」、「assignment operator」這三個要一起 define,不然就都不要 define。
不遵守會怎樣嗎?
請參考下面的例子:only destructor
class IntVec {
public:
IntVec(int n): data(new int[n]) { }
~IntVec() { delete[] data; };
int& operator[](int n)
{ return data[n]; }
const int& operator[](int n) const
{ return data[n]; }
private:
int* data;
};
int main()
{
IntVec x(100);
IntVec y = x; // Trouble!
return 0;
}
-
Shallow Copy : Default assignment operator 是直接 copy values,對於 pointer 而言就是 shallow copy,也就是 y.data=x.data (address to data memory),也就是 x.data 和 y.data 所指的是同一塊 memory。
-
RAII : 離開 scope 就會 call object destructor,因此離開 main function 會先 call y destructor,然後就會 delete data memory,接著再 call x destructor,也會 delete data memory,就會發現該 memory area 已經被 free not allocate,進而產生 runtime error。
a.out(24257,0x10ba57dc0) malloc: *** error for object 0x7ff306c01750: pointer being freed was not allocated
a.out(24257,0x10ba57dc0) malloc: *** set a breakpoint in malloc_error_break to debug
Abort trap: 6
上面是少了 assignment operator 的例子,copy constructor 也是同理:
IntVec y(x); // Trouble!
結論是,只要具備以下三個條件,就會產生此 error
- Data member is the pointer.
- There is a destructor. (free resource)
- There is no assignment operator and copy constructor. (shallow copy)
正確的寫法
下面的例子將展示符合「The Rule of Three」的寫法:
class IntVec {
public:
IntVec(int n): data(new int[n]), size(n) { }
~IntVec() { delete[] data; };
int& operator[](int n)
{ return data[n]; }
const int& operator[](int n) const
{ return data[n]; }
IntVec(const IntVec& v):
data(new int[v.size]),
size(v.size) {
std::copy(data, data + size, v.data);
}
IntVec&
operator=(const IntVec& v) {
int* newdata = new int[v.size];
std::copy(v.data,v.data+v.size, newdata);
delete[] data;
data = newdata;
size = v.size;
return *this;
}
private:
int* data;
int size;
};
我們先分析assignment operator 做了什麼事情:實作了 deep copy
IntVec&
operator=(const IntVec& v) {
int* newdata = new int[v.size];
std::copy(v.data,v.data+v.size, newdata);
delete[] data;
data = newdata;
size = v.size;
return *this;
}
再來看看 copy constructor:也是實作了 deep copy
IntVec(const IntVec& v):
data(new int[v.size]),
size(v.size) {
std::copy(data, data + size, v.data);
}
有人可能會好奇,為什麼 assignment operator 會多了 delete original data,主要原因是在 x assign 給 y 的時候,y 會先 create object(constructor) ,此時 y 會有擁有一個 original data,如果不 free 的話,會造成 memory leak。
Something to Discuss
Although a class with a destructor almost always needs a copy constructor and an assignment operator, the reverse is not always true.
根據上面的範例,我們可以知道,如果有實作 destructor ,代表對於該 class 有其他資源需要釋放,據此也應該實作 copy constructor and assignment operator。但是反之卻不是如此。
The reason is that not every copy constructor or assignment operator allocates resources, so not every copy constructor or assignment operator requires a destructor in order to free those resources.
其實重點是在「有沒有需要處理資源的 allocate and free 」,下面的例子將說明有些 case 在擁有 copy constructor and an assignment operator 的情況下,並不需要 destructor:
class Thing {
public:
Thing() { /* ... */ }
Thing(const Thing& t):
data(t.data)
{ } // don't copy the cache
Thing& operator=(const Thing& t) {
data = t.data; // copy the data
// clear the cache
cache = Cache();
}
private:
Data data;
Cache cache;
};
這個例子說明,在我們需要 copy constructor (不需要 copy cache) 和 assignment operator (需要 clear cache) 的情況下,並不需要 destructor。主要原因是,data member 並不包含 pointer ,也就是說此 class 並沒有 allocate(new) resource,因此也不需要 destructor。
Conclusion
為什麼 destructor 就一定要 copy constructor and assignment operator 呢?原因是,需要 destructor 意味著需要 deallocates a resource ,如果該 resource 需要 deallocates ,那麼就需要在 copy constructor and assignment operator 中實作 deep copy ,否則該 resource 就會 free twice。
為什麼擁有copy constructor and assignment operator,不一定需要 destructor 呢?原因是,copy constructor and assignment operator 的目的並不一定是牽扯到 allocate resource,上面的例子就清楚的說明此事,只要不牽扯到 allocate resource ,就不需要 destructor。
當你理解以上敘述,再來看 reference 出處的原文,相信你會讀起來輕鬆寫意,建議大家還是要去參照原文,畢竟我只是簡化其中,當個敲門磚罷了,希望有幫助到大家!
Reference
- 原文:C++ Made Easier: The Rule of Three By Andrew Koenig and Barbara E. Moo, June 01, 2001
- Rule Of Three in C++
- Wiki
Comments