C ++ присваивание приведения типа - PullRequest
5 голосов
/ 20 декабря 2011

Я наткнулся на нечто подобное сегодня, а затем попробовал несколько вещей и заметил, что в G ++ кажется законным следующее:

struct A {
    int val_;
    A() { }
    A(int val) : val_(val) { }
    const A& operator=(int val) { val_ = val; return *this; }
    int get() { return val_; }
};

struct B : public A {
    A getA() { return (((A)*this) = 20); } // legal?
};

int main() {
    A a = 10;
    B b;
    A c = b.getA();
}

То есть B::getB возвращает тип A, после которого ему присваивается значение 20 (через перегруженное A::operator=).

После нескольких тестов кажется, что он возвращает правильное значение (c.get вернет 20, как и следовало ожидать).

Так что мне интересно, это неопределенное поведение ? Если это так, что именно так? Если нет, то в чем преимущества такого кода?

Ответы [ 2 ]

3 голосов
/ 20 декабря 2011

После тщательного изучения с помощью @Kerrek SB и @Aaron McDaid, следующее:

return (((A)*this) = 20);

... похоже на сокращенный (но неясный) синтаксис для:

A a(*this); 
return a.operator=(20);

... или даже лучше:

return A(*this) = 20;

... и, следовательно, определяется поведение.

1 голос
/ 20 декабря 2011

Здесь происходит несколько совершенно разных вещей. Код действителен, однако вы сделали неверное предположение в своем вопросе. Вы сказали

"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

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...