C++OO风格中的引用计数
Reference Counting in C++ OO-Style
我在C++常见问题解答中遇到了一个有趣的基类实现,根据我天真的理解,它可以作为一些智能指针实现(例如shared_ptr)的替代方案。以下是逐字逐句的示例代码,但请按照上面的链接进行解释:
class Fred {
public:
static Fred create1(std::string const& s, int i);
static Fred create2(float x, float y);
Fred(Fred const& f);
Fred& operator= (Fred const& f);
~Fred();
void sampleInspectorMethod() const; // No changes to this object
void sampleMutatorMethod(); // Change this object
...
private:
class Data {
public:
Data() : count_(1) { }
Data(Data const& d) : count_(1) { } // Do NOT copy the 'count_' member!
Data& operator= (Data const&) { return *this; } // Do NOT copy the 'count_' member!
virtual ~Data() { assert(count_ == 0); } // A virtual destructor
virtual Data* clone() const = 0; // A virtual constructor
virtual void sampleInspectorMethod() const = 0; // A pure virtual function
virtual void sampleMutatorMethod() = 0;
private:
unsigned count_; // count_ doesn't need to be protected
friend class Fred; // Allow Fred to access count_
};
class Der1 : public Data {
public:
Der1(std::string const& s, int i);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual Data* clone() const;
...
};
class Der2 : public Data {
public:
Der2(float x, float y);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual Data* clone() const;
...
};
Fred(Data* data);
// Creates a Fred smart-reference that owns *data
// It is private to force users to use a createXXX() method
// Requirement: data must not be NULL
Data* data_; // Invariant: data_ is never NULL
};
Fred::Fred(Data* data) : data_(data) { assert(data != NULL); }
Fred Fred::create1(std::string const& s, int i) { return Fred(new Der1(s, i)); }
Fred Fred::create2(float x, float y) { return Fred(new Der2(x, y)); }
Fred::Data* Fred::Der1::clone() const { return new Der1(*this); }
Fred::Data* Fred::Der2::clone() const { return new Der2(*this); }
Fred::Fred(Fred const& f)
: data_(f.data_)
{
++data_->count_;
}
Fred& Fred::operator= (Fred const& f)
{
// DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
// (This order properly handles self-assignment)
// (This order also properly handles recursion, e.g., if a Fred::Data contains Freds)
Data* const old = data_;
data_ = f.data_;
++data_->count_;
if (--old->count_ == 0) delete old;
return *this;
}
Fred::~Fred()
{
if (--data_->count_ == 0) delete data_;
}
void Fred::sampleInspectorMethod() const
{
// This method promises ("const") not to change anything in *data_
// Therefore we simply "pass the method through" to *data_:
data_->sampleInspectorMethod();
}
void Fred::sampleMutatorMethod()
{
// This method might need to change things in *data_
// Thus it first checks if this is the only pointer to *data_
if (data_->count_ > 1) {
Data* d = data_->clone(); // The Virtual Constructor Idiom
--data_->count_;
data_ = d;
}
assert(data_->count_ == 1);
// Now we "pass the method through" to *data_:
data_->sampleMutatorMethod();
}
我认为这种方法在任何C++库中都没有使用;尽管它看起来很优雅。假设是单线程环境,为了简单起见,请回答以下问题:
- 这是管理对象生存期的智能指针方法的合适替代方案,还是只是在找麻烦
- 如果它合适,你为什么不经常使用它
这是管理对象生存期的智能指针方法的合适替代方案,还是只是在找麻烦?
不,我认为重新发明引用计数不是一个好主意,尤其是因为我们现在在C++11中有std::shared_ptr。根据std::shared_ptr,您可以很容易地实现您可能具有多态引用计数的Pimpl习语类。请注意,我们不再需要实现复制-ctor、赋值、dtor,与引用计数器和克隆相比,突变变得更简单:
// to be placed into a header file ...
#include <memory>
#include <utility>
#include <string>
class Fred
{
public:
static Fred create1(std::string const& s, int i);
static Fred create2(float x, float y);
void sampleInspectorMethod() const; // No changes to this object
void sampleMutatorMethod(); // Change this object
private:
class Data;
std::shared_ptr<Data> data_;
explicit Fred(std::shared_ptr<Data> d) : data_(std::move(d)) {}
};
以及实施。。。
// to be placed in the corresponding CPP file ...
#include <cassert>
#include "Fred.hpp"
using std::shared_ptr;
class Fred::Data
{
public:
virtual ~Data() {} // A virtual destructor
virtual shared_ptr<Data> clone() const = 0; // A virtual constructor
virtual void sampleInspectorMethod() const = 0; // A pure virtual function
virtual void sampleMutatorMethod() = 0;
};
namespace {
class Der1 : public Fred::Data
{
public:
Der1(std::string const& s, int i);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual shared_ptr<Data> clone() const;
...
};
// insert Der1 function definitions here
class Der2 : public Data
{
public:
Der2(float x, float y);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual shared_ptr<Data> clone() const;
...
};
// insert Der2 function definitions here
} // unnamed namespace
Fred Fred::create1(std::string const& s, int i)
{
return Fred(std::make_shared<Der1>(s,i));
}
Fred Fred::create2(float x, float y)
{
return Fred(std::make_shared<Der2>(x,y));
}
void Fred::sampleInspectorMethod() const
{
// This method promises ("const") not to change anything in *data_
// Therefore we simply "pass the method through" to *data_:
data_->sampleInspectorMethod();
}
void Fred::sampleMutatorMethod()
{
// This method might need to change things in *data_
// Thus it first checks if this is the only pointer to *data_
if (!data_.unique()) data_ = data_->clone();
assert(data_.unique());
// Now we "pass the method through" to *data_:
data_->sampleMutatorMethod();
}
(未经测试)
如果它合适,你为什么不经常使用它?
我认为引用计数,如果你自己实现的话,更容易出错。它在多线程环境中也有速度慢的名声,因为引用计数器必须以原子方式递增和递减。但我想,由于C++11提供了shared_ptr和move语义,这种写时复制模式可能会再次流行起来。如果为Fred类启用move语义,则可以避免原子递增引用计数器的一些开销。因此,将Fred对象从一个位置移动到另一个位置应该比复制它更快。
这是管理对象生存期的智能指针方法的合适替代方案,还是只是在找麻烦?
这是一种替代方案,但除非你有充分的理由使用它,否则它只是(以不可重复使用的方式)重新发明轮子。
如果您将代码更改为使用shared_ptr,您将不再需要显式定义复制/所有权语义(以及在您的pimpl库中定义复制构造函数和赋值)。您还将使用已经定义和测试过的代码(因为它是库的一部分)。
如果它合适,你为什么不经常使用它?
因为shared_ptr是可用的,并且已经实现了功能和所有的"gotcha"。
我也是,我想知道它是否适合作为智能指针的替代品。
但是,IMO,要成为智能指针,类必须可用作指针,即:
SmartPtr<int> ptr = new int(42);
int x = *ptr;
是的,这是一种内存管理,但它不是一个智能指针,因为它没有指针的语义。
正如评论中提到的,pimpl习惯用法确实有助于维护兼容性,而且它还可以促进开发,因为您不必重新编译包含类。但是,为了获得后一个优势,您不能在父类内部定义内部类(即Data),而只能提出一个前向声明,并将实际定义放在另一个头中。
class Fred {
...
private:
class Data;
};
而且,我发现在类Fred中声明Data的变体对未来的开发没有用处,因为如果你需要添加另一个类,你需要修改Fred,而不仅仅是创建另一个类别。这可能是需要的,但我建议你避免那个部分。
如果我不清楚什么,请随时提问!
-
C++FAQ的答案似乎更像是一个如何管理共享数据(使用写时复制)的简单示例。缺少几个方面,这可能很重要。
-
不同意我对1的看法。
为了避免像std::shared_ptr
那样使用"外部"引用计数带来的开销,您可以使用Andrei Alexandrescu的《现代C++设计》一书中描述的侵入式引用计数机制。Loki::COMRefCounted类展示了如何为Windows共享COM对象实现这样的所有权策略。
从本质上讲,它可以归结为智能指针模板类接受一个接口,该接口管理指针类实例本身中的引用计数和delete
可用性检查。我不知道STD C++库是否支持为std::shared_ptr
类实现这样的策略覆盖。
我们在许多嵌入式项目中非常成功地将Loki库仅用于智能指针模型。特别是由于这个特性,模型在细粒度方面的效率更高。
注意建议的(内置)实现默认情况下不是线程安全的。
如果以上所有方面都与您的目的无关,我建议使用sellibitze的答案中所示的Fred::Data
类的简单std::shared_ptr
表示。我也同意他在最后一段中提出的观点,引用计数和智能指针语义很容易被误解和实现错误。
如果C++11标准或boost不适合您,那么loki库仍然提供了一个易于集成和健壮的智能指针实现。
- Qt VTK交互风格的信号到小部件
- 将对象数组的引用传递给函数
- 什么时候在C++中返回常量引用是个好主意
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 何时在引用或唯一指针上使用移动语义
- 如何在c++中使用引用实现类似python的行为
- 编译C++时未定义的引用
- Ctypes wstring通过引用传递
- c++r值引用应用于函数指针
- 理解c++中的引用
- 我可以使用条件运算符初始化C风格的字符串文字吗
- C++取消引用指针.为什么会发生变化
- 如何修复此错误:未定义对"距离(浮点数,浮点数,浮点数,浮点数,浮点数)"的引用
- 我的项目不会像"undefined reference to `grpc::g_core_codegen_interface'"那样使用未定义的引用错误进行编译
- C++OO风格中的引用计数
- 在Google c++风格指南中引用的对象所有者是什么?
- 通过传递引用或返回引用来初始化结构是更好的风格吗?
- 为什么我不需要解引用一个char指针来输出c风格的字符串?
- 使用这个来引用类中的私有成员是一种好的风格吗?
- 编程风格:对象与引用或值通道?(c++)