繁体   English   中英

为什么不能从 unique_ptr 构造 weak_ptr?

[英]Why can't a weak_ptr be constructed from a unique_ptr?

如果我理解正确, weak_ptr不会增加托管 object 的引用计数,因此它不代表所有权。 它只是让您访问一个 object,其生命周期由其他人管理。 所以我真的不明白为什么weak_ptr不能从unique_ptr构造,而只能从shared_ptr构造。

有人可以简要解释一下吗?

如果你考虑一下, weak_ptr必须引用除了对象本身之外的东西。 那是因为对象可以不再存在(当没有更强的指针时), weak_ptr仍然需要引用包含对象不再存在的信息的东西。

使用shared_ptr ,包含引用计数的东西。 但是对于unique_ptr ,没有引用计数,因此没有包含引用计数的东西,因此当对象消失时没有任何东西可以继续存在。 所以没有什么可以引用weak_ptr

也没有理智的方法来使用这样的weak_ptr 要使用它,您必须有一些方法来保证在您使用它时不会破坏对象。 使用shared_ptr很容易 - 这就是shared_ptr功能。 但是你如何用unique_ptr做到这一点? 你显然不能拥有其中的两个,并且其他东西必须已经拥有该对象,否则它将被破坏,因为你的指针很弱。

除非通过lock()将它转换为std::shared_ptr ,否则不能使用std::weak_ptr 如果标准允许你建议,那意味着你需要将std :: weak_ptr转换为唯一才能使用它,违反唯一性(或重新发明std::shared_ptr

为了说明,请看两段代码:

std::shared_ptr<int> shared = std::make_shared<int>(10);
std::weak_ptr<int> weak(shared);

{
*(weak.lock()) = 20; //OK, the temporary shared_ptr will be destroyed but the pointee-integer still has shared  to keep it alive
}

现在提出你的建议:

std::unique_ptr<int> unique = std::make_unique<int>(10);
std::weak_ptr<int> weak(unique);

{
*(weak.lock()) = 20; //not OK. the temporary unique_ptr will be destroyed but unique still points at it! 
}

有人说,你可能会建议只有一个unique_ptr ,你仍然可以取消引用weak_ptr (不创建另一个unique_ptr )然后没有问题。 但是, unique_ptrshared_ptr与一个引用有什么区别? 或者,使用get的常规unique_ptr和C指针有什么区别?

weak_ptr不适用于“一般非常规资源”,它有一个非常具体的工作 - weak_ptr的主要目标是防止循环指向shared_ptr ,这将导致内存泄漏。 还需要使用plain unique_ptrshared_ptr完成其他任何工作。

shared_ptr基本上有两部分:

  1. 指向的对象
  2. 引用计数对象

一旦引用计数降至零,就删除对象(#1)。

现在, weak_ptr需要能够知道对象是否仍然存在。 为了做到这一点,它必须能够看到引用计数对象(#2),如果它不为零,它可以为对象创建一个shared_ptr (通过递增引用计数)。 如果计数为零,则返回空的shared_ptr

现在考虑何时可以删除引用计数对象(#2)? 我们必须等到没有shared_ptr或者weak_ptr对象引用它。 为此目的,引用计数对象包含两个引用计数,一个引用和一个引用。 引用计数对象仅在其计数均为零时才会被删除。 这意味着只有在所有弱引用消失后才能释放部分内存(这意味着make_shared隐藏了一个缺点 )。

TL;博士; weak_ptr取决于弱引用计数是的一部分shared_ptr ,不能有一个weak_ptr没有shared_ptr

从概念上讲,没有什么能阻止一个实现,其中weak_ptr只提供访问权限,而unique_ptr控制生命周期。 但是,有一些问题:

  • unique_ptr不使用引用计数开头。 添加用于管理弱引用的管理结构是可能的,但需要额外的动态分配。 由于unique_ptr应该避免原始指针上的任何(!)运行时开销,因此该开销是不可接受的。
  • 为了使用weak_ptr引用的对象,你需要从中提取一个“真实”引用,它首先会验证指针是否先过期,然后给你这个真正的引用(在这种情况下是一个shared_ptr )。 这意味着您突然有一个对应该唯一拥有的对象的第二个引用,这是一个错误的配方。 这不能通过返回一个混合的半强指针来解决,该指针只会暂时延迟对指针的可能破坏,因为你也可以存储那个,同时击败unique_ptr背后的想法。

只是想知道,你在这里使用weak_ptr试图解决什么问题?

没有人提到问题的性能方面,所以让我扔掉0.02美元。

weak_ptr必须以某种方式知道对应的shared_ptr何时全部超出范围并且指向的对象已被解除分配和销毁。 这意味着shared_ptr需要以某种方式将对每个weak_ptr的破坏传递给同一个对象。 这有一定的成本 - 例如,需要更新全局哈希表,其中weak_ptr从中获取地址(如果对象被销毁,则为nullptr )。

这还涉及锁定多线程环境,因此对于某些任务来说可能太慢。

但是, unique_ptr的目标是提供零成本的 RAII样式抽象类。 因此,除了delete (或delete[]动态)动态分配的对象之外,不应产生任何其他成本。 例如,通过执行锁定或以其他方式保护的哈希表访问所施加的延迟可以与解除分配的成本相当,但是在unique_ptr的情况下这是不期望的。

看起来每个人都在这里写关于std :: weak_ptr但不是关于弱poiner概念,我相信是作者要求的

我认为没有人提到为什么标准库没有为unique_ptr提供weak_ptr。 弱指针CONCEPT不会拒绝unique_ptr用法。 弱指针只是一个信息,如果该对象已经被删除,所以它不是一个神奇但非常简单的广义观察者模式。

这是因为线程安全并且与shared_ptr保持一致。

您只是不能保证在尖端对象的调用方法期间不会破坏您的weak_ptr(从其他线程上存在的unique_ptr创建)。 这是因为weak_ptr需要与std :: shared_ptr保持一致,以保证线程安全。 您可以实现与unique_ptr一起正常工作的weak_ptr,但只能在同一个线程上 - 在这种情况下不需要锁定方法。 您可以检查base :: WeakPtr和base :: WeakPtrFactory的铬源 - 您可以使用unique_ptr自由使用它。 Chromium弱指针代码很可能是基于最后一个成员销毁 - 你需要添加工厂作为最后一个成员,之后我相信WeakPtr被告知对象删除(我不是100%肯定) - 所以它看起来不是很难实现。

总体上使用带有弱指针概念的unique_ptr是可以的恕我直言。

这可能是区分原因,宁愿一个有用unique_ptrshared_ptr

性能一个明显的原因是计算时间和内存使用。 正如当前定义的那样, shared_ptr对象通常需要类似于引用计数值的东西,这需要占用空间并且还必须主动维护。 unique_ptr对象没有。

语义使用unique_ptr ,作为程序员,当指向对象将被销毁时,你有一个很好的主意:当unique_ptr被销毁或者其中一个修改方法被调用时。 等等大型或不熟悉的代码库,使用unique_ptr静态传达(并强制执行)有关程序运行时行为的一些信息,这些信息可能并不明显。

上面的注释通常集中在基于性能的原因上,即weak_ptr对象与unique_ptr对象绑定是不可取的。 但有人可能想知道基于语义的论证是否足以让STL的某些未来版本支持原始问题隐含的用例。

经过多年的c++编程工作,我终于意识到c++世界的正确方法是永远不要使用shared_ptr/weak_ptr。 我们花了很多时间来修复由于 share_ptr 的所有权不明确导致的错误。

解决方案是使用 unque_ptr 和一些用于 unique_ptr 的弱指针,正如本主题所期望的那样。

cpp标准库没有unique_ptr的弱指针,所以我在这里创建了一个非常简单但有用的库——

https://github.com/xhawk18/noshared_ptr

它有两个新的智能指针,

noshared_ptr<T>, a new kind of unique_ptr
noweak_ptr<T>, the weak pointer for noshare_ptr<T>

我用在单个对象上实现weak_ptr的MWE 向自己展示了这个问题。 (我在这里在X上实现它,但X可以是任何可以告诉我们它何时死亡的东西,例如带有自定义删除器的unique_ptr )。

然而最终的问题是,在某些时候我们需要对弱指针本身进行引用计数,因为虽然X不是共享的,但弱指针是共享的。 这让我们又回到了使用shared_ptr的整个过程。

也许这样做的唯一好处是单一所有权意图更明确并且不能被违反,但是, 正如 Stroustrup 建议并引用了这个答案,这可以通过using语句来暗示。

#include <iostream>
#include <memory>

template<typename T>
struct ControlBlock
{
    T* Target;
    explicit ControlBlock(T* target) : Target(target) {}
};

template<typename T>
struct WeakReference
{
    std::shared_ptr<ControlBlock<T>> ControlBlock;
    T* Get() { return ControlBlock ? ControlBlock->Target : nullptr; }
};

template<typename T>
struct WeakReferenceRoot
{
    WeakReference<T> _weakRef;
    WeakReferenceRoot(T* target) : _weakRef{std::make_shared<ControlBlock<T>>(target)} { }
    const WeakReference<T>& GetReference() { return _weakRef; }
    ~WeakReferenceRoot() { _weakRef.ControlBlock->Target = nullptr; }
};

struct Customer
{
    WeakReferenceRoot<Customer> Weak{this};
};

int main() {
    WeakReference<Customer> someRef;
    std::cout << "BEFORE SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
    {
        Customer obj{};
        someRef = obj.Weak.GetReference();
        std::cout << "IN SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
    }
    std::cout << "OUT OF SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
    return 0;
}

可悲的是,与许多案例一样 - 因为 C++ 委员会根本不关心并驳回了这些用例。

它是怎么回事: weak_ptr是根据共享指针指定的,排除了使它成为更广泛有用的智能指针的任何尝试。 在 C++ 中,weak-ptr 在概念上是一个非拥有指针,必须将其转换为shared_ptr才能访问底层 object。并且unique_ptr不支持任何类型的引用计数(因为根据定义它是唯一所有者)转换指向具有任何所有权的指针的weak_ptr是不允许的。 遗憾的是,要获得提供更多通用性的好名字的智能指针有点太晚了。

但是你可以创建这样的东西:

为了安全起见,您需要自己的删除器(unique_ptr 具有类型中的删除器),以及一个新的非拥有unique_ptr_observer来更改删除器。 创建观察者时,它会将清理处理程序注册为删除程序。 这样你就可以有一个unique_ptr_observer来检查线程安全是否仍然是一个问题,因为你需要一个锁定机制,创建用于读出的副本,或者其他一些方法来防止指针在你正在积极查看时被删除它。 (删除器是类型的一部分真是太烦人了。。。。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM