我有一个后续问题:移动unique_ptr:重置源vs.销毁旧对象
对于原始问题的快速总结,cppreference上有以下示例代码:struct List
{
struct Node
{
int data;
std::unique_ptr<Node> next;
};
std::unique_ptr<Node> head;
~List()
{
// destroy list nodes sequentially in a loop, the default destructor
// would have invoked its `next`'s destructor recursively, which would
// cause stack overflow for sufficiently large lists.
while (head)
head = std::move(head->next);
}
...
};
答案告诉我析构函数中的循环定义得很好,因为unique_ptr::operator=(unique_ptr&& other)
被定义为调用reset(other.release())
,这保证了在this
持有的raw_pointer被删除之前从other
提取原始指针。
我认为cppreference上的代码仍然是错误的,因为deleter,在之后转移了对reset
的调用删除了我们正在处理的unique_ptr对象的引用。为了澄清,我所理解的operator=的注释实现:
auto& unique_ptr<T, Deleter>::operator=(unique_ptr<T, Deleter>&& other)
{
// Here, "other" refers to a unique_ptr that lives in the T that is owned by our instance
// Thus, if we delete the raw pointer held in this instance, we delete the object that "other" references
reset(other.release()); // This correctly transfers over the raw pointer from "other", but deletes the raw pointer held in this instance, thus deleting the object "other" refers to
get_deleter() = std::forward<Deleter>(other.get_deleter()); // this should be UB, because "other" is a dangling reference
}
我的推理正确吗?这实际上是UB和cppref上的代码是错误的吗?或者这是因为使用的unique_ptrs没有存储默认删除器吗?
据我所知你是对的。当前的标准草案规定单对象std::unique_ptr
的move赋值是这样的,参见[unique.ptr.single.asgn]/3:
constexpr unique_ptr& operator=(unique_ptr&& u) noexcept;
[…]
Effects:呼叫
reset(u.release())
后接get_deleter() = std::forward<D>(u.get_deleter())
。
你是正确的,reset
可能会间接结束u
的生命周期,就像链表例子中的情况一样。那么u.get_deleter()
总是具有未定义的行为,而不管删除器的类型如何,因为您不能在对象的生命周期之外调用非静态成员函数。u.release()
之前释放了u
的托管指针,这一点都不重要。
因此链表示例中的head = std::move(head->next);
具有未定义行为。
似乎这确实是所有三大标准库实现的方式,可以用c++ 23来验证std::unique_ptr
是constexpr
可用的。该项目
#include<memory>
struct A {
std::unique_ptr<A> a;
};
consteval void f() {
auto a = std::make_unique<A>(std::make_unique<A>());
a = std::move(a->a);
}
int main() {
f();
}
无法在Clang libstdc++, Clang libc++和MSVC中编译,原因是在常量表达式中访问超出其生命周期的对象。(GCC似乎对常量表达式中的UB更宽容。没有指定是否检测到库函数中的UB,所以没关系。)
我可能误解了Effects:"应该在标准中解释,但这对我来说似乎是一个缺陷。这种行为非常出乎意料。我认为应该有可能以一种有效的方式指定这一点,例如,首先将u
移动到临时副本,然后按当前规则交换或移动分配。这也可以作为一种解决方法(这里使用c++ 23auto
语法):
head = auto(std::move(ahead->next));
或者如果我我误解了">效果"的含义:;子句,并且它要求实现不具有UB,即使u
将通过reset
删除,那么所有三个主要实现都不符合。
我找不到任何关于这个问题的公开的LWG问题。