C++ 是否可能/希望创建不可复制的共享指针模拟(以启用弱\u ptr跟踪/借用类型语义)?

C++ 是否可能/希望创建不可复制的共享指针模拟(以启用弱\u ptr跟踪/借用类型语义)?,c++,c++11,c++14,c++17,C++,C++11,C++14,C++17,问题:唯一的\u ptr很好地表达了所有权,但不能让弱的\u ptr跟踪它们的对象生命周期。共享的\u ptr可以由弱\u ptr跟踪,但不能明确表示所有权 建议的解决方案:派生一个新的指针类型,我称之为strong_ptr,它只是一个共享的_ptr,但是删除了复制构造函数和赋值运算符,因此很难克隆它们。然后,我们创建另一个新的借用的\u ptr类型,它不容易存储,无法处理弱\u ptr访问对象时所需的临时生存期扩展,因此可以避免在任何地方显式使用共享的\u ptr 这个问题和这个问题都是相似的

问题:唯一的\u ptr很好地表达了所有权,但不能让弱的\u ptr跟踪它们的对象生命周期。共享的\u ptr可以由弱\u ptr跟踪,但不能明确表示所有权

建议的解决方案:派生一个新的指针类型,我称之为strong_ptr,它只是一个共享的_ptr,但是删除了复制构造函数和赋值运算符,因此很难克隆它们。然后,我们创建另一个新的借用的\u ptr类型,它不容易存储,无法处理弱\u ptr访问对象时所需的临时生存期扩展,因此可以避免在任何地方显式使用共享的\u ptr

这个问题和这个问题都是相似的,但在这两种情况下,选择都是唯一的与共享的,答案并没有向我提出一个令人满意的解决方案。也许我应该回答这些问题,而不是问一个新问题?不确定在这种情况下正确的礼仪是什么

这里有一个基本的刺。请注意,为了避免弱指针的用户必须转换为shared_ptr才能使用它,我创建了一个借用的_ptr类型,感谢rust提供的名称,该名称包装了shared_ptr,但使用户很难意外地存储它。因此,通过使用不同的仓皇共享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) {}
};
这看起来相当简单,是对shared_ptr的改进,但我找不到任何关于这种技术的讨论,所以我只能想象我错过了一个明显的缺陷

有人能给我一个具体的理由说明为什么这是个坏主意吗?是的,我知道这比unique\u ptr效率低-对于PIMPL等,我仍然会使用unique\u 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
}

唯一的所有权是唯一的,句号。一个地方拥有这个资源,并将在代码选择时释放它

共享所有权是共享的。多个地方可以拥有此资源,只有当所有地方都拥有此资源时,才会释放此资源。这是一种二进制状态:一个地方拥有资源,或者多个地方拥有资源

您的所有权语义是唯一的。。。除非他们不是。规则以某种方式起作用,除非它们不起作用,这是有问题的

现在,您的具体实现充满了漏洞。共享/弱\u ptr都是这些类型接口的显式部分,因此从强\u ptr中获取共享\u ptr非常容易。如果借用的\u ptr::borrow需要一个弱的\u ptr到一个强的\u ptr,那么您可以锁定它并获得一个共享的\u ptr

但是,即使您的接口正确地隐藏了所有这些,也就是说,您创建了自己的弱\u ptr等价类型,并且停止了从共享\u ptr继承,您的API也无法阻止某人将借用的\u ptr存储到任何他们想要的地方。哦,当然,他们以后不能更改它,但是在构建时将它存储在类成员中或者堆分配一个或任何东西都很容易

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

unique_ptr没有API鼓励;它具有API强制执行功能。这就是它独特的所有权。C++没有创建类似的强制执行语义的机制。
在某种程度上,鼓励可能是有用的,但对于那些想表达他们只是暂时声称拥有所有权的人来说,仅仅借用ptr可能同样有用。直接使用shared/weak_ptr就可以了。也就是说,您的API应该明确地认识到它使用的是共享所有权,这样就不会有人被愚弄而产生其他想法。

唯一所有权是唯一的,句号。一个地方拥有这个资源,并将在代码选择时释放它

共享所有权是共享的。多个地方可以拥有此资源,只有当所有地方都拥有此资源时,才会释放此资源。这是一种二进制状态:一个地方拥有资源,或者多个地方拥有资源

您的所有权语义是唯一的。。。除非他们不是。规则以某种方式起作用,除非它们不起作用,这是有问题的

现在,您的具体实现充满了漏洞。共享/弱\u ptr都是这些类型接口的显式部分,因此从强\u ptr中获取共享\u ptr非常容易。如果借用的\u ptr::borrow需要一个弱的\u ptr到一个强的\u ptr,那么您可以锁定它并获得一个共享的\u ptr

