因為今天突然看到一篇文章在讨論C++的指派構造,也就是 operator = 的重載, 裡面說到了一個“自指派”的問題,以前自己也看過Thinking In C++中有介紹到這個問題,于是又翻起書來,重溫了一遍,發現,裡面介紹到了一個很重要的技術,也就是标題所說的,Reference Counting(引用計數)的技術
那麼,什麼是“引用計數”呢?它又有什麼用呢?
在我們建構對象的時候,例如,通過拷貝構造函數和operator=對一個類的對象的引用進行了一次新的建構,也就是産生了一個新的對象,有可能,一個項目中,會有很多次調用拷貝構造的情況,而且,有時候,很多對象的建構,隻是為了讀,也就是拿來用,而不會對其進行寫入,那麼,這個時候,為了節省記憶體的開銷和效率的提高,我們就可以使用一種技術——引用計數。
Thinking In C++一書中是這麼說的:
You give intelligence to the object that’s being pointed to so it knows how many objects are pointing to it. Then copy-construction or assignment means attaching another pointer to an existing object and incrementing the reference count. Destruction means reducing the reference count and destroying the object if the reference count goes to zero.
應該能看懂吧,我就不詳細翻譯了,因為怕讓人産生歧義,還是原文簡單易懂。這裡隻是簡單說明一下:你應該讓這個對象知道自己被引用了多少次,當執行一次拷貝或者指派的時候,就應該讓這個對象的引用次數(ref count)加一,析構函數應該讓ref count減一,然後,在ref count = 0的時候,釋放這個對象的記憶體。
那麼,這個時候,很有可能會産生一個問題:當我有時需要對我這個類的對象進行write,也就是,改變其中的某個屬性,那麼,不是會造成與這個對象共用同一塊記憶體的對象的屬性也改變嗎?這個時候,就需要用到另一個技術:copy on write(有人翻譯成“寫拷貝”,也就是說,當我需要對這個對象進行“寫”操作,即,如上所說,改變其某個屬性的時候,就應該copy一下這個對象的内容去建構一個新的對象,而不是簡單的在原來這個對象上進行修改)。
那麼,應該怎麼來控制這個寫操作呢?其實很簡單,隻需要在這個類中對其對象的屬性有修改的成員函數中,在修改屬性之前,判斷一下,這個對象的存儲單元目前沒有被其他的對象引用,也就是ref count = 1,此時,才可以對目前對象進行改變;如果,ref count > 1,那麼,就需要在這個函數最開始部分先開辟一塊新的記憶體來copy一個與目前對象屬性相同的對象。
說了這麼一大通,到底應該怎麼做呢?我們直接來看代碼:同樣摘自Thinking In c++ ,
可能有點長,不過,為了搞清楚,也不能省事,其中加入了自己的注釋,便于了解
class Dog {
string nm;
int refcount;
Dog(const string& name)
: nm(name), refcount(1) {
cout << "Creating Dog: " << *this << endl;
}
// Prevent assignment:
Dog& operator=(const Dog& rv);
public:
// Dogs can only be created on the heap:
static Dog* make(const string& name) { //注意,此處是靜态方法make來産生新的Dog對象指針
return new Dog(name);
}
Dog(const Dog& d) //copy constructor of class Dog
: nm(d.nm + " copy"), refcount(1) {
cout << "Dog copy-constructor: "
<< *this << endl;
}
~Dog() {
cout << "Deleting Dog: " << *this << endl;
}
void attach() { //目前有新的對象與目前對象的記憶體塊相關聯
++refcount;
cout << "Attached Dog: " << *this << endl;
}
void detach() { //判斷目前ref count是否等于0,來決定是否要釋放記憶體
cout << "Detaching Dog: " << *this << endl;
// Destroy object if no one is using it:
if(--refcount == 0) delete this;
}
// Conditionally copy this Dog.
// Call before modifying the Dog, assign
// resulting pointer to your Dog*.
Dog* unalias() {
cout << "Unaliasing Dog: " << *this << endl; //判斷目前Dog對象是否需要copy on write
// Don't duplicate if not aliased:
if(refcount == 1) return this;
--refcount;
// Use copy-constructor to duplicate:
return new Dog(*this);
}
void rename(const string& newName) { //這個就是用來對Dog對象進行"寫"操作的函數
nm = newName;
cout << "Dog renamed to: " << *this << endl;
}
friend ostream& //為了便于追蹤,重寫的operator <<
operator<<(ostream& os, const Dog& d) {
return os << "[" << d.nm << "], rc = "
<< d.refcount;
}
};
class DogHouse {
Dog* p;
string houseName;
public:
DogHouse(Dog* dog, const string& house)
: p(dog), houseName(house) {
cout << "Created DogHouse: "<< *this << endl;
}
DogHouse(const DogHouse& dh)
: p(dh.p),
houseName("copy-constructed " +
dh.houseName) {
p->attach();
cout << "DogHouse copy-constructor: "
<< *this << endl;
}
DogHouse& operator=(const DogHouse& dh) {
// Check for self-assignment:
if(&dh != this) {
houseName = dh.houseName + " assigned";
p->detach(); // Clean up what you're using first:
p = dh.p; // Like copy-constructor
p->attach();
}
cout << "DogHouse operator= : "
<< *this << endl;
return *this;
}
// Decrement refcount, conditionally destroy
~DogHouse() {
cout << "DogHouse destructor: "
<< *this << endl;
p->detach();
}
void renameHouse(const string& newName) {
houseName = newName;
}
//DogHouse通過調用Dog::unalias()來判斷是否需要copy on write
void unalias() { p = p->unalias(); }
// Copy-on-write. Anytime you modify the
// contents of the pointer you must
// first unalias it:
void renameDog(const string& newName) {
unalias(); //在對Doghouse成員Dog的屬性進行改變的時候,必須要先調用unalias判斷,是否需要copy on write
p->rename(newName);
}
// ... or when you allow someone else access:
Dog* getDog() { //調用這個函數,也有可能在外界改變對象屬性,是以,也應該先調用unalias
unalias();
return p;
}
friend ostream&
operator<<(ostream& os, const DogHouse& dh) {
return os << "[" << dh.houseName
<< "] contains " << *dh.p;
}
};
int main() {
DogHouse
fidos(Dog::make("Fido"), "FidoHouse"),
spots(Dog::make("Spot"), "SpotHouse");
cout << "Entering copy-construction" << endl;
DogHouse bobs(fidos);
cout << "After copy-constructing bobs" << endl;
cout << "fidos:" << fidos << endl;
cout << "spots:" << spots << endl;
cout << "bobs:" << bobs << endl;
cout << "Entering spots = fidos" << endl;
spots = fidos;
cout << "After spots = fidos" << endl;
cout << "spots:" << spots << endl;
cout << "Entering self-assignment" << endl;
bobs = bobs;
cout << "After self-assignment" << endl;
cout << "bobs:" << bobs << endl;
cout << "Entering rename(/"Bob/")" << endl;
//bobs.getDog()->rename("Bob"); //如果把這行注釋了,那麼,運作的結果,就是下面的結果
cout << "After rename(/"Bob/")" << endl;
} ///:~
程式運作的結果如下:
Creating Dog: [Fido], rc = 1
Created DogHouse: [FidoHouse]
contains [Fido], rc = 1
Creating Dog: [Spot], rc = 1
Created DogHouse: [SpotHouse]
contains [Spot], rc = 1
Entering copy-construction
Attached Dog: [Fido], rc = 2
DogHouse copy-constructor:
[copy-constructed FidoHouse]
contains [Fido], rc = 2
After copy-constructing bobs
fidos:[FidoHouse] contains [Fido], rc = 2
spots:[SpotHouse] contains [Spot], rc = 1
bobs:[copy-constructed FidoHouse]
contains [Fido], rc = 2
Entering spots = fidos
Detaching Dog: [Spot], rc = 1
Deleting Dog: [Spot], rc = 0
Attached Dog: [Fido], rc = 3
DogHouse operator= : [FidoHouse assigned]
contains [Fido], rc = 3
After spots = fidos
spots:[FidoHouse assigned] contains [Fido],rc = 3
Entering self-assignment
DogHouse operator= : [copy-constructed FidoHouse]
contains [Fido], rc = 3
After self-assignment
bobs:[copy-constructed FidoHouse]
contains [Fido], rc = 3
Entering rename("Bob")
After rename("Bob")
DogHouse destructor: [copy-constructed FidoHouse]
contains [Fido], rc = 3
Detaching Dog: [Fido], rc = 3
DogHouse destructor: [FidoHouse assigned]
contains [Fido], rc = 2
Detaching Dog: [Fido], rc = 2
DogHouse destructor: [FidoHouse]
contains [Fido], rc = 1
Detaching Dog: [Fido], rc = 1
Deleting Dog: [Fido], rc = 0
通過對源碼的斷點跟蹤,相信應該不難了解原因……
我也不再贅言……歡迎交流……