Ссылка на базовый класс - присвоить ему другой тип

Что происходит в следующем примере?

struct B { };
struct D1 : B  { };
struct D2 : B  { };
int main()
{
    D1 d;
    D2 d2;
    B& x = d;
    x = d2;
}

Я знаю, что ссылка не переустанавливается. x по-прежнему относится к d, но тогда как вы можете назначить d2 на d?

Еще несколько:

struct B
{
    B () { x = 0; }
    int x;
    virtual void foo () { cout << "B" << endl; }
};
struct D1 : B
{
    D1 () { x = 1; }
    virtual void foo () { cout << "D1" << endl; }
};
struct D2 : B
{
    D2 () { x = 2; }
    virtual void foo () { cout << "D2" << endl; }
};

int main()
{
D1 d;
D2 d2;
B& x = d;
x.foo();   //D1
               //x.x is 1 here
x = d2;
x.foo();   //also D1
               //but x.x is 2 here
}

Кажется, что x.x был обновлен, но vftable не был... Почему?

Ответ 1

x относится к субобъекту B базового класса d. Назначение x = d2 фрагментов базового подобъекта B из d2 и присваивает его значение подобъекту d.

Это обычно не делается намеренно.

EDIT:

Кажется, что x.x обновлен, но vftable не был... Почему?

Это то, что делает оператор присваивания B::operator=. Базовые классы в С++ полностью не знают, что они базовые классы. Кроме того, тип объекта не может быть изменен в течение всего срока его службы. Ближайшей альтернативой является С++ 11 std::move, которая может переносить старый объект B внутри D1 в новый d2 объект. Затем вы уничтожили бы старый объект.

Ответ 2

Если вы хотите, вы можете реализовать the = самостоятельно и "избегать" нарезки, проверив соответствующий конкретный тип (или давая ошибку). Пример с ошибками приведен ниже.

struct B { 
  virtual B& operator = (B& b) = 0;
};
struct D1 : B  { 
  D1& operator = (B& b) {
    if ( dynamic_cast<D1*>(&b) == 0 ) {
      cerr << "Cannot assign non D1 to D1" << endl;
      exit(255);
    }
    // handle the assignments
    return *this;
  }
};
struct D2 : B  { 
  int c;
  D2& operator = (B& b) {
    if ( dynamic_cast<D2*>(&b) == 0 ) {
      cerr << "Cannot assign non D2 to D2" << endl;
      exit(255);
    }
    // handle the assignments
    return *this;
  }
};

Ответ 3

В вашем случае, когда вы назначаете этот способ, члены, которые не относятся к базовому классу, будут нарезаны. Это означает, что в этом случае он копируется, как если бы вы назначали один объект базового класса другому.