在 C++ 中实现智能指针的最佳方法是什么?

发布于 2024-07-13 05:58:37 字数 662 浏览 11 评论 0原文

我一直在评估各种智能指针实现(哇,有很多),在我看来,它们中的大多数可以分为两大类:

1)此类别在引用的对象上使用继承,以便它们具有引用计数通常会实现 up() 和 down() (或它们的等效函数)。 IE,要使用智能指针,您指向的对象必须继承自 ref 实现提供的某个类。

2) 该类别使用辅助对象来保存引用计数。 例如,它不是将智能指针直接指向一个对象,而是实际上指向这个元数据对象...谁有引用计数以及 up() 和 down() 实现(以及谁通常为指针提供了一种机制)获取所指向的实际对象,以便智能指针能够正确实现运算符 ->())。

现在, 1 的缺点是它强制您想要引用计数的所有对象从共同祖先继承,这意味着您不能使用它来引用您无法控制源代码的计数对象到。

2 的问题是,由于计数存储在另一个对象中,如果遇到指向现有引用计数对象的指针被转换为引用的情况,则可能会遇到错误(IE,因为计数不在实际对象,新引用无法获取计数... ref 到 ref 复制构造或赋值很好,因为它们可以共享计数对象,但如果您必须从指针进行转换,那么您'完全软管)...

现在,据我了解,boost::shared_pointer 使用机制 2,或类似的东西...也就是说,我无法完全决定哪一个更糟糕! 我只在生产代码中使用过机制 1...有人有这两种风格的经验吗? 或者也许还有另一种方法比这两种方法更好?

I've been evaluating various smart pointer implementations (wow, there are a LOT out there) and it seems to me that most of them can be categorized into two broad classifications:

1) This category uses inheritance on the objects referenced so that they have reference counts and usually up() and down() (or their equivalents) implemented. IE, to use the smart pointer, the objects you're pointing at must inherit from some class the ref implementation provides.

2) This category uses a secondary object to hold the reference counts. For example, instead of pointing the smart pointer right at an object, it actually points at this meta data object... Who has a reference count and up() and down() implementations (and who usually provides a mechanism for the pointer to get at the actual object being pointed to, so that the smart pointer can properly implement operator ->()).

Now, 1 has the downside that it forces all of the objects you'd like to reference count to inherit from a common ancestor, and this means that you cannot use this to reference count objects that you don't have control over the source code to.

2 has the problem that since the count is stored in another object, if you ever have a situation that a pointer to an existing reference counted object is being converted into a reference, you probably have a bug (I.E., since the count is not in the actual object, there is no way for the new reference to get the count... ref to ref copy construction or assignment is fine, because they can share the count object, but if you ever have to convert from a pointer, you're totally hosed)...

Now, as I understand it, boost::shared_pointer uses mechanism 2, or something like it... That said, I can't quite make up my mind which is worse! I have only ever used mechanism 1, in production code... Does anyone have experience with both styles? Or perhaps there is another way thats better than both of these?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

明媚殇 2024-07-20 05:58:38

Boost 还有一个侵入式指针(如解决方案 1),不需要从任何东西继承。 它确实需要更改指向类的指针来存储引用计数并提供适当的成员函数。 我在内存效率很重要并且不希望每个共享指针使用另一个对象的开销的情况下使用了它。

示例:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

使用 Ptr typedef 以便我可以轻松地在 boost::shared_ptr<> 和 boost::shared_ptr<> 之间切换。 和 boost::intrusive_ptr>> 无需更改任何客户端代码

Boost also has an intrusive pointer (like solution 1), that doesn't require inheriting from anything. It does require changing the pointer to class to store the reference count and provide appropriate member functions. I've used this in cases where memory efficiency was important, and didn't want the overhead of another object for each shared pointer used.

Example:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

The Ptr typedef is used so that I can easily switcth between boost::shared_ptr<> and boost::intrusive_ptr<> without changing any client code

情魔剑神 2024-07-20 05:58:38

如果您坚持使用标准库中的内容,那就没问题。
尽管除了您指定的类型之外还有一些其他类型。

  • 共享:所有权在多个对象之间共享
  • 。拥有:一个对象拥有该对象,但允许转让。
  • 不可移动:一个对象拥有该对象且不能转让。

标准库有:

  • std::auto_ptr

Boost 比 tr1(标准的下一版本)改编的

  • std::tr1::shared_ptr
  • std::tr1::weak_ptr

还多了一些,还有那些仍在 boost 中的(相对而言)无论如何,这是必须有的),希望能进入 tr2。

  • boost::scoped_ptr
  • boost::scoped_array
  • boost::shared_array
  • boost::intrusive_ptr

