Upcasting to superclasses or interfaces?
-
20-06-2021 - |
Question
I am trying confirm the theory behind something that I have already got working in practice. The full setup is somewhat contorted, as the functionality is split between different dlls, but I'll try to describe the situation:
class __declspec( dllexport ) BaseClass
/** This class' definition is available to everything,
* via .h file, .dll and .lib. */
{
protected:
std::string name;
public:
std::string GetName();
/** This is implemented in BaseClass, and just returns this->name. */
}
class DerivedClass: public BaseClass
/** This class is created within the executable, but is not 'visible' to other
* dlls - either through .h files, .lib, or anything else. */
{
public:
DerivedClass();
/** This sets this->name based on its own propertied. */
}
This upcasting works, but it requires full access to the definition of DerivedClass:
void* pointer;
DerivedClass* derived_pointer = reinterpret_class<DerivedClass*>(pointer);
BaseClass* base_pointer = dynamic_cast<BaseClass*>(derived_pointer);
base_pointer->GetName();
However, the following does NOT work:
void* pointer;
BaseClass* base_pointer = reinterpret_class<BaseClass*>(pointer);
base_pointer->GetName();
In order to get round this problem, I've implemented an interface:
class __declspec( dllexport ) IBaseClass
/** Fully virtual 'interface' class, in same file as BaseClass. */
{
public:
virtual std::string GetName() = 0;
}
class __declspec( dllexport ) BaseClass: public IBaseClass
/** This class' definition is available to
* everything, via .h file, .dll and .lib. */
{
protected:
std::string name;
public:
std::string GetName();
/** This is implemented in BaseClass, and just returns this->name. */
}
class DerivedClass: public BaseClass
/** This class is created within the executable, but is not 'visible'
* to other dlls - either through .h files, .lib, or anything else. */
{
public:
DerivedClass();
/** This sets this->name based on its own propertied. */
}
And now the following code does work:
void* pointer;
IBaseClass* ibase_pointer = reinterpret_class<IBaseClass*>(pointer);
ibase_pointer->GetName();
I have some dim memory of somebody telling me that casting to a fully virtual class is a special case - but I can't remember why, or find anything on the web about it.
Please help me - why does my code work?!
La solution
This is completely dependent on class layout, which is implementation-defined and cannot be relied on. Specifically for MSVC, a good intro to class layout is http://www.openrce.org/articles/full_view/23 and it's worth knowing that you can ask for class layout with /d1reportSingleClassLayout
flag.
In your case, since the first BaseClass
has no virtual members it will be placed inside DerivedClass
at a non-specified location. I'd guess that DerivedClass
has some virtual members as otherwise I'd expect BaseClass
to be at the start of DerivedClass
and the reinterpret_cast
to work. If it has virtual members you'd have:
+--------------------+
|DerivedClass vtable |
|BaseClass::name |
|DerivedClass members|
By adding the interface IBaseClass nothing changes; DerivedClass is still laid out as:
+--------------------+
|DerivedClass vtable |
|BaseClass::name |
|DerivedClass members|
However the DerivedClass
vtable starts with the IBaseClass
vtable:
+---------------------+
|IBaseClass::GetName |
|DerivedClass virtuals|
so calling through the DerivedClass
vtable works.