0

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?!

iammilind
  • 68,093
  • 33
  • 169
  • 336
Mike Sadler
  • 1,750
  • 1
  • 20
  • 37
  • 1
    Upcasting is trivial for `public` interface, so `dynamic_cast<>` is not needed for them. – iammilind Jul 12 '12 at 15:25
  • I'm not sure I follow - I'm not using dynamic_cast with the interface - just reinterpret_cast. Without the interface, it didn't work without the dynamic_cast (my situation is somewhat more complex that that shown above - I am not sure whether the above would work). – Mike Sadler Jul 12 '12 at 15:28
  • 1
    `reinterpret_cast` is wrong for either up- or downcasting. It is strictly for non-portable low-level machine word manipulations. – n. m. could be an AI Jul 12 '12 at 16:51

1 Answers1

1

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.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thanks, ecatmur - I'll have a look at that link tomorrow (it's getting late here). When you say that this is implementation specific, does this mean the whole concept of upcasting is flawed, or is there something specific I've done here that's risky? – Mike Sadler Jul 12 '12 at 16:26
  • @MikeSadler using `reinterpret_cast` is always risky; the safe way to cast is `static_cast` to `void` pointer and back to *the same type*, then using `static_cast` and `dynamic_cast` with object pointers. – ecatmur Jul 13 '12 at 07:06
  • Technically, what I have isn't a void pointer - it is a size_t that has been sent to Fortran and back (as I said, it's contorted). Because of this. I had thought that this was what reinterpret_cast was for: to cast to a completely different type (which I know is risky). Is this the case? – Mike Sadler Jul 13 '12 at 10:51
  • @MikeSadler yes, you need to use `reinterpret_cast` to convert a `size_t` to a pointer. This is safe as long as (a) `size_t` is large enough to hold a pointer, which it is on any sensible platform, and (b) you cast the `size_t` back to the *exact same* pointer type you started from. – ecatmur Jul 13 '12 at 13:55
  • Thanks, ecatmur - that's exactly the information I needed. We've now got it working as planned, but only by removing the dual inheritance (one of the contortions I omitted). From what you described above, the dual inheritance gave the compiler too much latitude to organise the internal memory how it felt like. – Mike Sadler Jul 16 '12 at 07:34