简体   繁体   English

了解重载的operator []示例

[英]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. 例如,如果fg是两个函数,并且我们执行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.

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