Thread: Multiple inheritance: casting yields different address

  1. #1
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057

    Multiple inheritance: casting yields different address

    The problem

    In a rather large C++ project I'm working on at the moment (callis -- which at 17,000 lines has passed xuni for size!), I'm having trouble with multiple inheritance. I've created a ReferenceCounter class to perform memory management, automatically freeing memory when the reference count reaches zero. Naturally, sometimes I want to refer to a derived class with a base class pointer, and register both types of pointers in the ReferenceCounter. This has worked very well so far, but now I've used multiple inheritance.

    The inheritance tree looks something like this:
    Code:
    class Base {
    public:
        virtual ~Base() {}
    };
    class Branch1 : public Base {};
    class Branch2 : public Base {};
    class Derived : public Branch1, public Branch2 {};
    I use code similar to this:
    Code:
    Derived *derived = new Derived();
    ReferenceCounter::add<Derived>(derived);
    
    Base *reference = derived;
    ReferenceCounter::add<Base>(reference);
    The problem is that, since I'm using multiple inheritance, Derived has two vtables, and casting it to a Base gives me a different address! So ReferenceCounter thinks it's a different memory block, and the program soon crashes (try to free memory twice, usually).

    Possible solutions

    I guess there are two ways to fix this. Either re-write/redesign the ReferenceCounter or don't use multiple inheritance. The ReferenceCounter is using const void* pointers and uses typeid to remember the type of the original object added to it. I don't really think it can be improved that much, unless there's a way to detect whether a class is multiply-inherited or not.

    Why multiple inheritance

    Maybe there's a better way of doing this. I'll describe my design; please critique it if you think of a better way.

    The code is a simple XML parser (to replace mxml). I have a ResourceElement base class, and subclasses ResourceElementRecursive and ResourceElementData. Then I have MXML and DP (DWK's Parser) implementations of these classes. The DP classes are ResourceElementDP, ResourceElementRecursiveDP, and ResourceElementDataDP.

    These last two classes use multiple inheritance. ResourceElementRecursiveDP, for example, inherits from ResourceElementDP (which provides some DP-necessary stuff) and from ResourceElementRecursive (which specifies the interface the class provides).

    I can't really think of a better way to do this. This pattern occurs elsewhere in the code, however, so I think there might be a better way. I have Image and TransformedImage, for example, and implementations in ImageSDL and TransformedImageSDL (which should inherit from TransformedImage, for the interface, and ImageSDL, for SDL code).

    Maybe I've been using too much Java lately, because it would be a perfect place to use Java interfaces.

    P.S. In GDB, I was having trouble casting variables when the type resides in a namespace, but I figured out how to do this. In case anyone's interested, you have to use single quotes around the name, e.g.
    Code:
    print *('Namespace::SomeDerivedClass' *)instance_of_some_base_class
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,412
    Quote Originally Posted by dwks
    The ReferenceCounter is using const void* pointers and uses typeid to remember the type of the original object added to it. I don't really think it can be improved that much, unless there's a way to detect whether a class is multiply-inherited or not.
    Why didn't you go for say, std::tr1::shared_ptr instead of ReferenceCounter?

    Quote Originally Posted by dwks
    Maybe I've been using too much Java lately, because it would be a perfect place to use Java interfaces.
    A class without data members defines a pure interface, so the C++ near-equivalent to a Java interface is an abstract base class without data members. That said, I think you knew this already

    EDIT:
    Oh, looking at your code again, maybe the appropriate solution in this case is to use virtual inheritance:
    Code:
    class Branch1 : virtual public Base {};
    class Branch2 : virtual public Base {};
    Last edited by laserlight; 06-05-2009 at 10:26 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    maybe the appropriate solution in this case is to use virtual inheritance
    D'oh! Of course that would make sense. I even know what virtual inheritance is, but for some reason I didn't think of it.

    Why didn't you go for say, std::tr1::shared_ptr instead of ReferenceCounter?
    Because I haven't really used boost or tr1 before (I know, this would be a good time to start).

    A class without data members defines a pure interface, so the C++ near-equivalent to a Java interface is an abstract base class without data members. That said, I think you knew this already
    Indeed, and that's what the code does: it provides pure interface classes. I just didn't think of virtual inheritance.

    Sigh. Well, it's a good thing I posted this! Thanks for your help.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  4. #4
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Out of curiosity, do you know whether Boost's shared_ptr handles multiple inheritance? This page does seem to suggest so. Re: boost::shared_ptr and multiple inheritance

    Anyway, I think I'll be trying this myself with Boost, so you can just tell me to go try it.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  5. #5
    Registered User
    Join Date
    May 2009
    Posts
    37
    Quote Originally Posted by dwks View Post
    Maybe there's a better way of doing this. I'll describe my design; please critique it if you think of a better way.
    Without going through your problem in detail (I appologize becuase I'm trying to get something done) would a virtual base class fix your problem? That way you should only have one copy of Base.

    Code:
    class Base {
    public:
        virtual ~Base() {}
    };
    class Branch1 : public virtual Base {};
    class Branch2 : public virtual Base {};
    class Derived : public Branch1, public Branch2 {};

  6. #6
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    Without going through your problem in detail (I appologize becuase I'm trying to get something done) would a virtual base class fix your problem? That way you should only have one copy of Base.
    Uh, read the first reply to the thread

  7. #7
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Well, I'm glad the solution is obvious to everyone except me.

    Too much Java and C, not enough C++.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  8. #8
    Registered User
    Join Date
    May 2009
    Posts
    37
    Quote Originally Posted by bithub View Post
    Uh, read the first reply to the thread
    So sue me

  9. #9
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    An alternative solution is to use an intrusive mechanism to maintain the ref count instead of keeping it in a separate data structure. If all of your classes (virtually) inherit from a class:

    Code:
    class RefCounted
    {
    public:
        RefCounted() : ref( 1 ) {}
        virtual ~RefCounted() {}
        virtual void Ref() { ++ref; }
        virtual bool Deref() { return --ref > 0; }
    
    private:
        int ref;
    };
    Then you can just invoke the ->Ref() and ->Deref() directly on the object and it will maintain the reference count correctly. (And Deref() returns false when the refcount goes to zero -- I did not include the deletion of the object here since that's better managed by the controller, or a smart pointer)

    Or, you could go non-intrusive, meaning that you use a smart pointer which points to the object as well as a dynamically-allocated refcount, in other words, exactly what boost::shared_ptr does.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #10
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    I prefer brewbuck's suggestion, except I like to keep the Ref() and Deref() functions as protected instead of public. Then you can have a separate AutoRef class which does the actual Ref() and Deref() calls to prevent against unwanted reference count leaks.

    For example:
    Code:
    void unsafe_foo()
    {
        MyRefCountedClass* c = CreateRefCountedClass();
        c->Ref(); // This is most likely done by whatever factory creates the object, but we'll put it here
        // code which can throw an exception
        c->Deref(); // Reference count leak if an exception is thrown before we get here.
    }
    
    void safe_foo()
    {
        AutoRef<MyRefCountedClass> ar = CreateRefCountedClass();
        ar->Ref();  // Again, this isn't needed if the factory adds the reference for you
        // code which can throw an exception
        // No need to call Deref() since that is done in the AutoRef's destructor
    }

  11. #11
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    And by the way, the reason I made Ref() and Deref() virtual is to allow the class the option of observing its ref and deref events (in case it wants to do something special whenever it gains/loses a reference)

    EDIT: I forgot to mention that the RefCounted base needs a special copy constructor/assignment operator -- you do NOT want to copy the ref count when you copy one object to another, but that's what would happen with the default copy constructor/assignment operator
    Last edited by brewbuck; 06-05-2009 at 04:43 PM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  12. #12
    Registered User
    Join Date
    Jan 2009
    Posts
    31
    boost::shared_ptr has a copy constructor and operator= that are template members and is designed to allow copying a shared_ptr<T> to a shared_ptr<U>, where T* is static_cast'able to U*. The shared_ptr<U> instance uses the same reference count pointer as the shared_ptr<T>, so it should be compatible with what you're trying to do.

  13. #13
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Thanks for all the suggestions . . . .
    Or, you could go non-intrusive, meaning that you use a smart pointer which points to the object as well as a dynamically-allocated refcount, in other words, exactly what boost::shared_ptr does.
    This is more or less what my ReferenceCounter does (although I think my code is somewhat less efficient!).

    @bithub: Yes, I have a SmartPointer class which is quite similar to your AutoRef.

    I like the idea of "intrusive" reference counting, where you maintain the reference count in the object itself (or a base class of it anyway). It seems like a cleaner solution. I'll have to think about it.

    Anyway, even with virtual inheritance, the object still has different addresses depending on what you cast it to.
    Code:
    Address as a Base:         0x1838010
    Address as a Derived1:     0x1838010
    Address as a Derived2:     0x1838020
    Address as a Conglomerate: 0x1838010
    I managed to get around this problem in my code by never having SmartPointers to anything except the base class. It's not a very nice solution, though, so I'll have to see about implementing intrusive reference counting or using boost's shared_ptr eventually. We'll have to see which.

    Thanks again.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  14. #14
    Registered User
    Join Date
    May 2009
    Posts
    37
    Quote Originally Posted by dwks View Post
    Anyway, even with virtual inheritance, the object still has different addresses depending on what you cast it to.
    Code:
    Address as a Base:         0x1838010
    Address as a Derived1:     0x1838010
    Address as a Derived2:     0x1838020
    Address as a Conglomerate: 0x1838010
    When you think about it, this has to be the case with multiple in heritance. Addresses will always move around. This is why I avoid it if I can do something another way. I think (but don't quote me) if only one of your base classes has any actual data, you won't have this issue. The other thing is since when you cast, your address changes, I imagine the compiler has to check for casting of a null pointer so it stays null.

    I discovered all these issues when going from C to C++. This is one reason why you should avoid the old C style casts for C++.
    Last edited by SyntaxError; 06-08-2009 at 02:41 PM.

  15. #15
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    I wasn't using C-style casts, I was using dynamic_cast.

    I think (but don't quote me) if only one of your base classes has any actual data, you won't have this issue.
    That was my first thought, but having data or having no data makes no difference. My understanding of this is pretty shaky, but here's what I think is happening. I think with multiple inheritance that instances of the class need two vtables, so that if an object is treated as one parent type, the appropriate methods can be looked up and called, and likewise for the other parent type.

    So it would seem to me that there's no way to use multiple inheritance yet have the address of the object, no matter what base class it is casted to, be the same. Anyway, I'm not too concerned about this; I can always use the intrusive idea outlined above by brewbuck and bithub.

    Or I could simply inherit every single class from a given (virtual) base class, then make ReferenceCounter always cast to this class when determining the address. I think that if this base class were virtual, it would always be located in the first vtable (at least that's what my experiments indicate), which would solve the problem.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to Send Mac Address From Client to Server
    By Lieyza197 in forum C Programming
    Replies: 2
    Last Post: 05-27-2009, 09:58 AM
  2. Multiple Inheritance Question
    By ss3x in forum C++ Programming
    Replies: 2
    Last Post: 10-30-2002, 06:09 AM
  3. Multiple Inheritance Ambiguity
    By FillYourBrain in forum C++ Programming
    Replies: 21
    Last Post: 08-23-2002, 10:31 AM
  4. C++ multiple inheritance problem
    By pongsor in forum C++ Programming
    Replies: 7
    Last Post: 02-08-2002, 08:18 PM
  5. Multiple virtual inheritance
    By kitten in forum C++ Programming
    Replies: 3
    Last Post: 08-10-2001, 10:04 PM