请参阅:
智能指针:或者谁拥有你宝贝?

If you stick with the ones that are in the standard library you will be fine.
Though there are a few other types than the ones you specified.

  • Shared: Where the ownership is shared between multiple objects
  • Owned: Where one object owns the object but transfer is allowed.
  • Unmovable: Where one object owns the object and it can not be transferred.

The standard library has:

  • std::auto_ptr

Boost has a couple more than have been adapted by tr1 (next version of the standard)

  • std::tr1::shared_ptr
  • std::tr1::weak_ptr

And those still in boost (which in relatively is a must have anyway) that hopefully make it into tr2.

  • boost::scoped_ptr
  • boost::scoped_array
  • boost::shared_array
  • boost::intrusive_ptr

See:
Smart Pointers: Or who owns you baby?

梦明 2024-07-20 05:58:38

在我看来,这个问题有点像问“哪种排序算法是最好的?” 没有一个答案,这取决于你的具体情况。

出于我自己的目的,我使用您的类型 1。我无权访问 TR1 库。 我确实可以完全控制我需要共享指针的所有类。 类型 1 的额外内存和时间效率可能相当小,但内存使用和速度对我的代码来说是个大问题,所以类型 1 是一个灌篮。

另一方面,对于任何可以使用 TR1 的人来说,我认为类型 2 std::tr1::shared_ptr 类将是一个明智的默认选择,只要没有迫切的理由不使用它就可以使用它。

It seems to me this question is kind of like asking "Which is the best sort algorithm?" There is no one answer, it depends on your circumstances.

For my own purposes, I'm using your type 1. I don't have access to the TR1 library. I do have complete control over all the classes I need to have shared pointers to. The additional memory and time efficiency of type 1 might be pretty slight, but memory usage and speed are big issues for my code, so type 1 was a slam dunk.

On the other hand, for anyone who can use TR1, I'd think the type 2 std::tr1::shared_ptr class would be a sensible default choice, to be used whenever there isn't some pressing reason not to use it.

轻许诺言 2024-07-20 05:58:38

2的问题可以解决。 出于同样的原因,Boost 提供了 boost::shared_from_this 。 实际上,这并不是什么大问题。

但他们选择第二个选项的原因是它可以在所有情况下使用。 依赖继承并不总是一种选择,然后你就剩下一个智能指针,你的一半代码都无法使用。

我不得不说#2 是最好的,因为它可以在任何情况下使用。

The problem with 2 can be worked around. Boost offers boost::shared_from_this for this same reason. In practice, it's not a big problem.

But the reason they went with your option #2 is that it can be used in all cases. Relying on inheritance isn't always an option, and then you're left with a smart pointer you can't use for half your code.

I'd have to say #2 is best, simply because it can be used in any circumstances.

安人多梦 2024-07-20 05:58:38

我们的项目广泛使用智能指针。 一开始,不确定要使用哪个指针,因此主要作者之一在他的模块中选择了侵入式指针,而另一个则选择了非侵入式版本。

一般来说,两种指针类型之间的差异并不显着。 唯一的例外是我们的非侵入式指针的早期版本是从原始指针隐式转换的,如果指针使用不正确,这很容易导致内存问题:

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

不久前,一些重构导致代码的某些部分被合并,因此必须选择使用哪种指针类型。 非侵入式指针现在将转换构造函数声明为显式,因此决定使用侵入式指针以节省所需的代码更改量。

令我们非常惊讶的是,我们注意到的一件事是,通过使用侵入式指针,我们立即获得了性能改进。 我们没有对此进行太多研究,只是假设差异在于维护计数对象的成本。 非侵入式共享指针的其他实现可能已经解决了这个问题。

Our project uses smart pointers extensively. In the beginning there was uncertainty about which pointer to use, and so one of the main authors chose an intrusive pointer in his module and the other a non-intrusive version.

In general, the differences between the two pointer types were not significant. The only exception being that early versions of our non-intrusive pointer implicitly converted from a raw pointer and this can easily lead to memory problems if the pointers are used incorrectly:

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

A while ago, some refactoring resulted in some parts of the code being merged, and so a choice had to be made about which pointer type to use. The non-intrusive pointer now had the converting constructor declared as explicit and so it was decided to go with the intrusive pointer to save on the amount of code change that was required.

To our great surprise one thing we did notice was that we had an immediate performance improvement by using the intrusive pointer. We did not put much research into this, and just assumed that the difference was the cost of maintaining the count object. It is possible that other implementations of non-intrusive shared pointer have solved this problem by now.

迷鸟归林 2024-07-20 05:58:38

