C++ 通过命名成员调用虚拟与通过地址或引用调用虚拟的区别
更新如下:在clang中,通过名称使用多态对象的左值不会激活虚拟分派,但会通过地址激活 对于以下基类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
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
且以前由ab
占用的存储器来存储新的D
然而,您随后编写s.b.something()代码>。这会导致未定义的行为,因为s.b
表示ab
,但存储在该位置的实际对象是aD
。参见C++14[basic.life]/7:
如果在对象的生命周期结束后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建了一个新对象,一个指向原始对象的指针,一个引用原始对象的引用,或者,原始对象的名称将自动引用新对象,并且在新对象的生存期开始后,可用于操纵新对象,如果:
- 新对象的存储正好覆盖原始对象占用的存储位置,并且
- 新对象与原始对象的类型相同(忽略顶级cv限定符),并且
[……]
最后一点不满足,因为新类型不同
(代码后面也有其他潜在问题,但由于此处导致了未定义的行为,因此这些问题没有实际意义;您需要进行重大的设计更改以避免此问题)。放置新建很好。使用s.b
引用新创建的对象不是。但是肯定不能保证D
的大小与b
相同?@Muscampester-有static\u assert
s保证大小和对齐都是正确的。@t.c.感谢您的评论,您是否有任何线索可以找到标准中指定动态类型设置(或更改)和分派差异的地方?[basic.life]/1,6-8s.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