views:

396

answers:

8

This is our ideal inheritance hierarchy:

class Foobar;

class FoobarClient : Foobar;

class FoobarServer : Foobar;

class WindowsFoobar : Foobar;

class UnixFoobar : Foobar;

class WindowsFoobarClient : WindowsFoobar, FoobarClient;

class WindowsFoobarServer : WindowsFoobar, FoobarServer;

class UnixFoobarClient : UnixFoobar, FoobarClient;

class UnixFoobarServer : UnixFoobar, FoobarServer;

This is because the our inheritance hierarchy would try to inherit from Foobar twice, and as such, the compiler would complain of ambiguous references on any members of Foobar.

Allow me to explain why I want such a complex model. This is because we want to have the same variable accessible from WindowsFoobar, UnixFoobar, FoobarClient, and FoobarServer. This wouldn't be a problem, only I'd like to use multiple inheritance with any combination of the above, so that I can use a server/client function on any platform, and also use a platform function on either client or server.

I can't help but feel this is a somewhat common issue with multiple inheritance... Am I approaching this problem from completely the wrong angle?

Update 1:

Also, consider that we could use #ifdef to get around this, however, this will tend to yield very ugly code like such:

CFoobar::CFoobar()
#if SYSAPI_WIN32
: m_someData(1234)
#endif
{
}

... yuck!

Update 2:

For those who want to read more into the background of this issue, I really suggest skimming over the appropriate mailing list thread. Thing start to get interesting around the 3rd post. Also there is a related code commit with which you can see the real life code in question here.

+1  A: 

There is nothing "illegal" about having the same base class twice. The final child class will just (literally) have multiple copies of the base class as part of it (including each variable in the base class, etc). It may result in some ambiguous calls to that base classes' functions, though, which you might have to resolve manually. This doesn't sound like what you want.

Consider composition instead of inheritance.

Also, virtual inheritance is a way to fold together the same base class which appears twice. If it really is just about data sharing, though, composition might make more sense.

Terry Mahaffey
+2  A: 

Use virtual inheritance, in the declaration of FoobarClient, FoobarServer, WindowsFoobar and UnixFoobar, put the word virtual before the Foobar base class name.

This will ensure there is always a single instance of Foobar no matter how many times it appears in your base class hierarchy.

Aviad P.
+12  A: 

It would work, although you'd get two copies of the base Foobar class. To get a single copy, you'd need to use virtual inheritance. Read on multiple inheritance here.

class Foobar;

class FoobarClient : virtual public Foobar;

class FoobarServer : virtual public Foobar;

class WindowsFoobar : virtual public Foobar;

class UnixFoobar : virtual public Foobar;

However, there are many problems associated with multiple inheritance. If you really want to have the model presented, why not make FoobarClient and FoobarServer take a reference to Foobar at construction time, and then have Foobar& FoobarClient/Server::getFoobar ?

Composition is often a way out of multiple inheritance. Take a example now:

class WindowsFoobarClient : public WindowsFoobar 
{
    FoobarClient client;
public:
    WindowsFoobarClient() : client( this ) {}
    FoobarClient& getClient() { return client }
}

However care must be taken in using this in the constructor.

Kornel Kisielewicz
Same answer as Aviad, but +1 vote for the example.
nbolton
Thanks for extending your answer to provide an alternative; I had not considered this.
nbolton
You're welcome :)
Kornel Kisielewicz
Kornel, I ended up doing it this way - although I did it the other way around; I renamed `WindowsFoobar` to `WindowsFoobarUtil` and created an `m_util` member for `Foobar`. The `WindowsFoobarUtil` instance is created in the constructor of each platform client/server class (e.g. `WindowsFoobarClient`).
nbolton
Glad it worked :)
Kornel Kisielewicz
+2  A: 

Have a look at this search. Diamond inheritance is somewhat of contentuous issue and the proper solution dependes on individual situation.

I would like to comment on the Unix/Windows side of things. Generally one would #ifndef things out that are not appropriate for the particular platform. So you would end up with just Foobar compiled for either Windows or Unix using preprocessor directives, not UnixFoobar and WindowsFoobar. See how far you can get using that paradigm before exploring virtual inheritance.

