我有一个后续问题: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);
}
...
};
答案告诉我,就包含的原始指针而言,析构函数中的循环定义明确,因为
unique_ptr::operator=(unique_ptr&& other)
被定义为调用 reset(other.release())
,这保证了原始指针是在 raw_pointer 之前从 other
中提取的this
持有的被删除。
我相信 cppreference 上的代码仍然是错误的,因为删除器在调用reset
删除我们正在使用的 unique_ptr whos 引用后被转移。澄清一下,我理解的 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_ptr 没有存储默认删除器?
std::unique_ptr
的移动分配的,见[unique.ptr.single.asgn]/3:
您是正确的,效果: 调用 reset(u.release()),然后调用 get_deleter() = std :: forward(u.get_deleter())。
reset
可能会间接结束
u
的生命周期,就像链表示例中的情况一样。那么
u.get_deleter()
总是有未定义的行为,不管删除器的类型如何,因为你不能在对象的生命周期之外调用非静态成员函数。托管指针是由
u
早些时候从
u.release()
释放的根本无关紧要。因此链表示例中的
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++23
auto
语法):
head = auto(std::move(ahead->next));
或者如果我am误解了“Effects:”子句的含义并且它要求实现没有UB,即使u
会通过
reset
删除,那么所有三个主要实现的行为都不符合。不过,我找不到关于此事的任何未解决的 LWG 问题。
while(head){
head.reset(head->next.release());
head.get_deleter() = std::forward<Deleter>
(head->next.get_deleter());
};
一旦head
是
reset
,
Deketer
中推断的
head->next
实例已经默认构造。只要
Deleter
是单态的并且可以简单地移动分配 -
std::default_deleter
就是这种情况 - 下一条指令是
NOOP
,这是明确定义的。但是如果
Deleter
不是单态的(即
Deleter
的实例带有不同的值)或者不是平凡的移动分配 - 由于
head->next.get_deleter()
的先前值的丢失 - 将有一个可能的 UB。这个 UB 与
Deleter
上的自我分配测试无关。由于
Deleter
正在管理推断指针的销毁,它实际上表现得像大多数其他 STL 类中的分配器。因此,
std::unique_ptr
的正确实现将在推断指针之前构造和分配
Deleter
。这个问题可能是标准缺陷报告的主题,或者至少是进一步的标准澄清恕我直言。