views:

277

answers:

7

I'm trying to derive a new class from an old one. The base class declaration looks like this:

class Driver : public Plugin, public CmdObject
{
protected:
    Driver();

public:
    static Driver* GetInstance();
    virtual Engine& GetEngine();
public:
    // Plugin methods...
    virtual bool InitPlugin (Mgr* pMgr);
    virtual bool Open();
    virtual bool Close();

    // CmdObject
    virtual bool ExecObjCmd(uint16 cmdID, uint16 nbParams, CommandParam *pParams, CmdChannelError& error);

    Mgr *m_pMgr;

protected:
    Services *m_pServices;
    Engine m_Engine;
};

Its constructor looks like this:

Driver::Driver() : 
    YCmdObject("Driver", (CmdObjectType)100, true),
    m_Engine("MyEngine")
{
    Services *m_pServices = NULL;
    Mgr *m_pMgr = NULL;
}

So when I created my derived class, I first tried to simply inherit from the base class:

class NewDriver : public Driver

and copy the constructor:

NewDriver::NewDriver() : 
    CmdObject("NewDriver", (EYCmdObjectType)100, true),
    m_Engine("MyNewEngine")
{
    Services *m_pServices = NULL;
    Mgr *m_pMgr = NULL;
}

The compiler (VisualDSP++ 5.0 from Analog Devices) didn't like this:

".\NewDriver.cpp", line 10: cc0293:  error: indirect nonvirtual base
      class is not allowed
 CmdObject("NewDriver", (EYCmdObjectType)100, true),

That made sense, so I decided to directly inherit from Plugin and CmdObject. To avoid multiple inheritance ambiguity problems (so I thought), I used virtual inheritance:

class NewDriver : public Driver, public virtual Plugin, public virtual CmdObject

But then, in the implementation of a virtual method in NewDriver, I tried to call the Mgr::RegisterPlugin method that takes a Plugin*, and I got this:

".\NewDriver.cpp", line 89: cc0286:  error: base class "Plugin" is
      ambiguous
 if (!m_pMgr->RegisterPlugin(this))

How is the this pointer ambiguous, and how do I resolve it?

Thanks,

--Paul

+4  A: 

If you derive from Driver, you don't have to call the constructors of Drivers bases explicitly:

class NewDriver : public Driver { /* ... */ };
NewDriver::NewDriver() : Driver() {}

The constructor of Driver then initializes its own bases, you don't have to and shouldn't do that directly.
If it should behave differently, let it take parameters:

class Driver : /* ... */ {
public:
    Driver(const std::string& name /* , ... */)
      : CmdObject(name /* , ... */)
    {}
    // ...
};

NewDriver::NewDriver() : Driver("NewDriver" /* , ... */) {}
Georg Fritzsche
Strictly speaking, if the base class ctor doesn't take any parameters, you don't even need to put it in the initialization list since it'll be default constructed automatically.
dash-tom-bang
@dash, That's not entirely accurate (at least not with g++). Virtually inherited types will be initialized before non-virtually inherited types. Thus, if the initialization order differs between the implied order (from the physical inheritance hierarchy) versus the physical, some compilers will at a minimum start emitting warnings about initialization order being rearranged (even if you're omitting initialization lists and allowing the compiler to do the ordering itself).
Nathan Ernst
I simply changed the wording slightly, i don't want to get in all the other possible intricacies here.
Georg Fritzsche
@Nathan Ah yeah, good point. I try to avoid chains-of-inheritance myself, I don't think I've ever left a virtual inheritance in place for more than a couple of minutes. :)
dash-tom-bang
@dash, I try and avoid them myself, but sometimes it's unavoidable. I, myself, only recently became aware of this as I upped the warning level in g++ from `-Wall` to `-Wextra` in an effort to warn on an unrelated issue (I think it was an empty control block, IIRC). The warnings are rather counterintuitive, as well, in that you have to look at the entire hierarchy in order to decide your initialization list order. Pain in the arse...
Nathan Ernst
A: 

I'm not certain that introducing Virtual Inheritance is the way you want to go here. The initial error you got was valid - you're trying to invoke the CmdObject() ctor from 'above' the Driver class. Can you add another ctor to the Driver class?

JBRWilkinson
+1  A: 

Georg has the correct answer, there is definitely no need to mess with multiple inheritance in this case.

Using multiple inheritance, especially to create the dreaded diamond is strongly discouraged, and will lead to a great deal of confusion and frustration for the vast majority of C++ programmers.

Tim Sylvester
A: 

If you had some reason for wanting to inherit from those classes again, then you shouldn't use virtual inheritance. If you want to use virtual inheritance, you need to specify that on the more-base classes. However, you don't want to use virtual inheritance here. Just leave out the initializer of the base classes, since Driver is already doing that. In your example, you don't need a NewDriver constructor at all, and if your implementation actually does need one, then it should only be used to initialize the member variables of NewDriver and it should expect that Driver does the right thing. If Driver does not do the right thing, just give it a constructor that takes appropriate parameters and have that constructor do the right thing. E.g.

class Driver : public Stuff
{
public:
    Driver(const char* enginename) : Stuff(99), m_Engine(enginename) { }

private:
   Engine m_Engine;
};

class NewDriver : public Driver
{
public:
   NewDriver() : Driver("New Driver!") { } // yes, Stuff gets initialized with 99 automatically!
};
dash-tom-bang
A: 

The CmdObject within Driver should be sufficient. Adjust Driver to have a protected constructor which can customize the CmdObject within specified limits.

class Driver ...
protected:
    Driver( std::string driverName, std::string engineName );

Driver::Driver( std::string driverName, std::string engineName ) :
    YCmdObject(driverName, (CmdObjectType)100, true),
    m_Engine(engineName) {

NewDriver::NewDriver() : 
    Driver( "NewDriver", "MyNewEngine" ) {
    ...
}
Potatoswatter
A: 

If You create a hierarchy:

class A {public:  a(int i){} };
class B : public A {public:  b(){} };
class C : public B {public:  c(); };

You can't pass arguments to the A's constructor directly form the C's constructor

C::C() : A(5) {} // illegal

unless A is a virtual base class of B which is a special case and in this case it is useful. Please check the links provided by Tim Sylvester.

The fact that the compiler mentions this case doesn't mean it's a solution for You.

If You create a hierarchy like in Your code, You have 2 subobjects of type Plugin and 2 subobject of type CmdObject in Your NewDriver object. In this case, if You try to downcast the this pointer of type NewDriver* for example to the type Plugin*, the compiler has no idea how to adjust the address, because it doesn't know to which of the 2 subobjects present in Your NewDriver object that pointer is supposed to point to. That's where the ambiguity mentioned in the error message comes form. There is a way to tell the compiler, but I think the mess I'm describing should convince You already that is not the way.

Maciej Hehl
A: 

The golden rule of multiple inheritance -- ALL public bases of ALL classes MUST be virtual. As long as you follow that rule, multiple inheritance will work properly. In your case, you're getting the ambiguous base class errors because Plugin is not declared virtual in Driver

Chris Dodd