Wut*_*utz 23 c++ unique-ptr language-lawyer move-semantics
我对此有一个后续问题:Move unique_ptr: Reset the source vs. destroy the old object
为了快速总结原始问题,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);
}
...
};
Run Code Online (Sandbox Code Playgroud)
答案告诉我,析构函数中的循环就所包含的原始指针而言是明确定义的,因为unique_ptr::operator=(unique_ptr&& other)
被定义为 call ,这保证了在删除所持有的 raw_pointer 之前reset(other.release())
提取原始指针。other
this
我相信 cppreference 上的代码仍然是错误的,因为删除器在调用删除我们正在使用的 unique_ptr whos 引用后被传输。reset
为了澄清这一点,根据我的理解,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
}
Run Code Online (Sandbox Code Playgroud)
我的推理正确吗?这实际上是 UB 并且 cppref 上的代码是错误的吗?或者这可以吗,因为使用的 unique_ptr 没有存储默认删除器?
use*_*522 19
据我所知你是正确的。当前的标准草案指定了单对象std::unique_ptr
的移动分配,如下所示,请参阅[unique.ptr.single.asgn]/3:
\n\n\n
constexpr unique_ptr& operator=(unique_ptr&& u) noexcept;
[...]
\n效果:
\nreset(u.release())
调用后接get_deleter() = std\xe2\x80\x8b::\xe2\x80\x8bforward<D>(u.get_deleter())
.
您是正确的,有可能会间接结束链表示例中的reset
生命周期。u
然后,u.get_deleter()
无论删除器的类型如何,总是具有未定义的行为,因为您无法在对象的生命周期之外调用非静态成员函数。托管指针u
之前被释放u.release()
根本不重要。
因此head = std::move(head->next);
在链表示例中具有未定义的行为。
看起来这确实是所有三大标准库实现的实现方式,可以使用 C++23 进行验证,其中std::unique_ptr
可用constexpr
。该程序
#include<memory>\n\nstruct A {\n std::unique_ptr<A> a;\n};\n\nconsteval void f() {\n auto a = std::make_unique<A>(std::make_unique<A>());\n a = std::move(a->a);\n}\n\nint main() {\n f();\n}\n
Run Code Online (Sandbox Code Playgroud)\n由于在常量表达式中访问超出其生命周期的对象,无法使用 Clang libstdc++、Clang libc++ 和 MSVC 进行编译。(GCC似乎对常量表达式中的UB比较宽松,是否检测库函数中的UB未明确,所以可以。)
\n我可能误解了标准中“效果: ”的解释方式,但这对我来说似乎是一个缺陷。这种行为是非常出乎意料的。我认为应该可以以一种有效的方式指定这一点,例如u
首先移动到临时副本,然后按照当前规则进行交换或移动分配。这也可以用作解决方法(此处使用 C++23auto
语法):
head = auto(std::move(ahead->next));\n
Run Code Online (Sandbox Code Playgroud)\n或者,如果我误解了“ Effects: ”子句的含义,并且它要求实现没有 UB,即使u
会通过 删除reset
,那么所有三个主要实现的行为都不符合要求。
不过,我在这个问题上找不到任何公开的 LWG 问题。
\n