5

I'm somewhat new to the more advanced features of C++. Yesterday, I posted the following question and I learned about virtual inheritance and the dreaded diamond of death.

Inheriting from both an interface and an implementation C++

I also learned, through other links, that multiple inheritance is typically a sign of a bad code design and that the same results can usually be better achieved without using MI. The question is... I don't know what is a better, single-inheritance approach for the following problem.

I want to define an Interface for two types of Digital Points. An Input Digital Point and an Output Digital Point. The Interface is to be slim, with only what's required to access the information. Of course, the vast majority of properties are common to both types of digital points. So to me, this is a clear case of Inheritance, not Composition.

My Interface Definitions look something like this:

// Interface Definitions
class IDigitalPoint
{
public:
  virtual void CommonDigitalMethod1() = 0;
};

class IDigitalInputPoint : virtual IDigitalPoint
{
public:
  virtual void DigitialInputMethod1() = 0;
};

class IDigitalOutputPoint : virtual IDigitalPoint
{
public:
  virtual void DigitialOutputMethod1() = 0;
};

My implementations look like this:

// Implementation of IDigitalPoint
class DigitalPoint : virtual public IDigitalPoint
{
public:
  void CommonDigitalMethod1();
  void ExtraCommonDigitalMethod2();
}

// Implementation of IDigitalInputPoint
class DigitalInputPoint : public DigitalPoint, public IDigitalInputPoint 
{
public:
  void DigitialInputMethod1();
  void ExtraDigitialInputMethod2();
}

// Implementation of IDigitalOutputPoint
class DigitalOutputPoint : public DigitalPoint, public IDigitalOutputPoint 
{
public:
  void DigitialOutputMethod1();
  void ExtraDigitialOutputMethod2();
}

So how could I reformat this structure, to avoid MI?

Greenonline
  • 1,330
  • 8
  • 23
  • 31
ThermoX
  • 277
  • 3
  • 11
  • With `IDigitalPoint` always being virtually inherited, I don't see an issue. Now, if you were to inherit from the last two classes, since both of them inherit non-virtually from `DigitalPoint` that would be an issue. If that's what you're asking about, you need to clarify that. PS: learning C++ from a good book is always better than asking questions on stackoverflow.com – Sam Varshavchik Jun 22 '16 at 10:57
  • @Sam You are right. Multiple inheritance isn't always bad. If OP want to see a rather good example for good multiple inheritance look at the policy chapter in Alexandrescues Modern C++ Design. But this is just my opinion. – Mehno Jun 22 '16 at 11:18
  • 3
    MI is very easy to misuse. But, used well, it is very useful. Most of the statements I've seen about MI being bad come from people who tend to misuse it - and then blame the tool for the resultant problems rather than the person wielding it. – Peter Jun 22 '16 at 11:50
  • @Sam Varshavchik I *do* intend to inherit from the last 2 classes! Those would be single-inheritance from DigitalInputPoint & DigitalOutputPoint. In fact, my original intent was to actually create the objects from derived classes, and upcast them into DigitalInputPoint & DigitalOutputPoint, for higher level code handling. Based on what I read on MI, the memory layout can be a little messy and non-optimized. Am I running the risk of running into unexpected issues, if I instantiate objects from derived DigitalInputPoint & DigitalOutputPoint classes? – ThermoX Jun 22 '16 at 12:00
  • It's not your job to worry about a class's internal memory layout. The compiler will figure it out. – Sam Varshavchik Jun 22 '16 at 12:07
  • Well... Yes... except for the optimization part. It's useful to realize that you're impacting performance when you're using MI. But once the DigitalInputPoint & DigitalOutputPoint classes have been properly merged (assuming I did, above)... Can I then derive classes from DigitalInputPoint & DigitalOutputPoint (single inheritance), without worrying too much about what the fact that DigitalInputPoint & DigitalOutputPoint were created using MI? – ThermoX Jun 22 '16 at 12:11
  • Note: `IDigitalInputPoint` and `IDigitalOutputPoint` messing things up due to private inheritance of `IDigitalPoint` –  Jun 22 '16 at 12:12

