简体   繁体   English

奇怪的std :: map行为

[英]Strange std::map behaviour

The following test program 以下测试程序

#include <map>
#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    map<int,int> a;
    a[1]=a.size();
    for(map<int,int>::const_iterator it=a.begin(); it!=a.end(); ++it)
            cout << "first " << (*it).first << " second " << (*it).second << endl;
}

leads to different output when compiled on g++ 4.8.1 (Ubuntu 12.04 LTS): g++ 4.8.1 (Ubuntu 12.04 LTS)上编译时导致不同的输出:

g++ xxx.cpp 
./a.out 
first 1 second 1

and on Visual Studio 2012 (Windows 7) (Standard Win32 Console Application Project): 在Visual Studio 2012(Windows 7)上(标准Win32控制台应用程序项目):

ConsoleApplication1.exe
first 1 second 0

Which compiler is right? 哪个编译器是对的? Am I doing something wrong? 难道我做错了什么?

This is actually a well-formed program that has two equally valid execution paths, so both compilers are right. 这实际上是一个结构良好的程序,它有两个同样有效的执行路径,因此两个编译器都是正确的。

a[1] = a.size()

In this expression, the evaluation of the two operands of = are unsequenced. 在这个表达式中,对=的两个操作数的评估是未经测序的。

§1.9/15 [intro.execution] Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. §1.9/ 15 [intro.execution]除非另有说明, 否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的

However, function calls are not interleaved, so the calls to operator[] and size are actually indeterminately sequenced , rather than unsequenced. 但是,函数调用不是交错的,因此对operator[]size的调用实际上是不确定的 ,而不是无序的。

§1.9/15 [intro.execution] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function. §1.9/ 15 [intro.execution]调用函数中的每个评估(包括其他函数调用)在执行被调用函数体之前或之后没有特别顺序排列,就被调用函数的执行而言是不确定的顺序功能。

This means that the function calls may happen in one of two orders: 这意味着函数调用可能以两个顺序之一发生:

  1. operator[] then size operator[]然后size
  2. size then operator[] size然后operator[]

If a key doesn't exist and you call operator[] with that key, it will be added to the map, thereby changing the size of the map. 如果某个键不存在,并且您使用该键调用operator[] ,它将被添加到地图中,从而更改地图的大小。 So in the first case, the key will be added, the size will be retrieved (which is 1 now), and 1 will be assigned to that key. 因此,在第一种情况下,将添加密钥,将检索大小(现在为1),并且将为该密钥分配1 In the second case, the size will be retrieved (which is 0), the key will be added, and 0 will be assigned to that key. 在第二种情况下,将检索大小(为0),将添加密钥,并将0分配给该密钥。

Note, this is not a situation that brings about undefined behaviour. 请注意,这不是导致未定义行为的情况。 Undefined behaviour occurs when two modifications or a modification and a read of the same scalar object are unsequenced. 如果未对两个修改或修改以及对同一标量对象的读取进行排序,则会发生未定义的行为。

§1.9/15 [intro.execution] If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. §1.9/ 15 [intro.execution]如果标量对象上的一个副作用相对于同一个标量对象上的另一个副作用或使用相同标量对象的值进行的值计算未被排序 ,则该行为未被定义。

In this situation, they are not unsequenced but indeterminately sequenced. 在这种情况下,它们没有被排除,但是不确定地排序。

So what we do have is two equally valid orderings of the execution of the program. 所以我们所拥有的是两个同样有效的程序执行顺序。 Either could happen and both give valid output. 两者都可能发生,并且都给出有效的输出。 This is unspecified behaviour . 这是未指定的行为

§1.3.25 [defns.unspecified] §1.3.25[defns.unspecified]
unspecified behavior 不明确的行为
behavior, for a well-formed program construct and correct data, that depends on the implementation 行为,对于格式良好的程序构造和正确的数据,取决于实现


So to answer your questions: 那么回答你的问题:

Which compiler is right? 哪个编译器是对的?

Both of them are. 他们两个都是。

Am I doing something wrong? 难道我做错了什么?

Probably. 大概。 It's unlikely that you would want to write code that has two execution paths like this. 您不太可能想要编写具有两个这样的执行路径的代码。 Unspecified behaviour can be okay, unlike undefined behaviour, because it can be resolved to a single observable output, but it's not worth having in the first place if you can avoid it. 与未定义的行为不同,未指定的行为可以是正常的,因为它可以解析为单个可观察的输出,但如果可以避免它,则首先不值得拥有。 Instead, don't write code that has this kind of ambiguity. 相反,不要编写具有这种模糊性的代码。 Depending on what exactly you want correct path to be, you can do either of the following: 根据您想要的正确路径,您可以执行以下任一操作:

auto size = a.size();
a[1] = size; // value is 0

Or: 要么:

a[1];
a[1] = a.size(); // value is 1

If you want the result to be 1 and you know the key doesn't yet exist, you could of course do the first code but assign size + 1 . 如果您希望结果为1并且您知道密钥尚不存在,那么您当然可以执行第一个代码,但指定size + 1

In this case, where a[1] returns a primitive type, please refer to this answer . 在这种情况下,如果a[1]返回基本类型,请参考这个答案 In the case in which the std::map 's value type is an user defined type and operator=(T, std::size_t) is defined for that type, the expression: std::map的值类型是用户定义类型并且为该类型定义operator=(T, std::size_t)情况下,表达式:

a[1] = a.size();

can be converted to the corresponding less-syntactic-sugar version: 可以转换为相应的less-syntactic-sugar版本:

a[1] = a.size();
a.operator[](1) = a.size();
operator=(a.operator[](1), a.size());

And, as we all know from the §8.3.6/9: 而且,正如我们从§8.3.6/ 9中所知道的那样:

The order of evaluation of function arguments is unspecified. 函数参数的评估顺序未指定。

which leads to the fact that the result of the above expression is unspecified . 这导致上述表达式的结果未指定

We have, of course, two cases: 当然,我们有两种情况:

  • If the a.operator[](1) is evaluated first, the size of the map is incremented by 1 leading to the first output ( first 1 second 1 ). 如果首先评估a.operator[](1) ,则地图的大小将增加1,从而导致第一个输出( first 1 second 1 )。
  • If the a.size() is evaluated first, the output you'll get is the second one ( first 1 second 0 ). 如果首先计算a.size() ,那么你得到的输出是第二个( first 1 second 0 )。

This is known as a sequence-point issue which means certain operations may be performed in any order chosen by the compiler. 这被称为序列点问题,这意味着可以按编译器选择的任何顺序执行某些操作。

If one has side-effects on the other, it is called "unspecified behaviour" a bit like "undefined behaviour" however where the result must be one of a fixed subset of outcomes, so here it must be either 0 or 1 and can't be any other value. 如果一个人对另一个人有副作用,那么它被称为“未指定的行为”,有点像“未定义的行为”,但是结果必须是结果的固定子集之一,所以这里必须是0或1并且可以'是任何其他价值。 In reality you should usually avoid doing it. 实际上你通常应该避免这样做。

In your particular case. 在你的特殊情况下。 performing operator [] on a map changes its size (if that element does not yet exist). 在地图上执行operator []改变其大小(如果该元素尚不存在)。 Thus it has a side effect on the right hand side of what it is assigning to it. 因此,它在分配给它的右侧有副作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM