gpt4 book ai didi

c++ - 在 C++ 中更改对象的动态类型

转载 作者:太空狗 更新时间:2023-10-29 20:33:10 25 4
gpt4 key购买 nike

在以下问题中,答案之一表明对象的动态类型不能更改:When may the dynamic type of a referred to object change?

但是,我从 CPPCon 或其他 session 的一些发言人那里听说这不是真的。

事实上这似乎不是真的,因为 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;
}

https://godbolt.org/z/MA1v8I

但是,如果添加以下内容:

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 指针很便宜,并且主要是出于纯粹的兴趣而问。是什么禁用了此类编译器优化?

最佳答案

您不能更改对象的类型。您可以销毁对象并在同一内存中创建新的东西 - 这是最接近“更改”对象类型的方法。这也是为什么对于某些代码编译器实际上会重新读取 vtable 的原因。但是检查这个 https://godbolt.org/z/Hmq_5Y - vtable 只读一次。一般来说 - 不能改变类型,但可以从 Ember 中摧毁和创造。

免责声明:拜托,拜托,不要做那样的事。这是一个糟糕的想法,困惑,任何人都难以理解,编译器可能对它的理解略有不同,一切都会变得很糟糕。如果你问出那种问题,你肯定不想在实践中实现它们。提出您真正的问题,我们会解决它。

编辑:这不是苍蝇:

#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 方法中从 Ember 中复活也可能不会顺利 - this 对象必须至少与调用 GetVal。编译器会用它做什么?你的猜测和我的一样好。

一般来说,如果您编写的代码对编译器将如何解释它存在任何疑问(换句话说,您进入了“灰色区域”,您、编译器制造商、语言编写者和编译器本身可能对此有不同的理解),您自讨苦吃。拜托,不要那样做。问我们,为什么您需要这样的功能,我们会告诉您如何根据语言规则实现它,或者您如何解决缺少它的问题。

关于c++ - 在 C++ 中更改对象的动态类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56622499/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com