以下代码是否调用未定义的行为

Does the following code invoke undefined behaviour?

本文关键字:未定义 调用 代码 是否      更新时间:2023-10-16

我想做这样的事情

#include <iostream>
#include <memory>
struct Foo {};
using FooPtr = std::unique_ptr<Foo>;
FooPtr makeFoo() { return FooPtr(new Foo()); }
struct Baz
{
    Baz(FooPtr foo) : Baz(std::move(foo), bar(foo)) {}
    Baz(FooPtr foo, int something) : _foo{ std::move(foo) }, _something{ something } {}
private:
    FooPtr _foo;
    int _something;
    static int bar(const FooPtr & ptr)
    {
        std::cout << "bar!" << std::endl;
        return 42;
    }
};
int main() {
    Baz baz(makeFoo());
    return 0;
}

我的问题是:函数参数求值的顺序是未指定的,那么传递一个将从一个参数中移动的值,以及调用另一个具有相同实例的函数的结果是否安全,作为对常量的引用传递,作为另一个参数?

我认为问题归结为何时执行实际的移动操作,这一点我不完全清楚(尤其是在启用优化时(。

执行 std::unique_ptr<Foo> 的移动构造函数之前,实际的"移动"不会发生(std::move()所做的只是将const FooPtr &右值转换为FooPtr &&右值引用(。在调用要委派到的双参数Baz构造函数之前,不会发生这种情况。为了实现这一点,必须首先计算该构造函数的所有参数。因此,在评估这些参数时对foo对象的任何使用都将发生在unique_ptr实例的实际"移动"之前。

由于您正在传递FooPtr(也称为按值std::unique_ptr<Foo>,并且std::unique_ptr是仅移动的,因此在向双参数构造函数评估第一个参数时,这将触发移动构造。由于未指定参数的求值顺序,因此在计算第二个论点之前可能会也可能不会发生这种移动。因此,您的示例的行为未指定的。

std::move不是

操作,而是对 r 值引用的强制转换。移动操作将在另一个Baz构造函数中发生。因此,您正在做的事情应该有效。

Scott Meyers 在他的帖子中提到了一个类似的 SO 问题,仅移动类型是否应该按值传递?似乎没有明确的答案是否应该按值传递它们,但这样做显然会导致未指定的行为,在您的情况下也是如此。

相关文章: