12

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

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

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

现在, 1 有一个缺点,它强制您希望引用 count 的所有对象都从一个共同的祖先继承,这意味着您不能使用它来引用您无法控制源代码的 count 对象到。

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

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

4

9 回答 9

26

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

  1. 不! 使用现有的、经过良好测试的智能指针,例如 boost::shared_ptr 或 std::tr1::shared_ptr(使用 C++ 11 的 std::unique_ptr 和 std::shared_ptr)
  2. 如果必须,请记住:
    1. 使用安全布尔成语
    2. 提供一个运算符->
    3. 提供强大的异常保证
    4. 记录你的班级对删除器的例外要求
    5. 尽可能使用 copy-modify-swap 来实现强异常保证
    6. 记录您是否正确处理多线程
    7. 编写广泛的单元测试
    8. 以这样的方式实现转换为基,它将删除派生指针类型(策略智能指针/动态删除器智能指针)
    9. 支持访问原始指针
    10. 考虑提供弱指针来打破周期的成本/收益
    11. 为您的智能指针提供适当的转换运算符
    12. 使您的构造函数模板化以处理从派生的构造基指针。

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

于 2009-02-02T19:00:37.110 回答
9

只是为了为无处不在的 Boost 答案提供不同的观点(即使它是许多用途的正确答案),请查看Loki的智能指针实现。对于设计哲学的论述,Loki 的原始创建者写了《现代 C++ 设计》一书。

于 2009-02-02T17:13:21.507 回答
7

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

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

于 2009-02-02T16:44:01.740 回答
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<> 和 boost::intrusive_ptr<> 之间轻松切换,而无需更改任何客户端代码

于 2009-02-02T16:55:14.307 回答
3

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

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

标准库有:

  • 标准::auto_ptr

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

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

而那些仍在提升中的人(无论如何这是必须拥有的)有望进入tr2。

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

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

于 2009-02-02T17:03:47.423 回答
2

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

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

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

于 2009-02-02T17:13:19.963 回答
1

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

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

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

于 2009-02-02T16:53:28.960 回答
1

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

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

void doSomething (NIPtr<int> const &);

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

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

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

于 2009-02-02T18:16:58.743 回答
1

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

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

于 2009-02-02T19:06:30.017 回答