【问题标题】:pros and cons of smart pointers智能指针的优缺点
【发布时间】:2009-12-17 04:06:06
【问题描述】:

我知道智能指针是用于资源管理的,并且支持RAII。

但是在哪些极端情况下智能指针看起来不智能以及使用它时要记住的事情呢?

【问题讨论】:

  • 是的,你没看过我的 MI 系列。

标签: c++ smart-pointers


【解决方案1】:

智能指针无助于防止图形结构中的循环。

例如,对象 A 拥有一个指向对象 B 和对象 B 的智能指针——返回对象 A。如果在 A 与 B(或 B 与 A)断开连接之前释放指向 A 和 B 的所有指针,A 和 B 都会抱在一起,形成快乐的内存泄漏。

垃圾收集可以帮助解决这个问题 - 它可以看到两个对象都无法访问并释放它们。

【讨论】:

  • Strong 智能指针在循环中会导致内存泄漏,但小心使用 weak 指针(任何完整智能指针库的一部分)允许即使使用循环结构,您也可以有效地使用智能指针而不会泄漏。
  • 这也与 GC 的常见泄漏有关,当您有一个循环结构并且不再需要它的一部分时,您必须手动中断循环以允许 GC(或允许 GC 收集整个结构)例如c#事件
  • @jk:不完全一样。 GC 可以处理 A 指向 B 和 B 指向 A 的情况,假设没有其他对象指向任何一个对象。如果还有另一个对 A 或 B 的引用,你只需要“打破循环”。所以问题真的不是循环。一个像样的 GC 可以处理这些。问题是当您从很少使用的数据创建对长期存在的数据的引用时,没有意识到它(如在事件中)。它与周期无关。
  • "垃圾收集可以帮助解决这个问题 - 它可以看到两个对象都无法访问并释放它们。" 但如果循环中的对象需要最终确定,则不会! (这是根据定义)
【解决方案2】:

我想提一下性能限制。智能指针通常使用原子操作(如 Win32 API 中的InterlockedIncrement )进行引用计数。这些函数比普通整数运算要慢得多。

在大多数情况下,这种性能损失不是问题,只要确保不要对智能指针对象制作太多不必要的副本(最好在函数调用中通过引用传递智能指针)。

【讨论】:

  • 除非性能损失变得明显,否则我建议不要使用对智能指针的引用。当用作函数参数时,它没有负面影响,但当用作类成员时可能会产生负面影响。当心!
  • @Benoit:我很确定“通过引用传递智能指针”特别是在调用中,在这种情况下你真的不应该建议某人通过值传递它。
  • 是的,我的意思是将它作为参数传递给函数。我已编辑回复以使其更清晰。
  • 还值得注意的是,在多线程应用程序中传递原始指针和未能使用 InterlockedIncrement 或其他一些同步机制是不安全的。考虑到这些限制,在这种情况下,ILCX 比锁要快得多,而且更难做到正确。恕我直言,这是智能指针的优点,而不是缺点
  • 公平地说,共享指针并不是唯一一种智能指针。其他类型的智能指针可以避免这种性能损失。
【解决方案3】:

这很有趣:Smart Pointers.
这是 Andrei Alexandrescu 的“现代 C++ 设计”中的一个示例章节。

【讨论】:

  • 如果链接失效,您能从中总结出相关点吗?
【解决方案4】:

注意转换 - 在原始指针和智能指针之间分配时。糟糕的智能指针——比如_com_ptr_t——允许隐式转换使情况变得更糟。大多数错误发生在转换时。

注意循环 - 如前所述,您需要弱指针来打破循环。然而,在复杂的图表中,这并不总是容易做到的。

选择太多 - 大多数库提供具有不同优点/缺点的不同实现。不幸的是,大多数时候这些不同的变体是不兼容的,这在混合库时成为一个问题。 (比如说,LibA 使用 LOKI,LibB 使用 boost)。必须为enable_shared_from_this 提前计划很糟糕,必须为一堆对象决定intrusive_ptrshared_ptrweak_ptr 之间的命名约定很糟糕。


对我来说,shared_ptr(或类似功能之一)的最大优势在于它与创建时的销毁策略相耦合。 C++ 和 Win32 都提供了很多摆脱事物的方法,这甚至都不好笑。在构建时进行耦合(不影响指针的实际类型)意味着我将这两种策略放在一个地方。

