Понимание примера перегруженного оператора []

Меня смущает вопрос, который я увидел в тесте на c ++.
Код здесь:

#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;
}

Я был уверен, что это напечатает 24 но вместо этого он печатает 44, Я бы очень хотел, чтобы кто-то разъяснил это. Это кумулятивная оценка?
Также является << бинарный инфикс ?

заранее спасибо

РЕДАКТИРОВАТЬ: в случае нечетко определенной перегрузки операторов, может ли кто-нибудь дать лучшую реализацию перегруженных операторов здесь, чтобы это напечатало 24?

13

Решение

Эта программа имеет неопределенное поведение: компилятор не обязан оценивать i[0] а также i[2] в порядке слева направо (язык C ++ предоставляет эту свободу компиляторам для обеспечения оптимизации).

Например, Clang делает это в этом случае, в то время как GCC не.

Порядок оценки не определен, поэтому вы не можете ожидать последовательного вывода, даже при многократном запуске вашей программы на определенном компьютере.

Если вы хотите получить согласованный вывод, вы можете перефразировать вышеизложенное следующим образом (или каким-то эквивалентным способом):

cout << i[0];
cout << i[2];

Как видите, GCC больше не выводит 44 в этом случае.

РЕДАКТИРОВАТЬ:

Если по какой-то причине вы действительно хотите выражение cout << i[0] << i[2] печатать 24, вам придется изменить определение перегруженных операторов (operator [] а также operator <<), потому что язык намеренно делает невозможным определить, какое подвыражение (i[0] или же [i2]) оценивается первым.

Единственная гарантия, которую вы получите здесь, это то, что результат оценки i[0] будет вставлен в cout до результата оценки i[2]так что ваш лучший выбор, вероятно, позволить operator << выполнить модификацию Int's элемент данных v, скорее, чем operator [],

Тем не менее, дельта, которая должна быть применена к v передается в качестве аргумента operator []и вам нужен какой-то способ переадресации operator << вместе с оригиналом Int объект. Одна возможность состоит в том, чтобы позволить operator [] вернуть структуру данных, которая содержит дельту, а также ссылку на исходный объект:

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)
}
};

Теперь вам просто нужно переписать operator << так что он принимает Decorator объект, изменяет указанный Int объект, применяя сохраненную дельту к v член данных, затем печатает его значение:

ostream &operator<< (ostream &o, Decorator const& d) {
d.i.v += d.v;
o << d.i.v;
return o;
}

Вот живой пример.

отказ: Как уже упоминали другие, имейте в виду, что operator [] а также operator << обычно это не мутантные операции (точнее, они не изменяют состояние индексируемого и вставляемого в поток объекта соответственно), поэтому вам крайне не рекомендуется писать подобный код, если только вы не пытаетесь решить C ++ пустяки.

16

Другие решения

Чтобы объяснить, что здесь происходит, давайте сделаем все намного проще: cout<<2*2+1*1;, Что происходит сначала, 2 * 2 или 1 * 1? Один из возможных ответов: 2 * 2 должно произойти первым, так как это самая левая вещь. Но стандарт C ++ гласит: кого это волнует ?! В конце концов, результат 5 в любом случае. Но иногда это имеет значение. Например, если f а также g две функции, и мы делаем f()+g()тогда нет гарантии, которая будет вызвана первой. Если f печатает сообщение, но g Выход из программы, тогда сообщение может никогда не быть напечатано. В твоем случае, i[2] звонили раньше i[0]потому что C ++ считает, что это не имеет значения. У вас есть два варианта:

Один из вариантов — изменить код, чтобы он не имел значения. Перепишите свой [] оператор, так что это не меняет Intи возвращает свежий Int вместо. В любом случае, это, вероятно, хорошая идея, так как это сделает ее совместимой с 99% всех остальных [] операторы на планете. Это также требует меньше кода:

Int &operator[](int x) { return this->v + x;},

Другой вариант — сохранить [] то же самое, и разделить вашу печать на два утверждения:

cout<<i[0]; cout<<i[2];

Некоторые языки действительно гарантируют, что в 2*2+1*1, 2 * 2 делается первым. Но не C ++.

Изменить: я был не так ясно, как я надеялся. Давайте попробуем помедленнее. Есть два способа оценки C ++ 2*2+1*1,

Способ 1: 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5,

Способ 2: 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5,

В обоих случаях мы получаем один и тот же ответ.

Давайте попробуем это снова с другим выражением: i[0]+i[2],

Способ 1: i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6,

Способ 2: i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8,

Мы получили другой ответ, потому что [] имеет побочные эффекты, поэтому важно, будем ли мы i[0] или же i[2] первый. Согласно C ++, это оба правильные ответы. Теперь мы готовы атаковать вашу исходную проблему. Как вы скоро увидите, это не имеет ничего общего с << оператор.

Как C ++ справляется с cout << i[0] << i[2]? Как и раньше, есть два варианта.

Способ 1: cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4,

Способ 2: cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4,

Первый метод напечатает 24, как вы ожидали. Но согласно C ++, метод 2 одинаково хорош, и он напечатает 44, как вы видели. Обратите внимание, что проблема происходит до << вызывается. Перегрузить нельзя << чтобы предотвратить это, потому что к тому времени << работает, «повреждение» уже сделано.

5