尽管右侧有异常,但仍发生C ++中的赋值


73

我有一些(C ++ 14)代码看起来像这样:

map<int, set<string>> junk;
for (int id : GenerateIds()) {
    try {
        set<string> stuff = GetStuff();
        junk[id] = stuff;
    } catch (const StuffException& e) {
        ...
    }
}

这有效。有时会GetStuff()抛出一个异常,这很好用,因为如果可以,那么我就不需要垃圾地图中的值。

但是起初我是在循环中编写此代码的,这是行不通的:

junk[id] = GetStuff();

更准确地说,即使GetStuff()抛出异常,junk[id]也会被创建(并分配一个空集)。

这不是我期望的:我希望它们以相同的方式起作用。

这里有我误解的C ++原理吗?




14
只是为了向有类似问题的未来读者澄清:这里没有作业发生junk[id]创建一个new set,是的,但是使用的是默认构造函数。这就是为什么集合为空的原因。如果GetStuff()可以成功,则此空集将用作要分配给的对象。但是抛出的异常恰恰是为什么没有分配发生的原因。该集保留其默认状态。这是一个正确的C ++对象,您可以正常调用其成员。即,junk[id].size()之后将为0。
MSalters

1
@MSalters很好的澄清!我过于宽松地使用了“分配”(但是对于问题标题“-”来说,“默认构造”可能有点沉重)。
jma

2
为使此操作更加安全(和更有效),分配应为junk[id] = std::move(stuff);
大卫·哈门

Answers:


96

在C ++ 17之前,赋值运算符的左侧和右侧之间没有顺序。

在C ++ 17中,首先引入了显式排序(首先评估了右侧)。

这意味着未指定评估顺序,这取决于实现以所需顺序执行评估,具体而言,在这种情况下,它首先评估左侧。

有关更多详细信息,请参见此评估订单参考(尤其是要点20)。


4
如果我正确地理解了引用,那么可以通过删除“重载”来缩短答案中的第一句话,因为在C ++ 17中,首先针对重载和非重载赋值运算符评估右侧。
汉斯·奥尔森

17

std :: map :: operator []

返回对该值的引用,该值映射到与键等效的键,如果该键尚不存在,则执行插入。

junk[id]导致上述插入,之后已经发生GetStuff()抛出。请注意,在C ++ 14中,这些事情发生的顺序是由实现定义的,因此对于其他编译器junk[id] = GetStuff();,如果GetStuff()抛出,则可能不会插入。


12

你误会了如何operator[]在工作std::map

它返回对映射项的引用。因此,您的代码首先是在该位置插入默认项,然后调用operator=以设置新值。

要使此功能按预期方式工作,您需要使用std::map::insert(*):

junk.insert(std::make_pair(id, GetStuff()));

警告insert仅在id尚未映射时才添加值。


6
我不认为对operator[]这里的工作方式有任何误解。另外,从您的答案中还不清楚operator=,即使右侧抛出异常,为什么也要评估左侧
great_prime_is_463035818 18-11-5

如果有任何误解,对我来说,是[[b] = ...; 不只是为了[...]。
汉斯·奥尔森

8
@ user463035818:发问者表示相信发生了分配,这强烈表明他们没有意识到评估operator[]会自己创建一个空集junk[id]
user2357112支持Monica

1
赞扬了一种建议的写方法,而无需首先默认构造条目,而不管是否存在“误解”。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.