【讨论】:

    【解决方案5】:

    除了技术限制(已经提到:循环依赖),我想说智能指针最重要的一点是要记住它仍然是删除堆分配对象的一种解决方法。 在大多数情况下,堆栈分配是管理对象生命周期的最佳选择 - 以及引用的使用。

    【讨论】:

    • 堆栈分配在大多数情况下肯定不是最佳选择。堆栈分配有其自身的限制,如果您处理任何合理数量的数据结构,您很快就会遇到它们。大多数复杂的程序使用堆分配仅仅是因为当数量增长时你很快就会破坏堆栈。 Linux 默认是 8megs pr 线程 IIRC。在大多数嵌入式系统上,它在 kb 范围内。
    • 我明白你的意思。但是,您是否宁愿创建特定的容器类来分配和取消分配自己的内存块,而不是在代码中的任何地方分配内存块并希望您没有忘记任何人?这样,您可以规避堆栈大小问题...
    • 当然我们同意这一点——在我看来,这就是好的 C++ 的全部意义:) 我只是反对你的观点,即“堆栈分配是大多数情况下的最佳选择”。
    【解决方案6】:

    【讨论】:

      【解决方案7】:

      这里有一些事情

      • 没有确定的实体会破坏该对象。通常希望准确了解对象何时以及如何被销毁
      • 循环依赖 - 如果它们存在,您将有内存泄漏。这通常是 weak_ptr 来救援的地方。

      【讨论】:

      • 通常只要知道一个对象在适当的时间被销毁就足够了。我还没有遇到很多需要确切知道何时以及响应什么的情况。
      • 保证对象的销毁发生在明确定义的位置(即使在引用计数的智能指针的情况下,也保证销毁发生在明确定义的位置;它只是使用引用计数的智能指针,直到运行时你才知道那个地方)。
      • 这通常是weak_ptr来救援的地方。”通常不会,weak_ptr只能隐藏问题。
      【解决方案8】:

      在某些类型的具有循环的数据结构中,引用计数存在问题。从多个线程访问智能指针也可能会出现问题,对引用计数的并发访问可能会导致问题。 boost 中有一个名为atomic.hpp 的实用程序可以缓解这个问题。

      【讨论】:

        【解决方案9】:

        许多人在将智能指针与原始指针(指向相同的对象)混合使用时会遇到问题。一个典型的例子是与使用原始指针的 API 交互时。
        例如;在boost::shared_ptr 中有一个返回原始指针的.get() 函数。如果小心使用,功能会很好,但似乎很多人会绊倒它。
        恕我直言,这是“泄漏抽象”的一个例子。

        【讨论】:

          【解决方案10】:

          Raymond Chen 对智能指针的矛盾是出了名的。关于when the destructor actually runs 存在一些问题(请注意,析构函数在明确定义的时间以明确定义的顺序运行;只是偶尔你会忘记它是最后一个函数中的行)。

          还请记住,“智能指针”是一个相当大的类别。我将std::vector 包含在该类别中(a std::vector is essentially a smart array)。

          【讨论】:

          • 但是“管理自己的内存”只是一个“健全的 C++ 类”,并且什么都没有。这是您对 any最起码的期望> 类。智能指针是一个管理其他人的内存的指针。它被赋予一个指针,并负责在正确的时间删除它。而析构函数与智能指针无关.这是C++中的通病,任何C++程序员最好都熟记于心。
          • (1) Bjarne Stroustrup 没有使用“智能指针”这个术语,但他在他的常见问题解答中确实注意到了一个等价物:www2.research.att.com/~bs/bs_faq2.html#auto_ptr。 (2) “智能指针”没有说明复制语义。 std::auto_ptr 处理复制与复制普通指针非常不同,但它仍然是(非常不受欢迎的)智能指针。 (3)“智能指针”也没有说明所有权;比较共享指针、弱指针和 auto_ptr/scoped_ptr。
          • @MaxLybbert 但是智能指针与常规指针具有相同的值语义:智能指针的值与指向对象的值无关。这就是为什么智能指针operator*const 成员函数并返回非const 指针的原因。
          • @curiousguy:你可以加粗你对“智能指针”的定义,但这并不能使它成为一个普遍的真理。 “智能指针”是一个非常大的类别,涵盖了多种实现。其中一些实现符合您的定义,但没有理由相信它们都符合。
          • @MaxLybbert 引用我自己的话:“std::auto_ptr 的值与指向对象的值无关。这就是为什么 std::auto_ptr operator*const 成员函数和返回一个非const 指针。” (显然,具有原始指针语义的智能点将是......原始指针。)
          猜你喜欢
          • 2015-02-03
          • 2011-01-07
          • 1970-01-01
          • 1970-01-01
          • 2018-12-24
          • 2016-10-09
          • 1970-01-01
          • 2017-04-29
          • 1970-01-01
          相关资源
          最近更新 更多