Размещение нового с производным классом

Гуру C++. Нужна ваша помощь с этой маленькой головкой:

#include <iostream>
struct B{
    virtual ~B() = default;
    virtual void talk() { std::cout << "Be-e-e\n"; }
};

struct D:B{
    void talk() override { std::cout << "Duh\n"; }
    ~D() { std::cout << "~D()\n"; }
};

int main(){
    B b{};      // vptr points to B
    new (&b) D; // vptr now points to D
    b.talk();   // "Be-e-e" (why? shouldn't the vptr be used?)
    b = D{};    // "~D()" (why? shouldn't the copying be elided?)
    b.talk();   // "Be-e-e"

    B*b1{new D};
    b1->talk(); // "Duh"
    delete b1;  // "~D()"

    return 0;
}

Код довольно прост: иметь базовый объект в стеке, размещать в нем новый и производный (да, эээу, но терпи меня) и вызывать виртуальный метод, ожидая, что производный вывод будет напечатан.

Фактический выход

Приведенный выше код дает следующий результат:

Be-e-e
~D()
Be-e-e
Duh
~D()

Такое поведение повсеместно наблюдается в MSVC, gcc, clang и нескольких онлайн-компиляторах, на которых я его пробовал (что является чрезвычайно убедительным свидетельством того, что это я ошибаюсь).

Часть 1

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

Главный вопрос: это ожидаемое поведение? (Я хочу сказать "да", поэтому, если это не так - объясните мне, пожалуйста)

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


Если я правильно понимаю, первый b.talk() должен выводить "Duh", поскольку теперь объект имеет производный тип. Почему он все еще печатает "Be-e-e"?

Назначение объекта производного типа объекту базового типа (в дополнение к сращиванию объектов) не копирует vptr, поэтому ожидается второй вывод "Be-e-e", при условии, что объект все еще относится к базовому типу, когда мы дойдем до этой строки кода.

Часть 2

Почему в назначении ~D() присутствует вызов b = D{};? Разве это не временное, которое следует исключить из копирования без необходимости вызова деструктора для этого временного?

Часть 3

Последний блок кода, в котором используются указатели, работает «как ожидалось» и находится здесь только для проверки работоспособности.

Виртуальные вызовы работают только через указатели и ссылки. Не на прямых объектах.

Galik 17.10.2018 05:11

Также я думаю, что у вас есть неопределенное поведение в нескольких местах.

Galik 17.10.2018 05:15

Думаю, это связано: stackoverflow.com/questions/15188894/…

Galik 17.10.2018 05:17
b = D{}; также, по сути, ошибочен, поскольку производные классы могут использовать конструктор базового класса, но не наоборот ... (Здесь b - базовый класс и может Только использовать свой собственный конструктор ...
Ruks 17.10.2018 05:21

@Ruks "производные классы могут использовать конструктор базового класса, но не наоборот" Что ты имеешь в виду?

curiousguy 28.10.2018 22:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
311
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Смотрим на код:

B b{};      // vptr points to B
new (&b) D; // vptr now points to D

Это потенциальная проблема по двум причинам. Сначала вы не вызывали деструктор для базового объекта B. Во-вторых, размер B может быть слишком мал для размещения объекта типа D.

b.talk();   // "Be-e-e" (why? shouldn't the vptr be used?)

Виртуальные вызовы работают только при вызове через указатель или ссылку. Такой прямой вызов функции никогда не использует виртуальная отправка.

b = D{};    // "~D()" (why? shouldn't the copying be elided?)

Поскольку b объявлен как тип B, и вы не можете исключить копию между различными типами, такими как D и B.

"Сначала вы не вызывали деструктор для базового объекта B." Нет причин: этот конкретный деструктор не имеет функционального эффекта.

curiousguy 22.10.2018 19:22

@curiousguy Деструктор может не иметь функциональный эффект по отношению к программе, но неизвестно, как он влияет на внутреннее состояние компилятора и его процесс принятия решений. Если деструктор не вызывается, это все еще неопределенное поведение.

Galik 22.10.2018 19:51

"Это все еще неопределенное поведение" с каких это пор?

curiousguy 22.10.2018 20:53

Другие вопросы по теме

Haskell - Сумки - Как я могу использовать полиморфизм в Haskell?
VB.net: создание одного из нескольких объектов производного класса в ответ на запрос пользователя
Запретить унаследованному члену доступ к защищенному члену
Полиморфизм Java - как определить, будет ли вызываться метод суперкласса против подкласса и переменная суперкласса против переменной подкласса?
Будет ли полиморфный объект, приведенный к производному типу, иметь такое же ссылочное значение
Элегантное решение проблемы полиморфного наследования C++
Laravel - Получить сбор всех данных, поступающих из полиморфных отношений
Как полиморфизм в Java работает в этом общем случае (метод с параметром)?
Как я могу получить доступ к методу базового класса с таким же именем в производном классе, используя объект производного класса
Сохранить указатель на функцию производного класса в базовом классе