更多 C++ 慣用法/計數體
外觀
管理資源/物件的邏輯共享,防止昂貴的複製,並允許使用動態分配資源的物件的正確資源釋放。
- 引用計數(侵入式)
- 計數體
當使用 控制代碼/體 慣用法時,經常會發現複製體很昂貴。這是因為複製體涉及記憶體分配和複製構造。可以透過使用指標和引用來避免複製,但這會留下誰負責清理物件的問題。一些控制代碼必須負責釋放為體分配的記憶體資源。通常是最後一個。如果沒有自動回收記憶體資源,它會留下內建型別和使用者定義型別之間的使用者可見區別。
解決方案是在體類中新增一個引用計數,以促進記憶體管理;因此得名“計數體”。記憶體管理被新增到控制代碼類中,特別是對它的初始化、賦值、複製和銷燬的實現。
#include <cstring>
#include <algorithm>
#include <iostream>
class StringRep {
friend class String;
friend std::ostream &operator<<(std::ostream &out, StringRep const &str) {
out << "[" << str.data_ << ", " << str.count_ << "]";
return out;
}
public:
StringRep(const char *s) : count_(1) {
strcpy(data_ = new char[strlen(s) + 1], s);
}
~StringRep() { delete[] data_; }
private:
int count_;
char *data_;
};
class String {
public:
String() : rep(new StringRep("")) {
std::cout << "empty ctor: " << *rep << "\n";
}
String(const String &s) : rep(s.rep) {
rep->count_++;
std::cout << "String ctor: " << *rep << "\n";
}
String(const char *s) : rep(new StringRep(s)) {
std::cout << "char ctor:" << *rep << "\n";
}
String &operator=(const String &s) {
std::cout << "before assign: " << *s.rep << " to " << *rep << "\n";
String(s).swap(*this); // copy-and-swap idiom
std::cout << "after assign: " << *s.rep << " , " << *rep << "\n";
return *this;
}
~String() { // StringRep deleted only when the last handle goes out of scope.
if (rep && --rep->count_ <= 0) {
std::cout << "dtor: " << *rep << "\n";
delete rep;
}
}
private:
void swap(String &s) throw() { std::swap(this->rep, s.rep); }
StringRep *rep;
};
int main() {
std::cout << "*** init String a with empty\n";
String a;
std::cout << "\n*** assign a to \"A\"\n";
a = "A";
std::cout << "\n*** init String b with \"B\"\n";
String b = "B";
std::cout << "\n*** b->a\n";
a = b;
std::cout << "\n*** init c with a\n";
String c(a);
std::cout << "\n*** init d with \"D\"\n";
String d("D");
return 0;
}
避免了無端的複製,從而導致了更有效的實現。這種慣用法假設程式設計師可以編輯體的原始碼。當不可能時,使用分離的計數體。當計數器儲存在體類內部時,它被稱為侵入式引用計數;當計數器儲存在體類外部時,它被稱為非侵入式引用計數。此實現是淺複製的變體,具有深複製的語義和 Smalltalk 名稱-值對的效率。
輸出應如下所示
*** init String a with empty
empty ctor: [, 1]
*** assign a to "A"
char ctor:[A, 1]
before assign: [A, 1] to [, 1]
String ctor: [A, 2]
dtor: [, 0]
after assign: [A, 2] , [A, 2]
*** init String b with "B"
char ctor:[B, 1]
*** b->a
before assign: [B, 1] to [A, 1]
String ctor: [B, 2]
dtor: [A, 0]
after assign: [B, 2] , [B, 2]
*** init c with a
String ctor: [B, 3]
*** init d with "D"
char ctor:[D, 1]
dtor: [D, 0]
dtor: [B, 0]
建立多個引用計數會導致對同一個體的多次刪除,這是未定義的。必須小心避免為同一個體建立多個引用計數。侵入式引用計數很容易支援它。使用非侵入式引用計數,程式設計師需要自律才能防止重複引用計數。
- boost::shared_ptr(非侵入式引用計數)
- boost::intrusive_ptr(侵入式引用計數)
- std::shared_ptr
- Qt 工具包,例如 QString