您不能更改对象的类型。您可以销毁对象并在同一内存中创建新的东西 - 这与“更改”对象类型最接近。这也是为什么对于某些代码编译器实际上会重新读取 vtable 的原因。但是检查这个https://godbolt.org/z/Hmq_5Y - vtable 只读一次。一般来说 - 不能改变类型,但可以从灰烬中摧毁和创造。
免责声明:拜托,拜托,不要做那样的事情。这是一个糟糕的想法,混乱,任何人都难以理解,编译器可能对它的理解略有不同,一切都会变得很糟糕。如果你问这样的问题,你肯定不想在实践中实现它们。询问您真正的问题,我们将解决它。
编辑:这不会飞:
#include <iostream>
class A {
public:
virtual int GetVal() const = 0;
};
class C final : public A {
public:
int GetVal() const override {
return 0;
}
};
class B final : public A {
public:
int GetVal() const override {
const void* cptr = static_cast<const void*>(this);
this->~B();
void* ptr = const_cast<void*>(cptr);
new (ptr) C();
return 1;
}
};
int main () {
B b;
int sum = 0;
for (int i = 0; i < 10; ++i) {
sum += b.GetVal();
}
std::cout << sum << "\n";
return 0;
}
为什么?因为在主编译器B
中将其视为最终编译器,而语言规则的编译器知道,它控制对象的生命周期b
。因此它优化了虚拟表调用。
此代码有效:
#include <iostream>
class A {
public:
virtual ~A() = default;
virtual int GetVal() const = 0;
};
class C final : public A {
public:
int GetVal() const override {
return 0;
}
};
class B final : public A {
public:
int GetVal() const override {
return 1;
}
};
static void call(A *q, bool change) {
if (change) {
q->~A();
new (q) C();
}
std::cout << q->GetVal() << "\n";
}
int main () {
B *b = new B();
for (int i = 0; i < 10; ++i) {
call(b, i == 5);
}
return 0;
}
我曾经new
在堆上分配,而不是在堆栈上。这可以防止编译器假定b
. 这反过来意味着它不再可以假设内容b
可能不会改变。请注意,尝试在GetVal
方法中从灰烬中提升可能也不会顺利 -this
对象必须至少与调用一样长GetVal
。编译器会怎么做?你的猜测和我的一样好。
通常,如果您编写代码,这让编译器将如何解释它留下任何疑问(换句话说,您进入“灰色区域”,您、编译器制造商、语言编写者和编译器本身可能会有不同的理解),您自找麻烦. 请不要那样做。询问我们,为什么您需要这样的功能,我们会告诉您,如何根据语言规则实现它,或者如何解决缺少它的问题。