跳轉到內容

更多 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
[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]

James O. Coplien 的 C++ 慣用法

華夏公益教科書