Здесь происходит несколько совершенно разных вещей. Код действителен, однако вы сделали неверное предположение в своем вопросе. Вы сказали
"B :: getA возвращает [...] после того, как ему присвоено значение 20 для самого "
(мой акцент) Это не правильно. getA не изменяет объект. Чтобы убедиться в этом, вы можете просто поместить const
в сигнатуру метода. Я тогда полностью объясню.
A getA() const {
cout << this << " in getA() now" << endl;
return (((A)*this) = 20);
}
Так что здесь происходит? Глядя на мой пример кода (я скопировал свою расшифровку в конец этого ответа):
A a = 10;
Это объявляет A с конструктором. Довольно просто. Следующая строка:
B b; b.val_ = 15;
B не имеет конструкторов, поэтому я должен писать напрямую в его член val_ (унаследованный от A).
Прежде чем мы рассмотрим следующую строку, A c = b.getA();
, мы должны очень тщательно рассмотреть простое выражение:
b.getA();
Это не меняет b
, хотя может выглядеть превосходно.
В конце мой пример кода печатает b.val_
, и вы видите, что он по-прежнему равен 15. Оно не изменилось до 20. c.val_
изменилось до 20, конечно.
Загляните внутрь getA
, и вы увидите (((A)*this) = 20)
. Давайте разберем это:
this // a pointer to the the variable 'b' in main(). It's of type B*
*this // a reference to 'b'. Of type B&
(A)*this // this copies into a new object of type A.
Здесь стоит остановиться. Если бы это было (A&)*this
или даже *((A*)this)
, то это была бы более простая строка. Но это (A)*this
, и поэтому он создает новый объект типа A и копирует в него соответствующий фрагмент из b.
(Дополнительно: Вы можете спросить, как он может скопировать фрагмент. У нас есть ссылка B&
, и мы хотим создать новый A
. По умолчанию компилятор создает конструктор копирования A :: A (const A&)
. Компилятор можно использовать это, потому что ссылка B&
может быть естественным образом приведена к const A&
.)
В частности this != &((A)*this)
. Это может быть сюрпризом для вас. (Дополнительно: с другой стороны this == &((A&)*this)
обычно (в зависимости от того, существуют ли virtual
методы))
Теперь, когда у нас есть этот новый объект, мы можем посмотреть на
((A)*this) = 20
Это помещает число в это новое значение. Это утверждение не влияет this->val_
.
Было бы ошибкой изменить getA
так, чтобы он возвратил A&
. Во-первых, возвращаемое значение operator=
равно const A&
, и поэтому вы не можете вернуть его как A&
. Но даже если у вас есть const A&
в качестве возвращаемого типа, это будет ссылка на временную локальную переменную, созданную внутри getA. Не определено возвращать такие вещи.
Наконец, мы можем видеть, что c
возьмет эту копию, которая возвращается значением из getA
A c = b.getA();
Именно поэтому текущий код, где getA возвращает копию по значению , является безопасным и четко определенным.
== Полная программа ==
#include <iostream>
using namespace std;
struct A {
int val_;
A() { }
A(int val) : val_(val) { }
const A& operator=(int val) {
cout << this << " in operator= now" << endl; // prove the operator= happens on a different object (the copy)
val_ = val;
return *this;
}
int get() { return val_; }
};
struct B : public A {
A getA() const {
cout << this << " in getA() now" << endl; // the address of b
return (((A)*this) = 20);
// The preceding line does four things:
// 1. Take the current object, *this
// 2. Copy a slice of it into a new temporary object of type A
// 3. Assign 20 to this temporary copy
// 4. Return this by value
} // legal? Yes
};
int main() {
A a = 10;
B b; b.val_ = 15;
A c = b.getA();
cout << b.get() << endl; // expect 15
cout << c.get() << endl; // expect 20
B* b2 = &b;
A a2 = *b2;
cout << b2->get() << endl; // expect 15
cout << a2.get() << endl; // expect 15
}