使用std::unique_ptr和std::shared_ptr作为虚函数的参数

3
我将设计一个基于虚方法的C++类接口,以便提供可扩展性点。许多这些公共方法需要堆分配对象作为参数。由于我正在使用现代C++模式,计划使用std::unique_ptr或std::shared_ptr来进行这项工作,但我对两者都有疑虑。
如果使用std::unique_ptr,代码看起来会像这样:
class IFoo {
  virtual void doSomethingWithUser(std::unique_ptr<User> user) = 0;
}

强制调用者提供`std::unique_ptr`有一些缺点:
- 调用者不能对提供的用户进行任何操作,因为它必须被移动 - 如果`doSomethingWithUser`的任何实现需要将用户存储在某个容器中,则无法从`std::shared_ptr`构造
对于所有公共方法使用`std::shared_ptr`可以解决问题,但我们必须付出额外的内存空间和引用计数的原子增减的代价。
是否有什么经验法则可以遵循?

2
很多这些公共方法需要堆分配的对象作为参数。嗯?那是什么意思? - Fred Larson
@FredLarson 就像上面的例子一样,用户是堆分配的,它不在栈上存活。(用户设计为不可复制,因此需要堆分配) - Stefano Azzalini
2
我不明白堆栈分配与任何事情有什么关系。你是指需要通过指针(或引用)传递而不是按值传递吗? - Fred Larson
@FredLarson 确切无误 - Stefano Azzalini
2
@steazzalini 无法复制与需要堆分配没有任何关系。我的大多数类都是栈分配的,但仍然可以移动。如果移动不是一个选项,或者您需要拥有指向值的所有权的多态性,则可能需要堆分配。 - Guillaume Racicot
1个回答

6

如果doSomethingWithUser不需要所有权,您不应将其转移给该方法。

实际上,复制共享指针或移动唯一指针有效地转移了资源的所有权。

您的函数参数应反映有关传递的资源的承担所有权的意图。

如果您只需要观察该值,并可能对其进行更改,则应将非拥有句柄传递给您的函数。

如果您需要保持资源活动并可能删除它,则应传递一个拥有句柄,无论它是共享还是独特的所有权。

在您的情况下,函数名称告诉我您需要“对用户执行某些操作”,而不包含超出调用者的生存期。因此,您应传递一个非拥有句柄。该句柄可以是User*User&,甚至可以是std::reference_wrapper<User>boost::optional<User&>,具体取决于您的需求。

您的接口确实应表达对象应该能够执行的操作,并确实强制每个函数应采用哪些参数,但是您的接口还应表达它对其参数拥有的所有权。

我通常更喜欢使用引用,因为它们确保它不可能为空,并且与自动存储中的对象很好地配合使用。有些人可能会认为他们更喜欢原始指针作为非null非拥有句柄,但我强烈反对这种做法,因为它们强制执行&object语法并允许空值。

非拥有原始指针没有任何问题。但是,在现代C++中不应使用原始拥有指针。


2
@wphicks std::weak_ptr 仍然是共享所有权,但却是一个非拥有句柄。 - Guillaume Racicot
你说得完全正确。如果有人和我一样存在误解的话,在此提供第二段参考资料。链接 - wphicks
我完全同意你的想法,@GuillaumeRacicot,但问题在于第三方实现仍然是开放的,而我没有控制权。我无法对可能的实现进行假设或设置限制。 如果某些实现决定缓存传递的用户,并且我选择使用引用,那么事情将不会顺利运行。 - Stefano Azzalini
@NirFriedman 我更新了那段话。如果这个解释更好,请告诉我。 - Guillaume Racicot
@steazzalini 我添加了一段关于这个的内容。如果需要其他信息,请告诉我。 - Guillaume Racicot
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接