【问题标题】:Should I use unique_ptr or shared_ptr in this case?在这种情况下我应该使用 unique_ptr 还是 shared_ptr ?
【发布时间】:2014-02-18 09:06:32
【问题描述】:

在我的 QT 应用程序的主窗口中,我使用 std::shared_ptr 来保存指向我的网络服务实例的指针,该实例管理与多个客户端的所有连接。现在,我必须将此指针传递给多个子窗口,以便它们可以与客户端进行通信。

我最好在主窗口和子窗口中使用 std::shared_ptr 成员变量并在创建子窗口时传递复制它,还是最好使用 std::unique_ptr 并将原始指针传递给子窗口,因为主窗口无论如何都会比子窗口寿命长?

非常感谢!

【问题讨论】:

  • 我认为 std::shared_ptr 将是一个更好的选择,因为你在谈论子窗口。

标签: c++ shared-ptr unique-ptr


【解决方案1】:

主要的实际区别是当主窗口被销毁而子窗口仍然存在并正在使用网络服务时会发生什么:

  • 如果您使用 unique_ptr 并传递原始指针,那么您将获得未定义的行为。
  • 如果您使用shared_ptr,则网络服务会一直存在,直到所有子窗口都被销毁。

现在,如果这种情况在设计上是不可能的,那么未定义的行为本质上就不是问题。如果由于错误而发生这种情况,那么如果网络服务与主窗口一起被破坏,它可能可以帮助您检测错误,如果您使用unique_ptr,就会发生这种情况。使用unique_ptr表示主窗口是唯一拥有网络服务的东西,其他人只是按照主窗口的指示使用它。

另一方面,如果您稍后更改设计并希望使条件合法,或者如果您想以不同的方式使用子窗口,这意味着没有一个网络服务对象可以全部使用并且比它们更长寿全部,那么如果你从一开始就使用shared_ptr 会更容易。使用shared_ptr表示所有窗口共享网络服务的所有权。

我认为一般情况下无法说您是否应该尝试让您的代码在“不可能的情况”下继续工作。在这种情况下,这样做非常便宜(shared_ptr 的复制成本当然比原始指针高,但与创建子窗口相比便宜,并且代码的结构都相同)。在某些情况下,例如在对子窗口代码进行单元测试时,灵活地使“不可能的条件”发生是很有用的。所以可能使用shared_ptr 来获得灵活性。如果出于其他原因强制执行生命周期约束很重要,那么您可能不想要任何灵活性,在这种情况下,请避免编写只会隐藏错误的代码。

【讨论】:

    【解决方案2】:

    实际上,您还没有探索过第三种选择。

    • 传递 shared_ptr 允许您拥有多个所有者,但在您的情况下,这似乎是不必要的
    • 使用unique_ptr 并传递原始指针会强制执行单个所有者,但如果出现错误,您将面临未定义的行为(崩溃)。

    第三种选择是使用weak_ptr 结合这两种方法:

    • 主窗口拥有shared_ptr 中的设备
    • 子窗口下传weak_ptr

    当子窗口需要使用设备进行通信时,他们会在weak_ptr上使用lock()临时获得一个shared_ptr。如果 shared_ptr 为空(这是一个错误),您可以 assertthrow ,否则使用它与设备通信,然后让它随你而去完成了。


    编辑:正如 Steve Jessop 所指出的,另一个断言很有用(并且可以实现):确保当主窗口破坏 shared_ptr 时,它是最后一个所有者并且设备被释放(以防止所有权意外泄露)。

    天真的方式,在破坏之前断言它是unique,不幸的是受到竞争条件的影响;在调用unique 和实际销毁之间,调用weak_ptr::lock 可以创建一个新的所有者。

    但是,它可以很简单地完成:

    1. 从您的shared_ptr 创建一个名为monitor 的新weak_ptr
    2. 重置shared_ptr
    3. 调用monitor.lock(),如果它返回一个非空的shared_ptr,那么在野外就有一个所有者。

    【讨论】:

    • 不可能原子地测试shared_ptr 的引用计数并销毁它,是吗?我问是因为您可以做出一个潜在有用的第二个断言,即当子窗口破坏其shared_ptr 时,它不是最后一个所有者。当然,你可以断言 not unique() 然后销毁。
    • @SteveJessop:这确实很有趣,我将在答案中扩展这个断言。
    【解决方案3】:

    关于使用std::shared_ptrstd::unique_ptr 的问题主要是所有权问题之一。该对象一次只有一个所有者吗?还是该对象会有多个所有者?

    在您的情况下,您似乎想要多个所有者,所以 std::shared_ptr 是要走的路。

    不要使用std::unique_ptr,然后传递原始包装的指针。当您忘记它并且 std::unique_ptr 对象超出范围而其他人仍然可以访问原始指针时,它会回来咬您。

    【讨论】:

      【解决方案4】:

      在我看来,网络服务是一个对象 应该在您的程序的生命周期中存在。在这种情况下,它是 不确定应该使用任何类型的智能指针;这 最明显的解决方案是main 中的局部变量。在 无论如何,首先要问自己的是什么 的对象应该是。如果该生命周期对应于 main 的范围(或任何其他函数的范围),本地 变量是要走的路。如果那个生命周期应该对应 主窗口的成员变量,主窗口的成员变量 将是适当的解决方案。这一切都取决于你如何 应用程序指定对象的生命周期(这是一个设计 低级编程无法解决的问题 技术)。

      关于您可能需要使用指针的唯一情况是,如果 类型是多态的,实际类型直到 运行时(例如,因为它是由 一个配置文件)。在这种情况下,您必须管理 自己的生命周期,但如果它确实对应于一个范围, std::unique_ptr 可能是一个很好的解决方案,因为如果不移动, 它精确地模拟了作用域变量的生命周期。 (ID 在这里对std::shared_ptr 持怀疑态度;一生应该 可能是确定性的,而不取决于某人是否 碰巧持有指向它的指针。我也能想象场景 网络服务器将持有指向其客户端的指针, 有循环的风险。)

      【讨论】:

      • 感谢您指出这一点。但是,如何从 QT 窗口访问 main() 中的局部变量?我其实想过做服务singleton,但是我不想做是有原因的,不过我不记得为什么了。
      猜你喜欢
      • 2011-07-31
      • 2019-01-20
      • 1970-01-01
      • 2013-01-30
      • 2018-04-02
      • 2017-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多