更多 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