1

I have two (unrelated) classes. The first one is "Point":

typedef std::complex<double> complex_number;

class Point
{
public:
    Point(const complex_number &affix);

    const complex_number & get_affix() const;
    void set_affix(const complex_number & new_affix);

    // etc
private:
    complex_number affix_;
};

(By the way, sorry for asking this unrelated question, but where is the right place to put my type definitions? Is here, top of my point.hpp file, "good practice"?)

The second one is "Abstract_Vertex" (this is meant to be part of an abstract graph later):

typedef int vertex_label;

class Abstract_Vertex
{
public:
    Abstract_Vertex(const vertex_label &label);

    const vertex_label & get_label() const;
    const vertex_label & get_neighbor_label(const int &index) const;
    void set_label(const vertex_label &new_label);

    bool is_neighbor_label(const vertex_label &label) const;

    // etc
protected:
    vertex_label label_;
    std::vector<vertex_label> neighbor_labels_;
};

Now I want to create a third class, "Plane_Vertex" (a vertex located somewhere in the plane). I am pondering two ways to do that :

  1. Multiple inheritance : the Plane_Vertex class inherits from both Point and Vertex, and does not have any members

  2. the Plane_Vertex class only inherits from Vertex and has a private Point point_ member.

Of course I could easily avoid multiple inheritance and just go for option 2., but I am a good little conscientious C++ learner and I want to know what "good practice" would be in my situation.

Thank you for your insights!

