C++ 通过命名成员调用虚拟与通过地址或引用调用虚拟的区别

C++ 通过命名成员调用虚拟与通过地址或引用调用虚拟的区别,c++,c++11,undefined-behavior,standards-compliance,dynamic-binding,C++,C++11,Undefined Behavior,Standards Compliance,Dynamic Binding,更新如下:在clang中,通过名称使用多态对象的左值不会激活虚拟分派,但会通过地址激活 对于以下基类B和派生的D,虚拟函数某物,联合空间 #include <iostream> using namespace std; struct B { void *address() { return this; } virtual ~B() { cout << "~B at " << address() << endl; } vir

更新如下:在clang中,通过名称使用多态对象的左值不会激活虚拟分派,但会通过地址激活

对于以下基类
B
和派生的
D
,虚拟函数
某物
,联合
空间

#include <iostream>
using namespace std;

struct B {
    void *address() { return this; }
    virtual ~B() { cout << "~B at " << address() << endl; }
    virtual void something() { cout << "B::something"; }
};

struct D: B {
    ~D() { cout << "~D at " << address() << endl; }
     void something() override { cout << "D::something"; }
};

union Space {
    B b;
    Space(): b() {}
    ~Space() { b.~B(); }
};
当使用-O2优化编译并运行程序时,这是输出:

$./a.out 
Destroying the old B: ~B at 0x7fff4f890628
"D::something" expected, but "B::something" happened
"D::something" expected, and "D::something" happened
Destruction of D expected:
~B at 0x7fff4f890628
But did not happen!
Destruction of D expected again:
~D at 0x7fff4f890608
~B at 0x7fff4f890608
令我惊讶的是,使用placement new设置
s.b
的动态类型会导致通过名称或地址在相同的l值上调用
某物的差异。第一个问题很重要,但我无法找到答案:

  • 是对派生类进行新的布局,如<代码> new(& S.B)D< /C> >强>未定义行为< /强>根据C++标准?
  • 如果不是未定义的行为,那么不通过命名成员的l值激活虚拟分派的选择是标准中指定的还是G++,Clang中的选择
  • 谢谢,我在S.O.的第一个问题

    更新 引用标准的答案和注释是准确的:根据标准,
    s.b
    将永远引用精确类型的对象
    b
    ,允许内存更改类型,但通过
    s.b
    使用该内存是“未定义的行为”,即禁止,或者编译器可以随意翻译。如果
    Space
    只是一个字符缓冲区,那么就地构造、析构函数和更改类型都是有效的。在导致这个问题的代码中确实做到了这一点,并且它与标准遵从性AFAIK一起工作。 谢谢。

    表达式
    new(&s.b)D
    重新使用名为
    s.b
    且以前由a
    b
    占用的存储器来存储新的
    D

    然而,您随后编写
    s.b.something()。这会导致未定义的行为,因为
    s.b
    表示a
    b
    ,但存储在该位置的实际对象是a
    D
    。参见C++14[basic.life]/7:

    如果在对象的生命周期结束后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建了一个新对象,一个指向原始对象的指针,一个引用原始对象的引用,或者,原始对象的名称将自动引用新对象,并且在新对象的生存期开始后,可用于操纵新对象,如果:

    • 新对象的存储正好覆盖原始对象占用的存储位置,并且

    • 新对象与原始对象的类型相同(忽略顶级cv限定符),并且

      [……]

    最后一点不满足,因为新类型不同


    (代码后面也有其他潜在问题,但由于此处导致了未定义的行为,因此这些问题没有实际意义;您需要进行重大的设计更改以避免此问题)。

    放置
    新建
    很好。使用
    s.b
    引用新创建的对象不是。但是肯定不能保证
    D
    的大小与
    b
    相同?@Muscampester-有
    static\u assert
    s保证大小和对齐都是正确的。@t.c.感谢您的评论,您是否有任何线索可以找到标准中指定动态类型设置(或更改)和分派差异的地方?[basic.life]/1,6-8
    s.b
    指的是过期对象,两种形式都未定义。(GCC也在
    -O3
    上对“工作”版本进行了设备化)感谢您的回答。解决方案是使用原始缓冲区,我被做
    thing.base
    的便利吸引,但它是UB。该标准允许在
    char
    unsigned char
    的缓冲区中进行类型双关,在您所指的部分中,您可以根据需要放置新的显式析构函数调用。您可以论证“新对象”是
    D
    B
    子对象,假设为典型布局。你的[…]中的第4号子弹排除了这一论点。
    $./a.out 
    Destroying the old B: ~B at 0x7fff4f890628
    "D::something" expected, but "B::something" happened
    "D::something" expected, and "D::something" happened
    Destruction of D expected:
    ~B at 0x7fff4f890628
    But did not happen!
    Destruction of D expected again:
    ~D at 0x7fff4f890608
    ~B at 0x7fff4f890608