Виртуальные таблицы при копировании объектов - PullRequest
4 голосов
/ 03 октября 2010
#include <iostream>
#include <string>
class A
{
public:
    A(int a) : _a(a) {}
    virtual ~A() {}
    virtual void f() const {std::cout << _a << std::endl;}
private:
    int _a;
};
class B : public A
{
public:
    B(int a, int b) : A(a), _b(b) {}
    virtual void f() const {std::cout << _b << std::endl;}
private:
    int _b;
};
int main()
{
    B b (1,2);
    A a (5);
    A& ref = a;
    ref = b;
    ref.f();
    return 0;
}

Вывод:

1

Я понимаю, что при копировании объекта производного (расширенного) класса в объект базового класса, производный объект вырезается и копируются только данные базового класса.Но я подумал, что виртуальная таблица 'ref' теперь должна быть как виртуальная таблица 'b', поэтому 'ref.f ();'должен вызвать функцию:

void B::f() const {std::cout << _b << std::endl;}

Но после копирования vtbl из 'ref' остается vtbl класса A. почему?Спасибо.

Ответы [ 6 ]

4 голосов
/ 04 октября 2010

Во-первых, «виртуальная таблица» не является стандартной концепцией C ++.Это очень специфичный для реализации механизм реализации динамического связывания и реализации виртуальных функций.

Сказав это,

Но я подумал, что виртуальная таблица 'ref' теперь должна бытьвиртуальная таблица 'b', поэтому 'ref.f ();'должен вызвать функцию

Это не правильно.Виртуальная таблица для каждого класса, а не для объекта.Это только Vptr для каждого объекта.

Тип 'ref' (что подтверждается typeid (ref) .name, если хотите) - 'A &'.Когда вы присваиваете 'ref = b', неявный оператор присваивания 'A' вызывается с объектом 'b' в качестве аргумента.Этот оператор просто слепо копирует подобъект «A» в «b» в текущий объект, на который ссылается «ref» (то есть «a»).Следовательно, объект «a» теперь находится в том же состоянии, что и подобъект «A» в «b».

Как вы можете видеть, во всей этой очень длинной истории VTABLE и VPTR вообще не существует !.

3 голосов
/ 04 октября 2010

Виртуальная таблица (или, точнее, указатель на виртуальную таблицу), хранящаяся в каждом полиморфном объекте, никогда не копируется.

Причины вашего вопроса "почему" не совсем ясны. Конечно , он не копируется.Почему это должно быть скопировано?Виртуальная таблица - это способ описания специфического для конкретного поведения поведения конкретного объекта.Это в основном [косвенно] описывает фактический тип объекта.Поскольку фактический тип объекта не изменяется во время копирования, виртуальная таблица остается прежней.

2 голосов
/ 03 октября 2010

Если вы используете указатели (вместо ссылок), ваши производные объекты останутся такими, какими они должны быть. Тогда vtbl вызывается правильно.

A* ref;
ref = &b;
ref->f();

Когда вы используете ссылки, система времени выполнения не понимает, что вы имели в виду B. При выполнении задания он считает A.

1 голос
/ 03 октября 2010

Нет, виртуальная таблица не действует как указатель и не копируется при назначении экземпляру базового класса (это называется нарезкой).

Сначала уточним ваш код:

A& ref = a;
ref = b;

Прямо эквивалентно:

a = b;

Теперь, что здесь происходит, это то, что содержимое экземпляра A заменяется частью A содержимого экземпляра B. Однако результат по-прежнему является экземпляром A, поэтому он указывает на виртуальную таблицу A, правильно вызывая A::f.

0 голосов
/ 04 октября 2010

Я думаю, вы запутались в «Справочнике против Пойнтера». Осознайте, что ваш код на самом деле делает .

A& ref = a;   // Reference
ref = b;

После первой строки ref и a - это два имени для одного и того же объекта . Эффективный тип ref равен A.

.

Во второй строке объект b назначается объекту, на который ссылается ref (что, конечно, a). Теперь у вас есть

  • оригинал b и
  • A -часть b, скопированная в объект a (и на которую ссылается ref).

После копирования любая "B -ность" скопированного объекта полностью теряется вместе с предыдущим содержимым объекта a. Элемент _a скопирован, элемент _b и таблица B отделены.

Попробуйте вместо этого:

int main()
{
    B b (1,2);
    A a (5);
    A * ref = &a;  // pointer, not reference
    ref = &b;
    ref->f();
    return 0;
}

Это дает "2", как вы, вероятно, ожидали. Он также не перезаписывает объект a, как в первом примере.

0 голосов
/ 03 октября 2010

A& ref = a; ref = b;

Статический, а также динамический тип вашей ссылки A&. Назначение ref = b не меняет динамический / статический тип ссылки в соответствии с вашими ожиданиями.

Из C ++ часто задаваемых вопросов Маршалла Клайна

Вы не можете отделить ссылку от референта.

В отличие от указателя, когда ссылка привязана к объекту, ее нельзя «переустановить» для другого объекта. В этом смысле ссылка похожа на указатель const, например int* const p .

Попробуйте это

A& ref = b;  //reference is bound to b [dynamic type of the referent is `B&`]) 
ref.f(); //Prints 2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...