C++ OO 风格的引用计数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11788187/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:33:00  来源:igfitidea点击:

Reference Counting in C++ OO-Style

c++reference-counting

提问by Verax

I came accross an intriguing implementation of a base class on the C++ FAQthat, according to my naive understanding, could serve as an alternative to some of the smart pointer implementations (e.g. shared_ptr). Here's the example code verbatim, but please follow the link above for an explanation:

我在C++ FAQ上遇到了一个有趣的基类实现,根据我的天真理解,它可以作为一些智能指针实现(例如 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();
}

I don't see this approach being used in any C++ libraries; although it seems quite elegant. Assuming a single-threaded environment, for the sake of simplicity, please answer the following questions:

我没有看到在任何 C++ 库中使用这种方法;虽然看起来很优雅。假设是单线程环境,为简单起见,请回答以下问题:

  1. Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects, or is it just asking for trouble?
  2. If it is suitable, why do you suppose it's not used more often?
  1. 这是用于管理对象生命周期的智能指针方法的合适替代方案,还是只是自找麻烦?
  2. 如果它合适,为什么你认为它不经常使用?

采纳答案by sellibitze

Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects, or is it just asking for trouble?

这是用于管理对象生命周期的智能指针方法的合适替代方案,还是只是自找麻烦?

No, I don't think it's a good idea to reinvent reference counting especially since we have std::shared_ptr now in C++11. You can easily implement your possibly polymorphic reference-counted Pimpl idiom class in terms of std::shared_ptr. Notice how we don't have to implement copy ctor, assignment, dtor anymore and mutation gets simpler w.r.t. the reference counter and cloning:

不,我认为重新发明引用计数不是一个好主意,尤其是因为我们现在在 C++11 中有 std::shared_ptr 。您可以根据 std::shared_ptr 轻松实现可能的多态引用计数 Pimpl 习惯用法类。请注意,我们不再需要实现复制构造函数、赋值、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)) {}
};

...and the implementation...

...和实施...

// 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();
}

(untested)

(未经测试)

If it is suitable, why do you suppose it's not used more often?

如果它合适,为什么你认为它不经常使用?

I think reference counting, if you implement it yourself, is easier to get wrong. It also has the reputation of being slow in multithreaded environments because the reference counters have to be incremented and decremented atomically. But I guess due to C++11 which offers shared_ptr and move semantics, this copy-on-write pattern might get a bit more popular again. If you enable move semantics for the Fred class you can avoid some of the costs of atomically incrementing reference counters. So moving a Fred object from one location to another should be even faster than copying it.

我认为引用计数,如果你自己实现,更容易出错。它也有在多线程环境中缓慢的声誉,因为引用计数器必须以原子方式递增和递减。但我想由于 C++11 提供了 shared_ptr 和移动语义,这种写时复制模式可能会再次流行起来。如果您为 Fred 类启用移动语义,您可以避免一些原子递增引用计数器的成本。因此,将 Fred 对象从一个位置移动到另一个位置应该比复制它更快。

回答by utnapistim

Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects, or is it just asking for trouble?

这是用于管理对象生命周期的智能指针方法的合适替代方案,还是只是自找麻烦?

It is an alternative, but unless you have a very good reason for using it, it's just reinventing the wheel (in a non-reusable way).

它是一种替代方法,但除非您有充分的理由使用它,否则它只是在重新发明轮子(以不可重用的方式)。

If you change your code to use shared_ptr instead, you will have removed the need to define copy/ownership semantics explicitly (and to define copy constructor and assignment in your pimpl base). You will also use code that's already defined and tested (since it's part of the library).

如果您将代码更改为使用 shared_ptr,您将不再需要显式定义复制/所有权语义(以及在 pimpl 库中定义复制构造函数和赋值)。您还将使用已经定义和测试过的代码(因为它是库的一部分)。

If it is suitable, why do you suppose it's not used more often?

如果它合适,为什么你认为它不经常使用?

Because shared_ptr is available and already implements the functionality and all the "gotcha"s.

因为 shared_ptr 可用并且已经实现了功能和所有“陷阱”。