Igor Zevaka
We had previously used #ifdefs, but because of the amount of code, this has started to get very messy over the course of 10 years. I am using this as an experimental replacement for the #ifdef paradigm... but I'm very worried this will make the code more complex, which will defeat the purpose I think.
nbolton
Right, sounds like a bit of a painful situation. I am not very familiar with cross compiling, it might pay off to see what patterns other people are using for this problem.
Igor Zevaka
+3  A: 

What you are directly after here is virtual inheritance feature of C++. What you are in here for is a maintenance nightmare. This might not be a huge surprise since well-known authors like H. Sutter have been arguing against such use of inheritance for a while already. But this comes from direct experience with code like this. Avoid deep inheritance chains. Be very afraid of the protected keyword - it's use is very limited. This kind of design quickly gets out of hand - tracking down patterns of access to protected variable somewhere up the inheritance chain from lower level classes becomes hard, responsibilities of the code parts become vague, etc., and people who look at your code a year from now will hate you :)

Nikolai N Fetissov
+1 "and people who look at your code a year from now will hate you"
nbolton
+2  A: 

Try this example of composition and inheritance:

class Client_Base;
class Server_Base;

class Foobar
{
  Client_Base * p_client;
  Server_Base * p_server;
};

class Windows_Client : public Client_Base;
class Windows_Server : public Server_Base;

class Win32 : Foobar
{
  Win32()
  {
    p_client = new Windows_Client;
    p_server = new Windows_Server;
  }
};

class Unix_Client : public Client_Base;
class Unix_Server : public Server_Base;

class Unix : Foobar
{
  Unix()
  {
    p_client = new Unix_Client;
    p_server = new Unix_Server;
  }
};

Many experts have said that issues can be resolved with another level of indirection.

Thomas Matthews
I still consider `#ifdef` to be severly evil, based on past experience.
Thomas Matthews
+2  A: 

You're in C++, you should get friendly with templates. Using the template-argument-is-a-base-class pattern, you'll not need any multiple inheritance or redundant implementations. It will look like this:

class Foobar {};

template <typename Base> class UnixFoobarAspect : public Base {};
template <typename Base> class WindowsFoobarAspect : public Base {};
template <typename Base> class FoobarClientAspect : public Base {};
template <typename Base> class FoobarServerAspect : public Base {};

typedef UnixFoobarAspect<FoobarClientAspect<Foobar>/*this whitespace not needed in C++0x*/> UnixFoobarClient;
typedef WindowsFoobarAspect<FoobarClientAspect<Foobar> > WindowsFoobarClient;
typedef UnixFoobarAspect<FoobarServerAspect<Foobar> > UnixFoobarServer;
typedef WindowsFoobarAspect<FoobarServerAspect<Foobar> > WindowsFoobarServer;

You might also consider using the curiously recurring template pattern instead of declaring abstract functions to avoid virtual function calls when the base class needs to call a function implemented in one of the specialized variants.

Ben Voigt
Um, don't you think this is going a little overboard for such a small example? Considering also that it will force to inline all of those classes?
Kornel Kisielewicz
Why do you think it is a small example? The OP didn't provide the body for ANY of the classes, there could be, and probably are, dozens of member functions implemented at each level of the hierarchy.It could be simplified, the bottom level of the hierarchy can be straight inheritance without using templates.Yes, all the classes will get inlined during instantiation, which is usually a huge performance win compared to virtual calls. You can still use explicit instantiation so the compiler only has to process the function bodies once, instead of every compilation unit where they are used.
Ben Voigt
This example is one of the ways to do a "policy-based design" (others don't use inheritance), and looking up information on PBD may provide you with useful insights and examples towards solving this kind of problem.
Narfanator
You've made WindowsAspect inherit from ServerAspect, which seems arbitrary. Why break the symmetry in one direction and not the other? Why is it better to introduce arbitrary relationships into the design, than it is to use multiple inheritance?
Steve Jessop
A: 

You can access the variable with the qualified class name, but I forget the exact syntax.

However, this is one of the bad cases of using multiple inheritance that can cause you many difficulties. Chances are that you don't want to have things this way.

It's much more likely you want to have foobar privately inherited, have each subclass own a foobar, have foobar be a pure virtual class, or have the derived class own the things it currently defines or even define foobar on its own.

Charles Eli Cheese