7 Answers7

7

"multiple inheritance is typically a sign of a bad code design" - parents that are pure interfaces are not counted in regards to this rule. Your I* classes are pure interfaces (only contain pure virtual functions) so you Digital*Point classes are OK in this respect

mvidelgauz
  • 2,176
  • 1
  • 16
  • 23
  • And the interfaces should *not* defined any member variables, correct? At least, not the IDigitalPoint one? Also, on the implementation side, my plan was to derive 2 ultimate classes, from DigitalInputPoint & DigitalOutputPoint and instantiate the objects from those classes. Then, I was going to upcast them into DigitalInputPoint & DigitalOuputPoint for higher level code handling. Will that be an issue? Basically, once you have correctly handled the MI part (assuming I did with my example above), can I safely keep on working from the merged MI versions, without worrying about other issues? – ThermoX Jun 22 '16 at 12:07
  • I you will derive other class from both DigitalInputPoint and DigitalOutputPoint you will get diamond problem because those are not pure interfaces (I assume `DigitalPoint` does have member variables). But as people already mentioned here it is not always a problem and some well regarded libraries (stl for example), just don't forget about virtual inheritance. It is hard to say whether you will or will not have other issues. So far I don't see "stoppers" in your design although it may deserve reconsidering given full requirements. – mvidelgauz Jun 22 '16 at 12:28
  • Alright... So the search continues... Thanks for your inputs, mvidelgauz... – ThermoX Jun 22 '16 at 12:36
  • So I *did* end up keeping the IM. Also, maybe I didn't explain properly... But I meant to say that I was going to derive single-inheritance classes on both DigitalInputPoint and DigitalOutputPoint (each one has 1 derived class). It worked out fine. I did have to make sure I "deleted" the inherited class at the same lever as I had created them, which was all the way down to the lowest level. In VS debug mode, everything looks ok, as far as I can tell. If I set a value through one class path, I see the same value affected through the other path. So no hidden duplicates. Thanks! – ThermoX Jun 23 '16 at 21:37
1

(Multiple) inheritance and interfaces tend to needless complications of simple relations.

Here we need only a simple structure and few freestanding functions:

namespace example {
    struct Point { T x; T y; }

    Point read_method();
    void write_method(const Point&)
    void common_method(Point&);
    void extra_common_method(Point&);
} // example

The common_method might be a candidate for a member function of Point. The extra_common_method, which is not so common, might be a candidate for another class encapsulating a Point.

0

This is exactly the situation in which the standard library does use virtual inheritance, in the std::basic_iostream hierarchy.

So, it may be the rare case where it genuinely makes sense.

However, this depends on exactly the fine details you've removed for clarity, so it isn't possible to say for certain whether a better solution exists.

For example, why is an input point different from an output point? A DigitalPoint sounds like a thing with properties, that might be modeled by a class. A DigitalInputPoint, however, just sounds like ... a DigitalPoint somehow coupled to an input source. Does it have different properties? Different behaviour? What are they and why?

Useless
  • 64,155
  • 6
  • 88
  • 132
0

You can go to below link to understand more about multiple inheritance Avoid Multiple Inheritance

Also, in your case, multiple inheritance makes sense!!. You may use composition if you want.

Community
  • 1
  • 1
Bhuwan
  • 225
  • 5
  • 15
  • Thanks. I read that link yesterday and is what actually prompted me to write this question. Obviously, my problem can be solved Not using MI since C# doesn't allow it. I'm just wondering about a clean way of doing it. And still exploring the answers here. Thanks for your link. – ThermoX Jun 22 '16 at 11:36
0

Consider a different approach:

class DigitalPoint
{
public:
  void CommonDigitalMethod1();
  void ExtraCommonDigitalMethod2();
}

// Implementation of IDigitalInputPoint
class DigitalInputPoint
{
public:
  void CommonDigitalMethod1();
  void DigitialInputMethod1();
  void ExtraDigitialInputMethod2();
}

