是否可能/需要创建不可复制的共享指针类似物(以启用weak_ptr跟踪/借用类型语义)?

voi*_*oid 2 c++ c++11 c++14 c++17

问题:Unique_ptrs 很好地表达了所有权,但不能让weak_ptrs 跟踪它们的对象生命周期。Shared_ptr 可以被weak_ptr 跟踪,但不能明确表达所有权。

建议的解决方案:派生一个新的指针类型(我将其称为strong_ptr),它只是一个shared_ptr,但删除了复制构造函数和赋值运算符,因此很难克隆它们。然后,我们创建另一个新的bored_ptr类型(不易存储)来处理weak_ptr访问对象时所需的临时生命周期扩展,从而可以避免在任何地方显式使用shared_ptrs。

这个问题std::unique_ptr 的非所有权副本和这个更好的共享指针通过“所有权”和“引用”的不同类型?两者都很相似,但在这两种情况下,选择都被简单地定义为 unique_ptr 与 shared_ptr ,并且答案并没有提出我认为令人满意的解决方案。(也许我应该回答这些问题而不是问新问题?不确定在这种情况下正确的礼仪是什么。)

这是一个基本的刺击。请注意,为了避免弱指针的用户必须转换为shared_ptr才能使用它,我创建了一个bored_ptr类型(感谢rust的名字),它包装了shared_ptr,但使用户很难意外存储它。因此,通过使用不同的 hamstrung shared_ptr 衍生产品,我们可以表达预期的所有权并指导客户端代码正确使用。

#include <memory>
template <typename T>
// This owns the memory
class strong_ptr : public std::shared_ptr<T> {
public:
  strong_ptr() = default;
  strong_ptr(T* t) : std::shared_ptr<T>(t) {}
  strong_ptr(const strong_ptr&) = delete;
  strong_ptr& operator=(const strong_ptr&) = delete;
};

template <typename T>
// This can temporarily extend the lifetime but is intentionally hard to store
class borrowed_ptr : public std::shared_ptr<T> {
public:
  borrowed_ptr() = delete;
  borrowed_ptr(const borrowed_ptr&) = delete;
  borrowed_ptr& operator=(const borrowed_ptr&) = delete;

  template <typename T>
  static borrowed_ptr borrow(const std::weak_ptr<T>& wp) 
  { 
    return wp.lock();
  }
private:
  borrowed_ptr(std::shared_ptr<T> &sp) : std::shared_ptr<T>(sp) {}
};
Run Code Online (Sandbox Code Playgroud)

这看起来相当简单,是对shared_ptr的改进,但我找不到任何关于这种技术的讨论,所以我只能想象我错过了一个明显的缺陷。

谁能告诉我这是一个坏主意的具体原因吗?(是的,我知道这比 unique_ptr 效率低 - 对于 PIMPL 等我仍然会使用 unique_ptr。)

警告:除了基本示例之外,我还没有使用过它,但是它可以编译并运行正常:

struct xxx
{
  int yyy;
  double zzz;
};

struct aaa
{
  borrowed_ptr<xxx> naughty;
};

void testfun()
{
  strong_ptr<xxx> stp = new xxx;
  stp->yyy = 123;
  stp->zzz = 0.456;

  std::weak_ptr<xxx> wkp = stp;

//  borrowed_ptr<xxx> shp = wkp.lock(); <-- Fails to compile as planned
//  aaa badStruct { borrowed_ptr<xxx>::borrow(wkp) }; <-- Fails to compile as planned
//  aaa anotherBadStruct; <-- Fails to compile as planned
  borrowed_ptr<xxx> brp = borrowed_ptr<xxx>::borrow(wkp); // Only way to create the borrowed pointer

//  std::cout << "wkp: " << wkp->yyy << std::endl; <-- Fails to compile as planned
  std::cout << "stp: " << stp->yyy << std::endl; // ok
  std::cout << "bp: " << brp->yyy << std::endl; // ok
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 5

独特的所有权是独一无二的,句号。一个地方拥有该资源,并会在该代码选择时释放它。

共享所有权是共享的。多个地方可以拥有该资源,只有当所有地方都拥有该资源时,该资源才会被释放。这是一种二元状态:要么一个地方拥有该资源,要么多个地方拥有该资源。

您的所有权语义是唯一的......除非它们不是。以某种方式发挥作用的规则,除非它们不发挥作用,否则就会产生问题。

现在,您的具体实现充满了漏洞。shared/weak_ptr都是这些类型的接口的明确一部分,因此shared_ptr从strong_ptr. 如果您有 aweak_ptr到 a strong_ptr( 需要borrowed_ptr::borrow),那么您可以只使用lock它并获得shared_ptr。

但即使你的接口正确地隐藏了所有这些(也就是说,你创建了自己的weak_ptr等价类型并且停止从 继承shared_ptr),你的 API 也无法阻止某人将其存储borrowed_ptr在他们想要的任何地方。哦,当然,他们以后无法更改它,但是很容易在构造时将其存储在类成员中或堆分配一个或其他什么。

因此,归根结底,锁定弱指针仍然代表着所有权声明。因此,指针堆栈的所有权语义仍然是共享的;只是 API 鼓励不要保留共享所有权太久。

unique_ptr没有“API鼓励”;它有 API执行。这就是赋予它独特所有权的原因。C++ 没有一种机制来创建您想要创建的所有权语义的类似强制执行。

鼓励在某种程度上可能是有用的,但borrowed_ptr对于那些想要表达自己只是暂时拥有所有权的人来说,鼓励可能同样有用。否则就直接shared/weak_ptr正常使用即可。也就是说,您的 API 应该明确认识到它正在使用共享所有权,这样就不会有人被愚弄而产生其他想法。