回答by π?ντα ?ε?

  1. The C++ FAQ's answer seems more to be a simplistic example how to manage shared data (using copy on write). There are several aspects missing, that might be important.

  2. N/A with my opinion for 1.

  1. C++ FAQ 的答案似乎更像是一个如何管理共享数据的简单示例(使用写时复制)。缺少几个方面,这可能很重要。

  2. 不适用,我对 1 的看法。

To avoid the overhead introduced with 'external' reference counting as with std::shared_ptryou can use an intrusive ref counting mechanism as described in Andrei Alexandrescu'sbook Modern C++ Design. The Loki::COMRefCountedclass shows how to implement such ownership policy for Windows shared COM objects.

为了避免“外部”引用计数引入的开销,std::shared_ptr您可以使用Andrei Alexandrescu 的Modern C++ Design 中描述的侵入式引用计数机制。在洛基:: COMRefCounted类展示了如何实现这种所有权政策的Windows共享COM对象。

Essentially it boils down to the smart pointer template class accepting an interface that manages the reference counting and the check for deleteavailability in the pointee class instance itself. I don't know if the STD C++ library supports to implement such policy override for the std::shared_ptrclass.

本质上,它归结为智能指针模板类接受一个接口,该接口管理引用计数和delete指针类实例本身的可用性检查。我不知道 STD C++ 库是否支持为类实现这种策略覆盖std::shared_ptr

We're using the Loki Librarysolely for the smart pointer model in a number of embedded projects very sucessfully. Especially because of this feature to model fine granularity aspects of efficiency.

我们在许多嵌入式项目中非常成功地将Loki 库专门用于智能指针模型。尤其是因为此功能可以在效率的细粒度方面进行建模。

Notethat the proposed (builtin) implementations aren't thread safe by default.

请注意,默认情况下建议的(内置)实现不是线程安全的。

If all of that above aspects don't concern your purpose, I would propose to go for simple std::shared_ptrrepresentation of your Fred::Dataclass as shown in sellibitze's answer. I also agree with the points he makes up in the last paragraph, reference counting and smart pointer semantics is prone to get it misunderstood and implemented wrong.

如果以上所有方面都与您的目的无关,我建议std::shared_ptrFred::Data按照 sellibitze 的回答中所示的方式简单地表示您的课程。我也同意他在最后一段中提出的观点,引用计数和智能指针语义很容易被误解和实现错误。

If the C++11 standard or boost aren't options to go for you, the loki library still provides an easy to integrate and robust smart pointer implementation.

如果 C++11 标准或 boost 不适合您,loki 库仍然提供易于集成和健壮的智能指针实现。

回答by fstamour

Me too, I wonder if it's suitable as an alternative for smart pointer.

我也是,我想知道它是否适合作为智能指针的替代品。

But, IMO, to be a smart pointer, a class must be usable as a pointer i.e. :

但是,IMO,要成为智能指针,类必须可用作指针,即:

SmartPtr<int> ptr = new int(42);
int x = *ptr;

So yes, it it a sort of memory management, but it's not a smart pointer because it doesn't have the semantic of a pointer.

所以是的,它是一种内存管理,但它不是智能指针,因为它没有指针的语义。

As mentioned in the comments, the pimpl idiom is really helpful for maintaining compatibility and it can also boost the development as you don't have to recompile the containing class. BUT to have the latter advantage, you must not define the inner class (i.e. Data) inside of the parent class, but rather just put a forward declaration and put the actual definition inside another header.

正如评论中提到的,pimpl idiom 对于保持兼容性非常有帮助,它还可以促进开发,因为您不必重新编译包含类。但是要获得后一个优势,您不能在父类内部定义内部类(即数据),而只需放置一个前向声明并将实际定义放入另一个标题中。

class Fred {
    ...
private:

class Data;

};

And, I find it not useful for future development to declare variant of Data inside the class Fred, because if you need to add another class you will need to modify Fred instead of just creating another class. This could be wanted, but I suggest you to avoid that part.

而且,我发现在类 Fred 中声明 Data 的变体对于未来的开发没有用,因为如果您需要添加另一个类,您将需要修改 Fred 而不是创建另一个类。这可能是需要的,但我建议你避免那部分。

If I wasn't clear about something, feel free to ask questions!

如果我有不清楚的地方,请随时提出问题!