Virtual destructor moves object out of rodata section
The moment you declare a virtual method, you add a non-constant pointer to your class that points to the virtual table of that class. This pointer will first be initialized to Object's virtual table, and then continue to change to the derived classes' virtual pointers throughout the constructor chain. It will then change again during the destructor chain and roll-back until it points to Object's virtual table. That would mean that your object can no longer be a pure read-only object and must move out of .rodata.
A cleaner solution would either be to omit any virtual function in your classes, or to avoid inheritence entirely and use templates to replace the required virtual function calls with compile time calls.
For classes having virtual methods, compiler has to define vtables for each class in order to dynamically dispatch virtual method calls based on the type the object has. So, every objects of such classes have a hidden pointer to their types' vtable. This pointer is added to the class by the compiler and isn't const
and changes throughout ctor and dtor call chain, so your instance
isn't const
and can't be in .rodata
.
An example demonstrating access to virtual methods through pointer to 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;
}
This code only works with particular compilers. Also note that the standard doesn't specify the implementation details of vtables, pointers to them and where they're stored, etc.