weak_ptr
不会增加所管理对象的引用计数,因此它不表示所有权。它只是让您访问由其他人管理的对象,该对象的生存期由其他人控制。因此,我不明白为什么可以从
shared_ptr
构造weak_ptr
,但不能从unique_ptr
构造。有人能简要解释一下吗?
weak_ptr
不会增加所管理对象的引用计数,因此它不表示所有权。它只是让您访问由其他人管理的对象,该对象的生存期由其他人控制。shared_ptr
构造weak_ptr
,但不能从unique_ptr
构造。如果您仔细想想,weak_ptr
必须引用除对象本身以外的某些内容。这是因为当没有强指针指向该对象时,对象可能会停止存在,而weak_ptr
仍然必须引用一些包含信息的东西,表明对象不再存在。
对于shared_ptr
而言,这个东西就是包含引用计数的内容。但是对于unique_ptr
而言,没有引用计数,因此没有包含引用计数的东西,因此当对象消失时也就没有其他东西可以继续存在。所以weak_ptr
就没有要引用的内容。
使用这样的weak_ptr
也没有合理的方法。为了使用它,您必须有某种方式来保证在您使用它的同时对象不会被销毁。对于shared_ptr
很容易做到这一点 - 这就是shared_ptr
的工作方式。但是如何使用unique_ptr
来实现这一点呢?您显然不能拥有两个,其他某个东西必须已经拥有对象,否则由于您的指针是弱的,对象将被销毁。
std::weak_ptr
必须通过lock()
转换为std::shared_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_ptr
和只有一个引用的shared_ptr
之间有什么区别呢?又或者说,通过使用get
获得的常规unique_ptr
和C指针之间有什么区别呢?
weak_ptr
并不适用于“一般的非拥有资源”,它有着非常具体的作用——主要目标是防止shared_ptr
循环指向导致内存泄漏。其他任何事情都需要使用普通的unique_ptr
和shared_ptr
来完成。
shared_ptr
或weak_ptr
对象引用它。为此,引用计数对象保持两个引用计数,一个是强引用,一个是弱引用。只有当这两个计数都为零时,引用计数对象才会被删除。这意味着在所有弱引用消失之后,部分内存才能被释放(这意味着使用make_shared
存在隐藏的缺点)。
简而言之:weak_ptr
依赖于shared_ptr
中的弱引用计数,没有shared_ptr
就不能有weak_ptr
。weak_ptr
是指向引用计数对象的 shared_ptr
。 - David Schwartz从概念上讲,没有什么阻止一个实现只提供访问权限的 weak_ptr 和一个控制生命周期的 unique_ptr。然而,存在以下问题:
unique_ptr
一开始就不使用引用计数。添加管理弱引用的管理结构是可能的,但需要额外的动态分配。由于 unique_ptr
应该避免任何指针原始指针之上的运行时开销,因此这种开销是不可接受的。weak_ptr
引用的对象,您需要从中提取一个“真实”的引用,首先要验证指针是否已过期,然后给您这个真实的引用(在这种情况下是一个shared_ptr
)。这意味着您突然有了第二个引用指向一个应该是唯一拥有的对象,这是错误的原因。这不能通过返回一个混合半强指针来修复,因为它只会暂时延迟指针所指向的对象的可能销毁,因为您同样可以存储该指针,从而破坏 unique_ptr
的想法。只是好奇,你在这里尝试解决什么问题,使用一个 weak_ptr
?
unique_ptr
。对于初学者来说,大多数人会写出简单的代码,但与原始指针相比,它们是巨大的。经过几次聪明地使用 noexcept
和移动语义,代码仍然不够高效。Stroustrup 知道他在谈论什么。我无法从我听他谈论 C++ 的几个小时中找到确切的分钟数。这是最近几年在 CppCon 上的一次演讲。编译器将消除所有开销,这是一个不错的猜测,但并不是真的。 - user904963到现在为止,还没有人提到问题的性能方面,所以让我来谈一下我的看法。
weak_ptr
必须要知道对应的 shared_ptr
什么时候都超出了作用域并且指向的对象已被释放和销毁。这意味着,shared_ptr
需要以某种方式向每个 weak_ptr
发送有关相同对象的销毁通知。这会产生一定的成本 - 例如,需要更新全局哈希表,其中 weak_ptr
获取地址(如果对象被销毁,则获取 nullptr
)。
这也涉及到多线程环境中的锁定,因此对于某些任务而言可能过于缓慢。
然而,unique_ptr
的目标是提供一个零代价的 RAII 风格的抽象类。因此,它不应该产生除删除动态分配对象(使用 delete
或 delete[]
)之外的任何其他成本。例如执行锁定或受保护的哈希表访问所造成的延迟可能与回收成本相当,但这对于 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>
shared_ptr
本身。至少可以使用typedef
来解决这个问题。Stroustrup在这里讨论了关于observer_ptr
重新发明轮子的类似问题:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1408r0.pdf。 - c z我用一个实现单个对象上的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;
}
区分喜欢使用unique_ptr
而不是shared_ptr
的原因可能会很有用。
性能 一个明显的原因是计算时间和内存使用。目前定义的shared_ptr
对象通常需要像引用计数值这样的东西,这占用了空间并且还必须得到积极维护。unique_ptr
对象则没有此问题。
语义 使用unique_ptr
时,作为程序员,您对指向的对象何时将被销毁有很好的了解:当unique_ptr
被销毁或调用其修改方法之一时。因此,在大型或不熟悉的代码库中,使用unique_ptr
静态地传递(并强制执行)有关程序运行时行为的一些信息,这可能不太明显。
上面的注释通常集中在那些基于性能的原因上,避免将weak_ptr
对象与unique_ptr
对象绑定在一起。但是,人们可能会想知道语义上的论据是否足以成为STL的某个未来版本支持原始问题提出的用例的足够理由。
不幸的是,与许多情况一样,这是因为C++委员会并不关心并驳回了这种用例。
现状:
weak_ptr
是以shared-ptr为基础来进行规范的,从而排除了任何试图使其成为更广泛有用的智能指针的尝试。在C++中,概念上,弱指针是一个非拥有指针,必须转换为shared_ptr
才能访问底层对象。由于unique_ptr
不支持任何形式的引用计数(因为根据定义它是唯一所有者),因此不允许将weak_ptr
转换为具有任何形式所有权的指针。
很遗憾,现在已经太晚了,无法获得良好命名的智能指针,它们可以提供更多的通用性。
但您可以创建类似于以下内容:
为了保证安全,您需要自己创建一个删除器(unique_ptr类型中包含删除器),以及一个新的非拥有型unique_ptr_observer,用于更改删除器。创建观察器时,它会将清理处理程序注册为删除器。这样,您就可以拥有一个unique_ptr_observer,它可以检查线程安全性是否仍然存在问题,因为您需要锁定机制、创建副本进行读取,或者其他方式来防止在您正在主动查看指针时删除指针。(真是让人烦恼,删除器居然是类型的一部分......)
observer_ptr
没有...做任何事情?它基本上只是语法糖,为那些不喜欢非拥有原始指针的人提供便利? - The Paramagnetic Croissant