移动一个唯一的指针-在cppreference上未定义的行为?



我有一个后续问题:移动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_ptrconstexpr可用的。该项目

#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问题。

相关内容

  • 没有找到相关文章

最新更新