28

考虑到以下几点:

std::string make_what_string( const std::string &id );

struct basic_foo
{
    basic_foo( std::string message, std::string id );
};

struct foo
    : public basic_foo
{
    foo::foo( std::string id)
        : basic_foo( make_what_string( id ), std::move( id ) ) // Is this valid?
    {
    }
};

因为 C++ 中的参数评估顺序未指定,所以我想知道该行是否

basic_foo( make_what_string( id ), std::move( id ) )

在上面的代码中是有效的。

我知道这std::move只不过是一个演员,但是什么时候执行 std::string move ctor?在评估完所有参数之后,是时候调用基本构造函数了吗?或者这是在评估参数期间完成的?换句话说:

编译器是否这样做:

std::string &&tmp2 = std::move(id);
std::string tmp1 = make_what_string(id);
basic_foo(tmp1, tmp2);

这是有效的。或这个:

std::string tmp2 = std::move(id);
std::string tmp1 = make_what_string(id);
basic_foo(tmp1, tmp2);

这是无效的。请注意,在这两种情况下,订单都是“意外”订单。

4

2 回答 2

20

请参阅第 1.9 节:

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。

调用函数时(无论该函数是否内联),与任何参数表达式或与指定被调用函数的后缀表达式相关的每个值计算和副作用都在执行主体中的每个表达式或语句之前进行排序称为函数。[注意:与不同参数表达式相关的值计算和副作用是无序的。——<em>尾注]

我认为问题在于,参数的初始化是否被认为是与参数表达式相关的副作用还不是很清楚。但是,它似乎得到了第 5.2.2 节的支持:

每个参数的初始化和销毁​​发生在调用函数的上下文中。

同一段中还有一个注释,使它更清楚一点:

调用函数时,每个参数 (8.3.5) 都应使用其对应的参数进行初始化 (8.5, 12.8, 12.1)。[注意:此类初始化相对于彼此的顺序是不确定的(1.9)-结束注释]

所以是的,参数的初始化相对于彼此是不确定的。初始化可能按以下任一顺序进行:

std::string message = make_what_string(id);
std::string id = std::move( id );

std::string id = std::move( id );
std::string message = make_what_string(id);

在第二种情况下,make_what_string最终使用移出的字符串。

因此,即使std::move实际上没有移动任何东西,重要的是实际移动也相对于另一个参数是无序的。

状态移动构造函数的定义basic_string(basic_string&& str)

[...]str处于未指定值的有效状态。

所以你没有未定义的行为,你有未指定的行为。

于 2013-03-28T11:43:15.457 回答
7

这不是真的有效。未指定函数参数评估的顺序。换句话说,你不知道编译器是否会选择这个序列:

tmp1 = make_what_string(id);
tmp2 = std::move(id);
basic_foo(tmp1, tmp2);

或者这个:

tmp1 = std::move(id);
tmp2 = make_what_string(id);  //id has already been moved from!
basic_foo(tmp2, tmp1);
于 2013-03-28T11:32:00.793 回答