在这种情况下,我应该使用unique_ptr还是shared_ptr

Should I use unique_ptr or shared_ptr in this case?

本文关键字:ptr 还是 shared unique 这种情况下 我应该      更新时间:2023-10-16

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

我最好在主窗口和子窗口中使用std::shared_ptr成员变量,并在创建子窗口时传递副本,还是最好使用std::unique_ptr并传递指向子窗口的原始指针,因为主窗口无论如何都会比子窗口更长?

非常感谢!

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

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

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

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

我不认为在面对"不可能的条件"时,是否应该尝试让代码继续工作是可能的。在这种情况下,这样做非常便宜(当然,shared_ptr的复制成本比原始指针更高,但与创建子窗口相比更便宜,而且代码的结构也相同)。在某些情况下,例如在单元测试子窗口代码时,具有使"不可能条件"发生的灵活性是有用的。所以可能使用shared_ptr来获得灵活性。如果由于其他原因,强制执行生存期约束是一件大事,那么您可能不希望有任何灵活性,在这种情况下,避免编写只会隐藏错误的代码。

实际上,还有第三种选择您尚未探索。

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

第三种选择是使用weak_ptr:将两种方法结合起来

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

当子窗口需要使用设备进行通信时,他们将使用weak_ptr上的lock()来临时获得shared_ptr。然后,如果shared_ptr为空(这是一个错误),您可以断言抛出,否则可以使用它与设备通信,然后让它继续运行。


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

天真的方式,声称它在毁灭之前是unique,不幸的是,它患有种族疾病;在对unique的调用和实际销毁之间,对weak_ptr::lock的调用可以创建新的所有者。

然而,它可以简单地完成:

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

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

在您的情况下,您似乎想要多个所有者,因此std::shared_ptr是可行的。

不要 使用std::unique_ptr,然后绕过原始换行指针。当你忘记它,std::unique_ptr对象超出范围,而其他人仍然可以访问原始指针时,它会再次咬你。

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

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