更新如下:在clang中,通过名称使用多态对象的左值不会激活虚拟分派,但它会通过其地址执行.
对于以下基类B和派生D,虚函数的东西,union空间
#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(); }
};
如果你有一个空间的值s,在Clang中:(更新:错误地声称g具有相同的行为)
如果你做sbsomething(),B :: something()将被调用,而不是对sb进行动态绑定,但是,如果你调用(& sb) – > something()将动态绑定到什么b真的包含(B或D).
完成代码是这样的:
union SpaceV2 {
B b;
SpaceV2(): b() {}
~SpaceV2() { (&b)->~B(); }
};
static_assert(sizeof(D) == sizeof(B), "");
static_assert(alignof(D) == alignof(B), "");
#include <new>
int main(int argc, const char *argv[]) {
{
Space s;
cout << "Destroying the old B: ";
s.b.~B();
new(&s.b) D;
cout << "\"D::something\" expected, but \"";
s.b.something();
cout << "\" happened\n";
auto &br = s.b;
cout << "\"D::something\" expected, and \"";
br.something();
cout << "\" happened\n";
cout << "Destruction of D expected:\n";
}
cout << "But did not happen!\n";
SpaceV2 sv2;
new(&sv2.b) D;
cout << "Destruction of D expected again:\n";
return 0;
}
使用-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值.第一个问题很重要,但我找不到答案:
>是否对派生类进行了新的放置,例如根据C标准的新(& s.b)D未定义行为?
>如果它不是未定义的行为,是不是通过标准中指定的命名成员的l值或G,Clang中的选项激活虚拟分派的选择?
谢谢,我在S.O.的第一个问题.永远.
UPDATE
引用标准的答案和注释是准确的:根据标准,sb将永远引用精确类型B的对象,允许内存改变类型,但是通过sb对该内存的任何使用都是“未定义的”行为“,即禁止,或编译器可以翻译,但它喜欢.如果Space只是chars的缓冲区,那么就地构造,破坏,改变类型是有效的.在导致这个问题的代码中确实如此,它与标准兼容AFAIK一起使用.
谢谢.
最佳答案 表达式new(& s.b)D;重新使用名为s.b且以前由B占用的存储器来存储新的D.
然而你然后写s.b.something(); .这会导致未定义的行为,因为s.b表示B,但存储在该位置的实际对象是D.参见C 14 [basic.life] / 7:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
[…]
最后一个要点不满意,因为新类型不同.
(代码后面还有其他潜在问题,但由于此处引起了未定义的行为,因此它们没有实际意义;您需要进行重大的设计更改以避免此问题).