跳转到内容

更多 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++ 惯用法

华夏公益教科书