这段C++代码会一直按照我的期望运行吗?还是执行顺序不是有保障的?

5

好的,我有一些代码看起来是可以工作的,但我不确定它是否总是有效。我正在使用类的一个成员作为映射键将unique_ptr移动到stl map中,但我不确定在某些情况下移动是否会使指针失效。

代码大致如下:

struct a
{
    std::string s;
};
std::map<std::string, std::unique_ptr<a>> m;
std::unique_ptr<a> p = std::make_unique<a>();

// some code that does stuff

m[p->s] = std::move(p);

这样目前似乎是可以工作的,但我认为在将字符串用作映射键之前,有可能会使 p 无效,这将导致内存异常。显然,我可以在移动之前创建一个临时字符串,或者通过迭代器进行赋值,但如果不必要,我宁愿不这样做。

2个回答

13

这段代码的行为已经被明确定义。

在C++17中,std::move(p) 将会在 m[p->s] 之前被评估。在C++17之前,std::move(p) 可以在 m[p->s] 之前或之后被评估。然而,这并不重要,因为 std::move(p) 不会修改 p。只有赋值操作才会导致 p 被移动。

所调用的赋值运算符具有如下签名:

unique_ptr& operator=(unique_ptr&& other);

并且被称为

m[p->s].operator=(std::move(p));

这意味着对p的修改保证直到进入operator=body时才会发生 (对other参数的初始化仅是引用绑定)。而且当对象表达式m[p->s]被评估后才能进入operator=的主体。

因此,您的代码在所有版本的C++中都是定义良好的。


谢谢你澄清这个问题。我希望这是可以的,因为它比其他选择更易读。 - Bill Sellers

5
代码没问题。在C++17中,我们获得了关于顺序的强保证,这使得这段代码100%正常。
在C++17之前,标准有:

在所有情况下,赋值都在右侧和左侧操作数的值计算之后进行排序,并在赋值表达式的值计算之前进行。

但这仍然意味着代码没问题。我们不知道 m[p->s]std::move(p) 中哪个先发生,但由于 move 实际上并不对 p 做任何事情,所以 p->s 将是有效的并且在将 p 移动到 m[p->s] 之前被解析。

这个引用是针对内置的赋值运算符的,不是吗? - chris
@chris 那个具体的段落适用于所有类型。 - NathanOliver
@idclev463035818 对的,std::move只是一个转换,所以它对p->s发生的事情没有影响。但是这个引用确实保证了在实际赋值(实际移动)发生之前,std::move(p)m[p->s]都将被评估。 - NathanOliver
我现在在标准中看到了上下文。看起来cppreference对这段文字的理解与我不同,这让我很感兴趣。 - chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接