我有大量使用常量构造函数构造的静态常量对象,因此它们无需任何构造函数调用就可以立即存储在最终的二进制文件中。
因为我在一个低内存系统(STM32微处理器)上工作,所以我想减少这些对象的内存占用,并且因为它们是常量,所以将它们存储在.rodata部分中。编译器对此进行了管理,没有任何问题。
但是,现在我向基类添加了一个虚拟析构函数,以便删除编译器警告,因此对象被存储在.data部分中。
当然,我可以使用一些#pragma专门删除基类的编译器警告,并删除虚拟析构函数,但我想知道是否有更干净的解决方案。
展示问题的极简主义代码:
class Object {
int value;
public:
constexpr Object(int param)
: value(param) {}
virtual int getValue() const = 0;
virtual ~Object() = default; // This line causes problems
};
class Derived : public Object {
volatile int otherValue;
public:
constexpr Derived(int param1, int param2)
: Object(param1), otherValue(param2) {}
int getValue() const override { return otherValue; }
};
const Derived instance(1,2);
int main() {
return instance.getValue();
}另外,下面是一个使用和不使用虚拟析构函数进行比较的CompilerExplorer:https://godbolt.org/z/M5G7LO
发布于 2019-08-18 19:13:18
一旦声明了一个虚方法,就添加了一个指向类的非常数指针,指向该类的虚拟表。该指针将首先被初始化为对象的虚拟表,然后在整个构造函数链中继续更改为派生类的虚拟指针。然后,它将在析构函数链和回滚期间再次更改,直到它指向对象的虚拟表。这意味着您的对象不能再是纯只读对象,必须移出.rodata。
一种更简洁的解决方案是省略类中的任何虚函数,或者完全避免继承,并使用模板将所需的虚函数调用替换为编译时调用。
发布于 2019-08-18 21:10:45
对于具有虚方法的类,编译器必须为每个类定义vtable,以便根据对象的类型动态调度虚方法调用。因此,这些类的每个对象都有一个隐藏的指向其类型的vtable的指针。这个指针是由编译器添加到类中的,并且不是const的,并且在整个ctor和dtor调用链中都会发生变化,所以您的instance不是const,也不能是.rodata。
一个example,演示通过指向vtable的指针访问虚拟方法。
#include <iostream>
class FooBar {
public:
virtual void foo() { std::cout << "foo" << std::endl; };
virtual void bar() { std::cout << "bar" << std::endl; };
};
int main()
{
FooBar obj;
// first bytes of 'obj' is a pointer to vtable
uintptr_t vtable_ptr = ((uintptr_t*)&obj)[0];
// 'foo' is at index '0' and 'bar' is at index '1'
uintptr_t method_ptr = ((uintptr_t*)vtable_ptr)[1];
// cast it to member pointer
void (*func)(FooBar*) = (void (*)(FooBar*))method_ptr;
// invoke the member function on 'obj'
(*func)(&obj);
return 0;
}这段代码只适用于特定的编译器。还要注意的是,该标准并没有指定vtable的实现细节、指向它们的指针以及它们的存储位置等。
https://stackoverflow.com/questions/57543586
复制相似问题