Почему возврат виртуального класса * по значению * в C ++ изменяет v-таблицу?

В следующем коде я возвращаю производный класс CDerived по значению из функции, возвращающей его базовый класс CBase.

Например, CDerived содержит поле _number, и я понимаю, что оно отсекается во время преобразования в тип возвращаемого значения по значению, CBase.

Однако меня смущает то, что виртуальная функция Print каким-то образом трансмутируется в свою версию базового класса. В этом случае v-table не просто был разрезан, а был изменен.

В моем фактическом коде _number не существует, как и других данных. Я хотел бы иметь возможность возвращать производный класс только для его v-таблицы, которая объявлена ​​базой.

Из этого вопроса я вижу, что могу обойти эту конкретную проблему, вернув указатель на функцию (или я полагаю, что создаст свою собственную v-таблицу), но почему именно v-таблица изменяется в первую очередь, и есть ли способ обойти это изменение? .

#include <iostream>

class CBase
{
public:
    virtual void Print() { std::cout << "CBase" << std::endl; }
};

class CDerived : public CBase
{
    int _number;

public:
    explicit CDerived(int n) : _number(n) { }
    virtual void Print() override { std::cout << "CDerived" << std::endl; }
};

CBase GetBase()
{
    return CDerived(42);
}

int main()
{
    CBase base = GetBase();
    base.Print(); // Outputs CBase
    return 0;
}

person c z    schedule 06.08.2020    source источник
comment
Помимо того, как работает C ++, в общем случае это было бы опасно - вызываемая виртуальная функция может получить доступ к членам (_number в данном случае), которых не существует.   -  person user202729    schedule 06.08.2020
comment
Часть того, что связывает объект с его классом, - это используемая им виртуальная таблица. Компилятор поддерживает это, и у нас нет возможности это изменить. Вы не возвращаете CDerived, вы возвращаете CBase, преобразованный из чего-то.   -  person StoryTeller - Unslander Monica    schedule 06.08.2020
comment
Как это могло работать по-другому? Если вы должны отрезать производные члены, вы также должны отрезать производные виртуальные функции, которые могут их использовать ... В противном случае язык будет поощрять создание неопределенных объектов Franken, чего, очевидно, не происходит. (Кажется, что vtable не нарезается или не заменяется; просто компилятор знает, что возвращаемое значение является базовым, поэтому при необходимости он использует базовую vtable.)   -  person underscore_d    schedule 06.08.2020
comment
Это похоже на проблему x / y. Я хотел бы иметь возможность возвращать производный класс только для его v-таблицы, Почему? Какую проблему вы пытаетесь решить? должен быть лучший способ.   -  person underscore_d    schedule 06.08.2020
comment
@underscore_d В вопросе указан лучший способ, но меня беспокоило почему этот способ (создание v-таблицы вручную) работает, а способ C ++ - нет. Тем более, что ковариация действительно работает, когда мы передаем ссылку.   -  person c z    schedule 06.08.2020


Ответы (1)


Вот как работает C ++.

Вы не возвращаете объект CDerived, вы возвращаете CBase, скопированный из CBase подобъекта временного CDerived.

person Caleth    schedule 06.08.2020