2

I am trying to create an inheritance hierarchy in C++ using the following simple model: courses, modules, and lessons, in which courses consist of zero or more modules and modules consist of zero or more lessons. Each of these classes also contains other information:

class course
{
private:
    const std:string & name_;
    const std::duration duration_;
    const difficulty difficulty_;
    ...
};

class module
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

class lesson
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

My questions:

  • Would it be correct to have a class that all three of these inherit from, which contains their common properties?

Personally, I think it would be more correct for them to inherit from Vector in the following fashion:

class course : public std::vector<module>
{
private:
    const std:string & name_;
    const std::duration duration_;
    const difficulty difficulty_;
    ...
};

class module : public std::vector<lesson>
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

class lesson
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

since courses are a collection of modules, and modules are a collection of lessons with some additional properties, and inheriting from vector would provide all of the standard functionality needed to add, remove, etc. modules and lessons.

  • Why not both? The way I learned inheritance at school, using Java, I was taught that multiple-inheritance is evil and to stay way from it for proper OO inheritance using the Liskov Substitution Principle, etc. There are a lot of people who don't believe in it and a lot of others who swear by it.

The other possible way would be to have:

class course : public namedentity
{
private:
    std::vector<module> modules_;
    ...
    const difficulty difficulty_;
    ...
};

class module : public namedentity
{
private:
    std::vector<lesson> lesons_;
    ...
};

class lesson : public namedentity
{
private:
    ...
};

Where namedentity contains all of the common properties, and these private vectors contain their children.

I guess what it boils down to is, are courses really vector<module>, and modules really vector<lesson>? Does this violate LSP or other OO principles? Would it be better served by having their children as a property?

Christophe
  • 68,716
  • 7
  • 72
  • 138
Francisco Aguilera
  • 3,099
  • 6
  • 31
  • 57
  • 5
    Don't inherit from classes in the standard library. _Is a_ module a list of courses or does it _have a_ list of courses? – Captain Obvlious Mar 12 '15 at 00:08
  • 1
    Why are you using const std::string references? :-/ That suggests that the names don't belong to these objects, but have their lifetime governed elsewhere. While this *might* be sensible if you have all your strings in some table whose lifetime is long enough that these don't go bad...*usually* you would think that the classes would contain string instances so the name has the same lifetime as the class. – HostileFork says dont trust SE Mar 12 '15 at 00:08
  • 2
    I don't see why you'd want to inherit from `vector` instead of having a `vector` member, for one thing the standard library containers aren't really designed to be used as base classes and don't have virtual destructors. Use inheritance if it makes sense logically, I don't think it's a good idea if you're using inheritance just to factor out some common members. – user657267 Mar 12 '15 at 00:09
  • @HostileFork I am using `const std::string &` because I am a complete scrub, and actually was wondering this exact same thing earlier, thanks for clearing that up, I will switch them. – Francisco Aguilera Mar 12 '15 at 00:09
  • What @CaptainObvlious said. Option 3. – keyser Mar 12 '15 at 00:11
  • Also, unrelated, but @HostileFork, can you have a constructor take a `const std::string &` parameter and initialize a `const std::string` (non-reference) using the referenced parameter? Is that legal cpp? – Francisco Aguilera Mar 12 '15 at 00:11
  • So containment with a common base class would be the best way to go. I was afraid of having to write a large wrapper class for all of the vector functionality I would want to have... :/ – Francisco Aguilera Mar 12 '15 at 00:14
  • 1
    @FranciscoAguilera Why do they need a common base class? – user657267 Mar 12 '15 at 00:15
  • @FranciscoAguilera Yes, you can initialize a const string member from a const reference. But if it's private the const can be sort of "noise". There are some real concrete benefits to passing parameters by const reference (random example: shared pointers get a huge benefit because copies are expensive) but making private class members const is sort of like marking int or bool parameters const to a function. It's probably not worth the extra typing. – HostileFork says dont trust SE Mar 12 '15 at 00:17
  • @user657267 because these entities all have common data - code reuse. – Francisco Aguilera Mar 12 '15 at 00:19
  • @Dan, i'm not sure I understand what you mean by "acts on a namedentity" – Francisco Aguilera Mar 12 '15 at 00:21
  • @Dan, ok, I see. I do not have any code that acts on named entities by themselves, so therefore I shouldn't even have that class? – Francisco Aguilera Mar 12 '15 at 00:22
  • Is a course a vector of modules? i.e. The modules are taken sequentially. A student could do several modules at the same time, but only be in one lesson at a time. So the durations have a different meaning. – QuentinUK Mar 12 '15 at 00:25

2 Answers2

3

It's not advisable to inherit publicly from a vector, because a course, a lesson, or a module are not vectors.

It just happens that you use vectors to implement them, because a module can have several courses. If tommorow you'd use a set, a map, or a linked list, or a database mapper to implement your objects, your'd have to rewrite your whole code if you base yourself on vector inheritance. And don't forget that , the standard vector emplace(), push_back(), erase(), ... would need to be overloaded, because all these operations on courses in a module would need to adapt the course duration.

By the way, a good rule of the thumb for class design is to use inheritance for "is-a" relationships and composition for "has-a".

More arguments for and against vector inheritance are in this SO question.

The second alternative looks much better, because a course, a lesson, a module really is a named entity. The relationship between the objects is also much clearer with this approach

Both approaches could be compatible with the Liskov Subsitution Principle:

  • if you write code for a namedentity, the same code could be used replacing the named entity with any of these subclasses (provided that they all implement constructors that can can be used with the same arguments than the base class). This makes sense whatever evolution you make on your datastructure in the future.
  • if you write code for handling a vector<module>, the same code could work with a course instead. However, there are chances that you'd use a different semantics in the constructors. LSP will backfire if you change your implementation.
Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
2

As a general rule, it's a bad idea to inherit from a standard library container. One compelling reason is that in use cases like the one you describe, it is hard not to provide the extra functionality offered by the derived class without requiring some constraints on the operations performed on the container, which breaks substitutability.

Instead, if you think the API of your class should expose container-operations on the collection of children, it's both cleaner and easier to provide a method that gives a reference to the container, stored as a member of the class. Better still would be to only expose the functionality of the enclosed container that you need, so that you have a place to enforce any invariants your class requires now, or might in the near future.

Regarding inheriting common properties: generally this is a good idea, but there are two motivations for doing so, namely code reuse and abstraction, and they don't necessarily always align.

Inheriting from an purely abstract class is more or less equivalent to implementing an interface in Java, and performance considerations aside, there's little reason to avoid multiple inheritance from such classes providing a little care is taken. On the other hand, the use of inheritance as a mechanism for code reuse allows you to use what is essentially a composition without all the forwarding boiler-plate code.

To be more concrete:

  • I'd advise against inheriting from std::vector. Provide methods that expose the required manipulations on the contained vector instead. This allows you both the option of changing your container implementation, and imposing any class invariants you may need.

  • If any other part of the code only cares about the namedness of these objects, then inheriting from a namedentity is good design.

  • If all these classes need the functionality of a non-purely abstract namedentity, then this is a different but also valid reason to inherit from it.

halfflat
  • 1,584
  • 8
  • 11