但是,即使您的接口正确地隐藏了所有这一切,也就是说,您也可以创建自己的弱ptr等价类型,并且 top继承自shared_ptr,您的API无法阻止任何人将借用的_ptr存储到他们想要的任何位置。哦,当然,他们以后不能更改它,但是在构建时将它存储在类成员中或者堆分配一个或任何东西都很容易

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

unique_ptr没有API鼓励;它具有API强制执行功能。这就是它独特的所有权。C++没有创建类似的强制执行语义的机制。
在某种程度上,鼓励可能是有用的,但对于那些想表达他们只是暂时声称拥有所有权的人来说,仅仅借用ptr可能同样有用。直接使用shared/weak_ptr就可以了。也就是说,您的API应该明确地认识到它正在使用共享所有权,这样就不会有人被愚弄而产生其他想法。

共享的PTR可以被弱PTR跟踪,但不能清楚地表达所有权。-我不同意。它们清楚地表达了共享所有权。你不是说唯一的吗?你可以通过使用std::move轻松地从std::unique ptr中借用。问题/缺点是不需要编译器来阻止您使用moved from对象,同样也没有证据表明代码的其他部分没有保存指向moved from对象的引用/指针。Rust编译器内置了这些方面,这将允许的代码限制在一个更安全的子集内,但会使某些东西编写起来更麻烦。你不能用库类来修复这些C++编译器的缺点。不要明确表示所有权。什么意思?我很清楚,它们表达了共同所有权。我不清楚这是如何比共享指针有所改进的。@eerorika:OP想要一个std::shared_ptr作为拥有的资源,并强制几个std::weak_ptr作为安全的观察者。std::unique_ptrand指针的替代方案不提供安全的观察者。共享_ptrs可以由弱_ptrs跟踪,但不能明确表示所有权。-我不同意。它们清楚地表达了共享所有权。你不是说唯一的吗?你可以通过使用std::move轻松地从std::unique ptr中借用。问题/缺点是不需要编译器来阻止您使用moved from对象,同样也没有证据表明代码的其他部分没有保存指向moved from对象的引用/指针。Rust编译器内置了这些方面,这将允许的代码限制在一个更安全的子集内,但会使某些东西编写起来更麻烦。你不能用库类来修复这些C++编译器的缺点。不要明确表示所有权。什么意思?我很清楚,它们表达了共同所有权。我不清楚这是如何比共享指针有所改进的。@eerorika:OP想要一个std::shared_ptr作为拥有的资源,并强制几个std::weak_ptr作为安全的观察者。使用std::unique_ptrand指针的替代方法不提供安全的观察者。请注意,可选方法使创建常规伪借用指针变得非常简单。不规则很容易消除。使用emplace技巧,您还可以避免在可选环境中创建副本时必须使用任何副本构造函数;模板结构生成器:std::函数{operator T{return*this;}};是一个玩具,它可以让你将任何你能构造的东西放置到一个可选的组件中。@Yakk AdamNevraumont:你是如何绕过工厂功能的?emplace只能通过构造函数工作,而借用的ptr只能通过工厂函数创建。@Nicolas感谢您深思熟虑的回复;非常好的批评。我同意我需要一个包装的弱ptr来改善这一点,并且如果用户想要规避这个意图,我实际上不能强迫他们这样做,这对他们来说并不困难。我还对以下观点表示同情,即它隐藏了一个事实,即强\u ptr所有者以外的其他人可能会延长对象的生命周期,这可能会令人惊讶,因此可能只有借用的\u ptr会增加任何价值。@nicol optional.emplace maker{[&]{return borrowed\u ptr::borrowp;}请注意,一个可选选项使得创建一个常规的伪借用指针变得非常简单。不规则很容易消除。使用emplace技巧,您还可以避免在可选环境中创建副本时必须使用任何副本构造函数;模板结构生成器:std::函数{operator T{return*this;}};是一个玩具,它可以让你将任何你能构造的东西放置到一个可选的组件中。@Yakk AdamNevraumont:你是如何绕过工厂功能的?emplace只能通过构造函数工作,而借用的ptr只能通过工厂函数创建。@Nicolas感谢您深思熟虑的回复;优秀的c 批判主义。我同意我需要一个包装的弱ptr来改善这一点,并且如果用户想要规避这个意图,我实际上不能强迫他们这样做,这对他们来说并不困难。我还对以下观点表示同情,即它隐藏了一个事实,即强\u ptr所有者以外的其他人可能会延长对象的生命周期,这可能会令人惊讶,因此可能只有借用的\u ptr会增加任何价值。@nicol optional.emplace maker{[&]{return borrowed\u ptr::borrowp;}