2013-02-18 6 views
3

Для этого есть много вопросов и ответов, но я не могу найти , почему нам нужно вернуть по ссылке.Перегрузка оператора с использованием ostream и цепочки. Зачем возвращаться по ссылке?

Если мы (предположим, что оператор уже правильно перегружен для объекта MyObject):

MyObject obj1; 
    MyObject obj2; 
    cout << obj1 << obj2; 

Теперь будет подвыражения, как ((соиЬ < < obj1) < < obj2)); Вопрос почему мы не можем вернуться по стоимости? (Хорошо, давайте предположим, что ему разрешено возвращать ostream как значение). Если cout < < obj1 возвращает объект потока вместо ссылки, в чем разница? Почему цепочки невозможно? Как и при перегрузке оператора '=', мы не можем цепеть, как A = B = C = D, если мы вернемся по значению. Зачем ?


Благодарим за ответы. Я понимаю, что я могу цепочки без возврата по ссылке, но мой вывод сильно отличается при перегрузке '='. Если я пишу:

class Blah{ 
    public: 
     Blah(); 
     Blah(int x, int y); 
     int x; 
     int y; 
     Blah operator =(Blah rhs); 
    }; 
    Blah::Blah(){} 
    Blah::Blah(int xp, int yp){ 
     x = xp; 
     y = yp; 
    } 
    Blah Blah::operator =(Blah rhs){ 
     Blah ret; 
     ret.x = rhs.x; 
     ret.y = rhs.y; 
     return ret; 
    } 
    int main(){ 

     Blah b1(2,3); 
     Blah b2(4,1); 
     Blah b3(8,9); 
     Blah b4(7,5); 
     b3 = b4 = b2 = b1; 
     cout << b3.x << ", " << b3.y << endl; 
     cout << b4.x << ", " << b4.y << endl; 
     cout << b2.x << ", " << b2.y << endl; 
     cout << b1.x << ", " << b1.y << endl; 
      return 0; 
    } 

Выход из этого является: 8,9 7,5 4,1 2,3

Но если я перегрузить с возвратом по ссылке и установить параметр как ссылки, а также изменять и возвращать * это при перегрузке вместо этого, я получаю: 2,3 2,3 2,3 2,3

в чем причина объекты не изменяются в первом примере? Это из-за lvalues ​​vs rvalues? Как насчет сокращенных операторов в сравнении?


ОК, другое обновление. Как уже упоминалось, правильный результат должен быть 2,3 для всех. Однако, если я напишу перегруженного оператора как:

 Blah Blah::operator =(Blah rhs){ 
     x = rhs.x; 
     y = rhs.y; 
     return *this; 
    } 

Затем я получу правильные результаты. (2,3, 2,3, 2,3, 2,3). Что происходит с этим? Перегруженный оператор обновляет lhs с помощью rhs в функции перегрузки, но возвращает * это кажется бессмысленным. Где * это в итоге: b3 = b4 = b2 = b1? Попробует ли он вернуться влево, так что он фактически ничего не возвращает, когда цепочка достигает b3 (что будет пытаться вернуться влево)?

+5

Потому что вы хотите продолжать вставлять в * тот же * поток. –

ответ

2

Возврат по значению не мешает цепочке. Но если вы вернетесь по значению, вы возвращаете копию , что обычно не то, что вы хотите в этом случае.

6

Основная причина заключается в том, что возврат по значению делает копию, а Объекты iostream не могут быть скопированы. У них есть состояние и идентификатор, и неясно, какое копирование их должно означать: объект содержит (логически, по крайней мере) его позицию в потоке , поэтому, если я создам копию, у меня есть два объекта, которые будут пишут на то же положение в потоке.

1
Blah Blah::operator =(Blah rhs){ 
    Blah ret; 
    ret.x = rhs.x; 
    ret.y = rhs.y; 
    return ret; 
} 
    Blah b1(2,3); 
    Blah b2(4,1); 
    Blah b3(8,9); 
    Blah b4(7,5); 
    b3 = b4 = b2 = b1; 

