首页 > 解决方案 > 派生类的新位置

问题描述

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;
}

代码非常简单:在堆栈上有一个基础对象,将派生对象放入其中(是的,eeew,但请耐心等待)并调用虚拟方法,期望打印派生的输出。

实际输出

上面的代码产生以下输出:

蜜蜂
〜D()
蜜蜂
呵呵
〜D()

在我尝试过的 MSVC、gcc、clang 和一些在线编译器上普遍观察到这种行为(这非常强烈地表明是我错了)。

第1部分

Placement-new 将派生类型对象重新更新到基本类型内存中。这会更新 vptr 以指向派生类型的 vtable(直接在调试器中观察到)。

主要问题:这是预期的行为吗?(我想说“是”,所以如果不是 - 请向我解释)

我想相信执行新放置(假设派生类型对象有足够的内存)应该就地初始化派生类型的全新对象。


如果我的理解是正确的,那么第一个b.talk()应该输出"Duh",因为现在的对象是派生类型的。为什么还在打印"Be-e-e"

将派生类型对象分配给基类型对象(除了导致对象拼接之外)不会复制 vptr,因此"Be-e-e"当我们到达代码。

第2部分

为什么任务中有~D()电话b = D{};?难道它不是一个临时的,应该被复制省略而不需要对该临时的析构函数调用吗?

第 3 部分

最后一个使用指针的代码块“按预期”工作,只是在这里进行完整性检查

标签: c++polymorphismundefined-behaviorplacement-newvptr

解决方案


看代码:

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,你不能省略不同类型之间的副本,比如DB


推荐阅读