在以下问题中,答案之一表明对象的动态类型不能更改:When may the dynamic type of a referred to object change?
但是,我听说在CPPCon或其他会议上的某些发言者的说法并非如此。
实际上,这似乎并不正确,因为在以下示例的每次循环迭代中,GCC和Clang都重新读取了vtable指针:
class A {
public:
virtual int GetVal() const = 0;
};
int f(const A& a){
int sum = 0;
for (int i = 0; i < 10; ++i) {
// re-reads vtable pointer for every new call to GetVal
sum += a.GetVal();
}
return sum;
}
但是,如果添加以下内容:
class B final : public A {
public:
int GetVal() const override {
return 1;
}
};
int g(const B& b){
int sum = 0;
for (int i = 0; i < 10; ++i) {
sum += b.GetVal();
}
return sum;
}
然后将功能g
简化为return 10;
,这实际上是由于final
而引起的。这也表明,动态可能发生变化的唯一可能位置是在GetVal
内部。
我知道重新读取vtable指针很便宜,并且询问主要是出于纯粹的兴趣。是什么阻碍了此类编译器优化?
答案 0 :(得分:5)
您不能更改对象的类型。您可以销毁对象并在同一内存中创建新的东西-这与“更改”对象类型最接近。这也是为什么某些代码编译器实际上会重新读取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
的时间。编译器将如何处理?你的猜测和我的一样。
通常,如果您编写代码,则毫无疑问,编译器将如何解释它(换句话说,您输入“灰色区域”,您,编译器制造商,语言作者和编译器本身可能会对此有所不同),找麻烦。拜托,不要那样做。问我们,为什么需要这样的功能,我们将告诉您,如何根据语言规则实现它,或者如何解决缺少它的问题。