b3 собирается взять в b4 как его правой части, но вы на самом деле не изменяя значения b3, вы делаете новую переменную того же типа Blah и вернуть его к нулю (в этом случае нуль ничего не значит, потому что нет ничего левее b3. Даже если что-то слева от b3, это не будет иметь значение, как другой Blah переменной будет делать то же самое, как b3 и b4 сделал.

в самом деле если у вас был другой класс (скажем, CoolCla ss, который также имеет и x и y) и перегрузил оператор присваивания, чтобы взять переменную blah и действительно ли он изменил свои собственные x и y, вы найдете это.

coolObject = b3 = b4 = b2 = b1; //CoolObject.x = 2, CoolObject.y = 3 

Я все еще не совсем уверен, какова ваша главная жалоба с передачей по ссылке. Вот как я напишу этого оператора для Бла.

Blah & Blah::operator = (const Blah & rhs) { x = rhs.x; y = rhs.y; return *this; } 

Это гарантирует, что ваш rhs не изменен и это поведение цепочки работает правильно.

Если вы ищете лучшее поведение с другим видом объекта, скажем, например, ostream, иногда может быть полезно объявить функцию друга. Это функции, которые можно объявить в новом классе, но не относятся к классу напрямую. Преимущество этого подхода состоит в том, чтобы иметь оператора, который выглядит так, как будто он исходит из класса ostream, но он находится в классе Blah. Вы можете использовать частные и защищенные члены внутри функции friend, что делает их полезными.

friend std::ostream & Blah::operator << (std::ostream & lhs, const Blah & rhs) 
{ return lhs << "{" << rhs.x << ", " << rhs.y << "}"; } 

Что это такое - это пропуск вокруг одного и того же объекта ostream и заполняет его данными в порядке очередности. Точное поведение, которое вы ожидаете найти с помощью обычного текста и ostream.

Используя свой первый пример, вы можете думать об этом таким образом. Предполагая obj1 и obj2 оба Blah, то cout объекта принимает в obj1 с помощью функции друга и возвращает тот же cout объекта модифицированных данных в obj1, а затем вновь модифицированный cout объект принимает в obj2 являются возвращают то же модифицированное cout объект снова, но теперь его также изменяет на obj2.

(cout << obj1) << obj2; 
+0

Спасибо! Теперь это намного яснее. Тем не менее, я все еще немного запутался в этом: Blah Blah :: operator = (const Blah rhs) {x = rhs.x; y = rhs.y; return * this; } Это дает то же поведение, что и возвращение по ссылке. Может быть, потому что я уже модифицировал объект левой руки? Но я не вижу, что произойдет с возвратом * this – user1511956

+0

Когда вы возвращаете '* this', вы удаляете ссылку на адрес своего объекта. Это означает, что вы возвращаете значение объекта, на который указывает 'this'. Я добавил некоторые дополнительные сведения о ostream к моему сообщению, но я не знаю, это то, что вы ищете. –

+0

Хмм, хорошо. Но я заметил, что при записи b3 = b2 = b4 = b1 я получаю правильное поведение при возврате по значению _if_ i return * this (при перегрузке, как в моем предыдущем комментарии). Я не вижу, где * этот указатель действительно возвращает в цепочке присваиваний w.r.t, который используется для возврата по значению. Я вижу, что объекты lhs получают свою ценность из-за x = rhs.x; у = rhs.y; но я понятия не имею, что происходит с этим. * Это просто пытается вернуться к левому виду? Так что * это действительно ничего не возвращает ни одному из объектов? – user1511956

1
Blah Blah::operator =(Blah rhs){ 
Blah ret; 
ret.x = rhs.x; 
ret.y = rhs.y; 
return ret; 
}-->(1) 

Blah b1(2,3); 
Blah b2(4,1); 
Blah b3(8,9); 
Blah b4(7,5); 

b3 = b4 = b2 = b1; ----> (2)

Вызвав (2) вы будете называть =() и здесь, в приведенном выше фрагменте кода (1) вы возвращаете временный объект. Обратите внимание, что вы не обновляете значение x фактического объекта. Например, запуск начинается, как и этот b2 (x, y) = b1 (2,3), и вы инициализируете значение 2 & 3 в временный объект ret и возврат временного объекта по значению. Так что временный объект теперь будет вызывать b4, то есть b4 = temp (2,3) и снова тот же файл. Вы будете копировать 2 & от 3 до b4 и возвращает объект temp который является invok b3 как b3 = temp (2,3). Теперь, если вы замените (2), как это напечатано cout < < b3 = b4 = b2 = b1 (при условии перегрузки < <), вы получили бы 2 & 3 как выход, потому что 2 & 3 будет доступен только в этой строке.В вашем примере вы печатаете в следующих строках, где это значение недоступно только потому, что вы не обновляете значение x & y в вызываемом объекте. Так что для этого вы должны вернуть * это как ссылку, что означает вы возвращаете объект, который вызывал функцию .SO, если вы делаете b2 = b1 и возвращаете * это, то есть означает, что вы возвращаете фактический объект, а значение x, значение фактического объекта обновляется, и оно будет доступно, если объект существует.

0

здесь один интересный ответ я основан на learncpp.com

ostream класс предоставляется как часть C++, который обрабатывает выводит потоки. Детали того, как внедряется ostream, очень сложны, но, к счастью, также совершенно не нужны для эффективного использования.

Поскольку ostream является классом, ostream & является ссылкой на класс ostream. Обратите внимание, что в качестве параметра мы также принимаем ostream &. ostream обычно передается по ссылке, потому что мы не хотим копировать его, когда мы его передаем.

Таким образом, наша перегруженная функция принимает параметр ostream в качестве параметра, записывает в него материал и затем возвращает тот же поток. Это позволяет цепи << созовет:

cout << cPoint1 << cPoint2 << cPoint3; 

Это решает следующим образом:

((cout << cPoint1) << cPoint2) << cPoint3; 

cout < < cPoint1 разрешен первый, с соиЬ становится ostream & параметр. Когда эта перегруженная функция закончена, записывая параметр out, она возвращает этот cout, поэтому следующий вызов << может использовать его. Таким образом:

((cout << cPoint1) << cPoint2) << cPoint3; 

становится:

(cout << cPoint2) << cPoint3; 

становится:

cout << cPoint3; 

Это требует наша перегруженная функция один последний раз. В конце этой функции снова возвращается cout. Но его никто не может использовать, поэтому возвращаемое значение игнорируется. Выражение завершается, и программа переходит к следующей строке.