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

时间:2009-02-02 16:33:15

标签: c++ smart-pointers raii reference-counting

我一直在评估各种智能指针实现(哇,那里有很多),在我看来,大多数可以归类为两大类:

1)此类别对引用的对象使用继承,以便它们具有引用计数,并且通常实现up()和down()(或它们的等价物)。 IE,要使用智能指针,您指向的对象必须从ref实现提供的某些类继承。

2)此类别使用辅助对象来保存引用计数。例如,不是将智能指针指向对象,而是实际指向此元数据对象...谁具有引用计数以及up()和down()实现(并且通常为指针提供一种机制)获取指向的实际对象,以便智能指针可以正确实现operator - >())。

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

2有一个问题,因为计数存储在另一个对象中,如果你有一个指向现有引用计数对象的指针被转换为引用的情况,你可能有一个错误(IE,因为计数不在实际的对象中,新的引用没有办法得到计数...参考ref复制构造或赋值是好的,因为它们可以共享count对象,但是如果你必须从指针转换,你完全被冲了过来...... ...

现在,正如我所理解的那样,boost :: shared_pointer使用机制2,或类似的东西......那就是说,我无法下定决心哪个更糟糕!我只使用机制1,在生产代码中......有没有人有这两种风格的经验?或许还有另一种方式比这两种方式更好?

9 个答案:

答案 0 :(得分:26)

  

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

  1. 不要!使用现有的,经过良好测试的智能指针,例如boost :: shared_ptr或std :: tr1 :: shared_ptr(std :: unique_ptr和std :: shared_ptr与C ++ 11 )
  2. 如果必须,请记住:
    1. 使用safe-bool idiom
    2. 提供运营商 - >
    3. 提供强有力的例外保证
    4. 记录您的课程对删除者的异常要求
    5. 尽可能使用copy-modify-swap来实现强大的异常保证
    6. 记录您是否正确处理多线程
    7. 编写广泛的单元测试
    8. 以这样的方式实现转换为base,它将在派生的指针类型上删除(policied smart pointers / dynamic deleter smart pointers)
    9. 支持访问原始指针
    10. 考虑提供破坏周期的弱指针的成本/利益
    11. 为您的智能指针提供适当的投射操作符
    12. 使您的构造函数模板化以处理从derived。
    13. 构造基指针
  3. 不要忘记我在上述不完整列表中遗忘的任何内容。

答案 1 :(得分:9)

只是为无处不在的Boost答案提供不同的视图(即使它是许多使用的正确答案),请查看Loki的智能指针实现。对于关于设计哲学的讨论,Loki的原始创作者写了这本书Modern C++ Design

答案 2 :(得分:7)

我已经使用boost :: shared_ptr已经有好几年了,虽然你对下行是正确的(没有可能通过指针进行赋值),但我认为它绝对值得,因为它有大量与指针相关的错误救了我。

在我的自制游戏引擎中,我尽可能地用shared_ptr替换了普通指针。如果你通过引用调用大多数函数,那么性能上升的原因实际上并不是那么糟糕,因此编译器不必创建太多临时的shared_ptr实例。

答案 3 :(得分:3)

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&lt;&gt;之间切换。和boost :: intrusive_ptr&lt;&gt;无需更改任何客户端代码

答案 4 :(得分:3)

如果你坚持使用标准库中的那些,你会没事的 虽然除了你指定的类型之外还有其他一些类型。

  • 共享:多个对象之间共享所有权的地方
  • 拥有:一个对象拥有该对象但允许传输。
  • 不可移动:一个对象拥有该对象且无法传输。

标准库有:

  • 的std :: auto_ptr的

Boost还有一些比tr1(标准的下一个版本)更新了

  • 的std :: TR1 :: shared_ptr的
  • 的std :: TR1 ::的weak_ptr

那些仍然处于提升状态的人(无论如何都是必须拥有的),希望能够成为tr2。

  • 升压:: scoped_ptr的
  • 升压:: scoped_array
  • 升压:: shared_array
  • 升压:: intrusive_ptr

请参阅: Smart Pointers: Or who owns you baby?

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:1)

2的问题可以解决。由于同样的原因,Boost提供了boost :: shared_from_this。在实践中,这不是一个大问题。

但他们选择#2的原因是它可以在所有情况下使用。依赖继承并不总是一个选项,然后你留下一个智能指针,你不能使用一半的代码。

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

答案 7 :(得分:1)

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

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

void doSomething (NIPtr<int> const &);

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

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

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

答案 8 :(得分:1)

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

我的答案解释了intrusive_ptr:Making shared_ptr not use delete。简而言之,如果您有一个已经引用计数的对象,或者需要(当您解释)已被引用为intrusive_ptr所拥有的对象时,则使用它。