Seub
  • 2,451
  • 4
  • 25
  • 34
  • Does it ever make sense for you to want to treat a `Plane_Vertex` like a `Point`? If it does, then multiple-inheritance. – jxh Jun 20 '12 at 00:02
  • possible duplicate of [Prefer composition over inheritance?](http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – Jon Cage Jun 20 '12 at 00:04
  • Check out the duplicate question I linked to; that has some really good answers on composition vs inheritance which is essentially what you're interested in. – Jon Cage Jun 20 '12 at 00:05
  • @user315052 : Well, I guess the question is almost metaphysical, I guess that yes it does make sense, but in my subjective mind a Plane_Vertex is a Vertex before it is a Point. – Seub Jun 20 '12 at 00:47
  • @Jon Cage : Thank you, in fact I am kind of a beginner in C++ and I was not aware of the concept of "composition" (vs inheritance). – Seub Jun 20 '12 at 00:48

2 Answers2

1

There could be a third option. You can do as the standard library does, and make the AbstractVertex a template. You can then implement the graph navigation, and generic functionality in the template, and let the user provide the values to store in the nodes as a template argument.

Because design is compromising, I am going to point out the main differences from my point of view.

Related/Unrelated vertex types

In the template approach, different instantiations are completely different types. That means that Vertex<int> and Vertex<Point> are not related. On the other side, in the polymorphic approach above all vertices IntVertex, PointVertex... are AbstractVertex objects: You can combine them in containers and connect them or use them interchangeably in code that can use AbstractVertex. Whether this is a limitation or an advantage of either approach is, again, a design decision. I prefer the types to be unrelated, as I don't feel that you should mix those types.

Amount of code to write

In the template approach you implement the template and then you just instantiate, in the polymorphic approach you need to provide a new class for each possible element that you want to include in a graph

If at the end you decide to go for the polymorphic approach (I wouldn't), then I would avoid using multiple inheritance from both types, and prefer composition. In general, inheritance is probably the most abused feature of object oriented languages, it can be used to solve many problems, and sometimes it seems like the easiest solution to all, but it is not. In particular neither of your base types seem to be designed for inheritance, there is no functionality to be overridden by derived types, no virtual functions. These are clear indications that you are abusing inheritance where composition suffices.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Your answer is very interesting to me because of what I am doing later on : I have a template class Graph, whose member is a vector, where T is meant to be instantiated either as a Abstract_Vertex or a Plane_Vertex (or, in fact, a CP_Vertex, a third kind). How do you think that plays in favor of one or another option? – Seub Jun 20 '12 at 00:41
  • After thinking about it for a couple minutes, IMHO, the only downside to the template approach in this setting is that while my Plane_Vertex would become a Vertex and my CP_Vertex would become a Vertex (which is fine!), my Abstract_Vertex would more be like a Vertex! That's kind of annoying. – Seub Jun 20 '12 at 00:59
  • @user1367124: If you implement the template approach, then there would not be a `AbstractVertex`, the question is whether in your design you need to use `PointVertex` and `CircleVertex` polymorphically as `AbstractVertex`, or whether that even makes sense or not. If you opt for the template approach, I would expect the `T` type in `Graph` to be the same instantiating type you would use with the `Vertex`. That is, you want a `Graph` that *contains* `Vertex`... The *variable* you want to use is what data is stored (`Point`), not how the graph is built (`Vertex`). – David Rodríguez - dribeas Jun 20 '12 at 01:12
  • ... consider `std::list`, internally the data is stored in some sort of node (let's call it for the sake of argument `node`), you don't want a `std::list< node >`, what you want is a `std::list` which internally uses whatever it deems necessary to hold the `int`s. The fact that it holds `node` is irrelevant to the users. Now, internally `Graph` could use a `std::vector< Vertex >` to store the data, but that is an implementation detail and should be kept out of the `Graph` interface. – David Rodríguez - dribeas Jun 20 '12 at 01:15
  • Thank you for your answers David, here's what I make out of it and still, my question. (1) "If you opt for the template approach, I would expect the T type in Graph to be the same instantiating type you would use with the Vertex" : yes, absolutely, and this is really cool I think. Thanks for that. (2) I am not entirely sure I understand you : maybe you didn't get that I *do* want to manipulate (and instantiate) "abstract vertices". Maybe I confused you by using the word "abstract" : my Abstract_Vertex was not an abstract class or whatever! They are abstract in a more mathematical sense. – Seub Jun 20 '12 at 01:45
  • @user1367124: The question still stands: Will you need to mix different `Vertex` types (i.e. store in a container both a `Vertex` and a `Vertex`?) You can write generic code that works independently of the particular type (for example, all the implementation of member methods will be generic, also the implementation of `Graph`...) but they will apply to a particular type `Vertex` for a given `T`. If you don't need to mix different vertices then not being able to do it has the advantage that the compiler will tell you if by mistake you do it. – David Rodríguez - dribeas Jun 20 '12 at 02:37
  • No you're right, I don't think I need to mix my different Vertex types, I don't need polymorphism. The template solution is great. But I'd like to know what you would do about my "abstract" vertices (which are not abstract in C++ sense, they just don't have a `Point` or a `Circle` variable or anything). Do I content myself with artificially declaring them as `Vertex` or `Vertex` and never use the int (or the bool)? That seems slightly unsatisfactory. It'd be really cool if you had an idea on how to deal with that. – Seub Jun 20 '12 at 03:22
  • @user1367124: What do you need your `AbstractVertex` for? What can be done with them? – David Rodríguez - dribeas Jun 20 '12 at 03:32
  • Do you not believe me that I need them? ;) Maybe you are making too big a deal of the word "abstract". Abstract graphs are very relevant (and not-so-abstract) in some contexts, you can do plenty of things with them. In fact, most things that I want to do with graphs and vertices in my program, I can do with abstract vertices. Simple examples include : `add_edge`, `sort_neighbors`, more complicated examples include things like : `is_the_graph_a_cycle`, `is_the_graph_planarizable`, `complete_graph_in_a_triangulation` (...) Drawing them is pretty much one of the only things you can't do... – Seub Jun 20 '12 at 03:56
0

Typically, unless you have a good reason for multiple inheritance, avoid it.

Reasons:

  1. You avoid the diamond problem.
  2. Keep the design as simple as possible - after a few levels, multiple inheritance can really be a pain to follow and maintain.
  3. It is generally easier to check that your class satisfies the SOLID principles of good design when you're not using multiple inheritance.
  4. Composition and/or aggregation usually provide as good or better approaches.
  5. It is easier to test your classes.

These should be enough to get you started. There's a lot more wisdom in the post that Jon Cage linked to.

Therefore, to answer your question: It looks like you don't have a good enough reason to use multiple inheritance. Use composition.

Carl
  • 43,122
  • 10
  • 80
  • 104