[英]Understanding an overloaded operator[] example
I am confused with a question that I saw in a c++ test. 我对在c ++测试中看到的问题感到困惑。 The code is here:
代码在这里:
#include <iostream>
using namespace std;
class Int {
public:
int v;
Int(int a) { v = a; }
Int &operator[](int x) {
v+=x;
return *this;
}
};
ostream &operator<< (ostream &o, Int &a) {
return o << a.v;
}
int main() {
Int i = 2;
cout << i[0] << i[2]; //why does it print 44 ?
return 0;
}
I was kinda sure that this would print 24
but instead it prints 44
. 我确信这会打印
24
但是打印44
。 I would really like someone to clarify this. 我真的希望有人澄清一下。 Is it a cumulative evaluation?
这是累积评估吗? Also is the
<<
binary infix ? 还是
<<
二进制中缀 ?
Thanks in advance 提前致谢
EDIT : in case of not well defined operator overload, could someone give a better implementation of the overloaded operators here so it would print 24
? 编辑 : 如果没有明确定义的运算符重载,有人可以在这里更好地实现重载运算符,所以它会打印
24
?
This program has indeterminate behavior: the compiler is not required to evaluate i[0]
and i[2]
in a left-to-right order (the C++ language gives this freedom to compilers in order to allow for optimizations). 该程序具有不确定的行为:编译器不需要以从左到右的顺序评估
i[0]
和i[2]
(C ++语言为编译器提供了这种自由以便进行优化)。
For instance, Clang does it in this instance , while GCC does not . 例如, Clang在这种情况下做 ,而GCC没有 。
The order of evaluation is unspecified, so you cannot expect a consistent output, not even when running your program repeatedly on a specific machine. 评估顺序未指定,因此即使在特定计算机上重复运行程序,也无法获得一致的输出。
If you wanted to get consistent output, you could rephrase the above as follows (or in some equivalent way): 如果您想获得一致的输出,可以按照以下方式(或以某种等效的方式)重新表达上述内容:
cout << i[0];
cout << i[2];
As you can see, GCC no longer outputs 44
in this case. 如您所见,GCC在这种情况下不再输出
44
。
EDIT: 编辑:
If, for whatever reason, you really really want the expression cout << i[0] << i[2]
to print 24
, you will have to modify the definition of the overloaded operators ( operator []
and operator <<
) significantly, because the language intentionally makes it impossible to tell which subexpression ( i[0]
or [i2]
) gets evaluated first. 无论出于何种原因,如果你真的希望表达式
cout << i[0] << i[2]
打印24
,你将不得不显着修改重载运算符( operator []
和operator <<
)的定义,因为语言故意无法判断哪个子表达式( i[0]
或[i2]
)首先被评估。
The only guarantee you get here is that the result of evaluating i[0]
is going to be inserted into cout
before the result of evaluating i[2]
, so your best bet is probably to let operator <<
perform the modification of Int's
data member v
, rather than operator []
. 你得到的唯一保证是评估
i[0]
的结果将在评估i[2]
的结果之前插入到cout
,所以你最好的选择可能是让operator <<
执行Int's
数据的修改成员v
,而不是operator []
。
However, the delta that should be applied to v
is passed as an argument to operator []
, and you need some way of forwarding it to operator <<
together with the original Int
object. 但是,应该应用于
v
的delta作为参数传递给operator []
,并且您需要某种方式将它与原始Int
对象一起转发给operator <<
。 One possibility is to let operator []
return a data structure that contains the delta as well as a reference to the original object: 一种可能性是让
operator []
返回一个包含delta的数据结构以及对原始对象的引用:
class Int;
struct Decorator {
Decorator(Int& i, int v) : i{i}, v{v} { }
Int& i;
int v;
};
class Int {
public:
int v;
Int(int a) { v = a; }
Decorator operator[](int x) {
return {*this, x}; // This is C++11, equivalent to Decorator(*this, x)
}
};
Now you just need to rewrite operator <<
so that it accepts a Decorator
object, modifies the referenced Int
object by applying the stored delta to the v
data member, then prints its value: 现在你只需要重写
operator <<
以便接受一个Decorator
对象,通过将存储的delta应用于v
数据成员来修改引用的Int
对象,然后打印它的值:
ostream &operator<< (ostream &o, Decorator const& d) {
d.i.v += d.v;
o << d.i.v;
return o;
}
Here is a live example . 这是一个实例 。
Disclaimer : As others have mentioned, keep in mind that operator []
and operator <<
are typically non-mutating operations (more precisely, they do not mutate the state of the object which is indexed and stream-inserted, respectively), so you are highly discouraged from writing code like this unless you're just trying to solve some C++ trivia. 免责声明 :正如其他人所提到的那样,请记住
operator []
和operator <<
通常是非变异操作(更准确地说,它们不会改变分别被索引和流插入的对象的状态),所以你非常不鼓励编写这样的代码,除非你只是试图解决一些C ++琐事。
To explain what's going on here, let's make things much simpler: cout<<2*2+1*1;
为了解释这里发生了什么,让我们简化一下:
cout<<2*2+1*1;
. 。 What happens first, 2*2 or 1*1?
首先发生什么,2 * 2或1 * 1? One possible answer is 2*2 should happen first, since it's the leftmost thing.
一个可能的答案是2 * 2应该首先发生,因为它是最左边的东西。 But the C++ standard says: who cares?!
但是C ++标准说:谁在乎?! After all, the result is 5 either way.
毕竟,结果是5种方式。 But sometimes it matters.
但有时候这很重要。 For instance, if
f
and g
are two functions, and we do f()+g()
, then there is no guarantee which gets called first. 例如,如果
f
和g
是两个函数,并且我们执行f()+g()
,则无法保证首先调用哪个函数。 If f
prints a message, but g
exits the program, then the message may never be printed. 如果
f
打印一条消息,但g
退出程序,则可能永远不会打印该消息。 In your case, i[2]
got called before i[0]
, because C++ thinks it doesn't matter. 在你的情况下,
i[2]
在i[0]
之前被调用,因为C ++认为它并不重要。 You have two choices: 你有两个选择:
One option is to change your code so it doesn't matter. 一种选择是更改代码,这无关紧要。 Rewrite your
[]
operator so that it doesn't change the Int
, and returns a fresh Int
instead. 重写你的
[]
运算符,使它不会改变Int
,而是返回一个新的Int
。 This is probably a good idea anyway, since that will make it consistent with 99% of all the other []
operators on the planet. 无论如何,这可能是一个好主意,因为这将使其与地球上所有其他
[]
运营商的99%保持一致。 It also requires less code: 它还需要更少的代码:
Int &operator[](int x) { return this->v + x;}
. Int &operator[](int x) { return this->v + x;}
。
Your other option is to keep your []
the same, and split your printing into two statements: 您的另一个选择是保持
[]
相同,并将您的打印分成两个语句:
cout<<i[0]; cout<<i[2];
Some languages actually do guarantee that in 2*2+1*1
, the 2*2 is done first. 有些语言确实保证在
2*2+1*1
,首先完成2 * 2。 But not C++. 但不是C ++。
Edit: I was not as clear as I hoped. 编辑:我不像我希望的那样清晰。 Let's try more slowly.
让我们慢慢来吧。 There are two ways for C++ to evaluate
2*2+1*1
. C ++有两种方法可以评估
2*2+1*1
。
Method 1: 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5
. 方法1:
2*2+1*1 ---> 4+1*1 ---> 4+1 --->5
。
Method 2: 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5
. 方法2:
2*2+1*1 ---> 2*2+1 ---> 4+1 --->5
。
In both cases we get the same answer. 在这两种情况下,我们得到相同的答案。
Let's try this again with a different expression: i[0]+i[2]
. 让我们用不同的表达式再试一次:
i[0]+i[2]
。
Method 1: i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6
. 方法1:
i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6
。
Method 2: i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8
. 方法2:
i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8
。
We got a different answer, because the []
has side effects, so it matters whether we do i[0]
or i[2]
first. 我们得到了一个不同的答案,因为
[]
有副作用,所以我们先做i[0]
还是i[2]
都很重要。 According to C++, these are both valid answers. 根据C ++,这些都是有效的答案。 Now, we are ready to attack your original problem.
现在,我们准备好攻击你原来的问题。 As you will soon see, it has almost nothing to do with the
<<
operator. 正如您将很快看到的,它几乎与
<<
运算符无关。
How does C++ deal with cout << i[0] << i[2]
? C ++如何处理
cout << i[0] << i[2]
? As before, there are two choices. 和以前一样,有两种选择。
Method 1: cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4
. 方法1:
cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4
。
Method 2: cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4
. 方法2:
cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4
。
The first method will print 24 like you expected. 第一种方法将按照您的预期打印24。 But according to C++, method 2 is equally good, and it will print 44 like you saw.
但是根据C ++,方法2同样很好,它会像你看到的那样打印44。 Notice that the trouble happens before the
<<
gets called. 请注意,问题发生在
<<
被调用之前。 There is no way to overload <<
to prevent this, because by the time <<
is running, the "damage" has already been done. 有没有办法超载
<<
防止这种情况,因为时间<<
运行时,“损害”已经完成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.