// Implementation of IDigitalOutputPoint
class DigitalOutputPoint
{
public:
  void CommonDigitalMethod1();
  void DigitialOutputMethod1();
  void ExtraDigitialOutputMethod2();
}

To be used like this:

template <class T>
void do_input_stuff(T &digitalInputPoint){
    digitalInputPoint.DigitialInputMethod1();
}

You get an easier implementation with a clearer design and less coupling with most likely better performance. The only One downside is that the interface is implicitly defined by the usage. This can be mitigated by documenting what the template expects and eventually you will be able to do it in concepts to have the compiler check it for you.
Another downside is that you cannot have a vector<IDigitalPoint*> anymore.

nwp
  • 9,623
  • 5
  • 38
  • 68
  • This doesn't work if you want to have something like a container of `DigitalPoint` pointers, where the type of each item is only known at runtime. You give up the benefits of dynamic polymorphism. – interjay Jun 22 '16 at 11:17
  • @interjay You are right, I edited it a bit to reflect that. The OP didn't say if that was a requirement. In my experience that requirement is rare and only comes up as an implementation detail. – nwp Jun 22 '16 at 11:30
  • @nwp Wouldn't I have to define CommonDigitalMethod1() on both DigitalInputPoint & DigitalOutputtPoint? I could use an internal pointer to a DigitalPoint instance, then wrapping the DigitalInputPoint::CommonDigitalMethod1() & DigitalOutputPoint::CommonDigitalMethod1() methods around that internal DigitalPoint pointer. Seems convoluted though, doesn't it? – ThermoX Jun 22 '16 at 14:13
  • @ThermoX The solution fitting to this answer is to make a free function `template CommonDigitalMethod1(T &t)` that works with both `DigitalInputPoint` and `DigitalOutputtPoint`. – nwp Jun 22 '16 at 14:22
0

Are you really sure that you need 3 interfaces?

class IDigitalPoint
{
public:
    virtual void CommonDigitalMethod1() = 0;
};


enum class Direction : bool { Input, Output };

template <Direction direction>
class DigitalPoint : public IDigitalPoint
{
public:
    void CommonDigitalMethod1() {}
    void ExtraCommonDigitalMethod2() {}

    virtual void DigitialMethod1() = 0;
};

class DigitalInputPoint : public DigitalPoint<Direction::Input>
{
public:
    void DigitialInputMethod1() {}
    void ExtraDigitialInputMethod2() {}

    // This is like DigitialInputMethod1()
    virtual void DigitialMethod1() override
    {}
};

class DigitalOutputPoint : public DigitalPoint<Direction::Output>
{
public:
    void DigitialOutputMethod1() {}
    void ExtraDigitialOutputMethod2() {}

    // This is like DigitialOutputMethod1()
    virtual void DigitialMethod1() override
    {}
};
slasla
  • 31
  • 5
  • Hi @slasla. Thanks for your input. However, DigitalInputPoint & DigitalOutputPoint may different in the number of methods (and, in fact, do in my real-life problem). Also, even if both had a single method, it doesn't mean they would imply the same process in both classes, and thus, justify a common name. – ThermoX Jun 22 '16 at 13:19
0

You could use composition instead of inheritance. Live Example

If the child classes do not use functionality from DigitalPoint, then you can try using CRTP. It can be confusing if you don't understand CRTP, but it works like a charm when it fits properly. Live Example

Andrew
  • 603
  • 1
  • 5
  • 13
  • Thanks Andrew. You've added up a few extra layers to my knowledge... I mentally find it hard to separate the common portions of the Digital Points, from their specific versions. Clearly, a Digital Input Point is just a more specific version of a Digital Point, in my case. To have its properties split into two separate, disconnected classes feels odd. But I agree, your option would avoid multiple inheritances. I'm just wondering what would people do, in this case, if it was written in C#?? Does C# offer other options to deal with these cases, using single inheritances? – ThermoX Jun 22 '16 at 16:05
  • @ThermoX C# offers generics (not to be confused with C++ templates!!!) that can help to build good design for this situation. How exactly - out of scope of this question and comment length limit :) – mvidelgauz Jun 23 '16 at 13:46