你所说的是侵入式非侵入式智能指针。 Boost两者都有。 每次需要更改引用计数时,boost::intrusive_ptr 都会调用一个函数来减少和增加对象的引用计数。 它不是调用成员函数,而是调用自由函数。 因此它允许管理对象而无需更改其类型的定义。 正如你所说, boost::shared_ptr 是非侵入式的,你的类别 2。

我有一个解释 intrusive_ptr 的答案: 使shared_ptr不使用delete。 简而言之,如果您有一个已经具有引用计数的对象,或者需要(正如您所解释的)一个已经被引用的对象由 intrusive_ptr 拥有,则可以使用它。

What you are talking about are intrusive and non-intrusive smart pointers. Boost has both. boost::intrusive_ptr calls a function to decrease and increase the reference count of your object, everytime it needs to change the reference count. It's not calling member functions, but free functions. So it allows managing objects without the need to change the definition of their types. And as you say, boost::shared_ptr is non-intrusive, your category 2.

I have an answer explaining intrusive_ptr: Making shared_ptr not use delete. In short, you use it if you have an object that has already reference counting, or need (as you explain) an object that is already referenced to be owned by an intrusive_ptr.

洒一地阳光 2024-07-20 05:58:37

“在 C++ 中实现智能指针的最佳方法是什么”

  1. 不要! 使用现有的、经过充分测试的智能指针,例如 boost::shared_ptr 或 std::tr1::shared_ptr (std: :unique_ptr 和 std::shared_ptr 与 C++ 11)
  2. 如果必须这样做,请记住:
    1. 使用 safe-bool 习惯用法
    2. 提供一个操作符->
    3. 提供强有力的异常保障
    4. 记录班级对删除程序提出的例外要求
    5. 尽可能使用 copy-modify-swap 来实现强大的异常保证
    6. 记录您是否正确处理多线程
    7. 编写大量单元测试
    8. 以删除派生指针类型(监管智能指针/动态删除器智能指针)的方式实现基数转换
    9. 支持访问原始指针
    10. 考虑提供弱指针来打破循环的成本/收益
    11. 为您的智能指针提供适当的转换运算符
    12. 使构造函数模板化以处理从派生构造基指针。

并且不要忘记上面不完整列表中我可能忘记的任何内容。

"What is the best way to implement smart pointers in C++"

  1. Don't! Use an existing, well tested smart pointer, such as boost::shared_ptr or std::tr1::shared_ptr (std::unique_ptr and std::shared_ptr with C++ 11)
  2. If you have to, then remember to:
    1. use safe-bool idiom
    2. provide an operator->
    3. provide the strong exception guarantee
    4. document the exception requirements your class makes on the deleter
    5. use copy-modify-swap where possible to implement the strong exception guarantee
    6. document whether you handle multithreading correctly
    7. write extensive unit tests
    8. implement conversion-to-base in such a way that it will delete on the derived pointer type (policied smart pointers / dynamic deleter smart pointers)
    9. support getting access to raw pointer
    10. consider cost/benifit of providing weak pointers to break cycles
    11. provide appropriate casting operators for your smart pointers
    12. make your constructor templated to handle constructing base pointer from derived.

And don't forget anything I may have forgotten in the above incomplete list.

若水般的淡然安静女子 2024-07-20 05:58:37

只是为了对无处不在的 Boost 答案提供不同的看法(尽管它是许多用途的正确答案),请查看Loki 的智能指针实现。 为了探讨设计哲学,Loki 的原始创建者写了本书现代 C++ 设计< /a>.

Just to supply a different view to the ubiquitous Boost answer (even though it is the right answer for many uses), take a look at Loki's implementation of smart pointers. For a discourse on the design philosophy, the original creator of Loki wrote the book Modern C++ Design.

千仐 2024-07-20 05:58:37

我已经使用 boost::shared_ptr 好几年了,虽然你对缺点的看法是正确的(不可能通过指针进行赋值),但我认为这绝对是值得的,因为它让我避免了大量与指针相关的错误。

在我的自制游戏引擎中,我尽可能用shared_ptr替换了普通指针。 如果您通过引用调用大多数函数,这样编译器就不必创建太多临时的shared_ptr 实例,那么这导致的性能损失实际上并没有那么糟糕。

I've been using boost::shared_ptr for several years now and while you are right about the downside (no assignment via pointer possible), I think it was definitely worth it because of the huge amount of pointer-related bugs it saved me from.

In my homebrew game engine I've replaced normal pointers with shared_ptr as much as possible. The performance hit this causes is actually not so bad if you are calling most functions by reference so that the compiler does not have to create too many temporary shared_ptr instances.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文