Author Topic: Implementing Interfaces using Multiple Inheritance in C++  (Read 3112 times)

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Implementing Interfaces using Multiple Inheritance in C++
« on: May 06, 2018, 04:58:59 PM »
So I came across a problem when working on the OP2Utility code. I am trying to add a Serializer interface to StreamReader/StreamWriter. Unfortunately I'm getting the error code C2385 ambiguous access of 'Serialize'.

The gist of the code is:
This is what the StreamReader series looks like. StreamReader and SeekableStreamReader are meant to be what I would call an abstract class in C#. They cannot exist of their own accord, but are not an interface as they will contain a little bit of data.

Code: [Select]
class StreamReader : public Serializer {
public:
virtual ~StreamReader() = 0;
        // TODO: Include a get function to indicate that it is a StreamReader as opposed to a StreamWriter
};

class SeekableStreamReader : public SeekableSerializer, StreamReader {
};

class FileStreamReader : public SeekableStreamReader {
public:
FileStreamReader(std::string fileName);
~FileStreamReader();
void Serialize(char* buffer, size_t size);
// Change position forward or backword in buffer.
void SeekRelative(int offset);

private:
std::ifstream file;
};

Below are the two Serializer interfaces.
Code: [Select]
class Serializer {
public:
virtual void Serialize(char* buffer, size_t size) = 0;
};

class SeekableSerializer : public Serializer {
public:
virtual void SeekRelative(int offset) = 0;
};

Anyways, I feel like this would have worked in C#, so something is breaking down in my knowledge of C++. It doesn't seem to like the fact that SeekableStreamReader inherits the function Serialize from both StreamReader and SeekableSerializer. I'm not sure how to set it up otherwise though.

Any help would be appreciated,
Brett

Offline lordpalandus

  • Banned
  • Hero Member
  • *****
  • Posts: 825
Re: Implementing Interfaces using Multiple Inheritance in C++
« Reply #1 on: May 06, 2018, 09:09:09 PM »
Perhaps inheritance isn't the best option. Perhaps class composition would work better. After all, inheritance is not the best option in every case involving classes.
Currently working on Cataclysm of Chaos, Remade.
Link to OPU page = http://forum.outpost2.net/index.php/topic,6073.0.html

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2350
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: Implementing Interfaces using Multiple Inheritance in C++
« Reply #2 on: May 06, 2018, 10:51:43 PM »
You failed to define Serialize() in SeekableSerializerSerializer::Serialize() is a pure virtual function and any classes that derive from it must define it. E.g., your code should look like this:

Code: [Select]
class Serializer {
public:
virtual void Serialize(char* buffer, size_t size) = 0;
};

class SeekableSerializer : public Serializer {
public:

/**
* Pure virtual function, this class cannot be
* instantiated on its own because it's abstract.
*/
virtual void SeekRelative(int offset) = 0;

/**
* Makes this function unambiguous though the above function
* is pure virtual so this is still an abstract class.
*/
virtual void Serialize(char* buffer, size_t size) final
{}
};

As noted above, though, SeekableSerializer is still an abstract class because the function SeekRelative is pure virtual. Though this may be moot as you do define it in FileStreamReader (I think?).

Additionally, SeekableStreamReader and FileStreamReader do not declare virtual destructors. If you're inheriting, your destructors must be virtual in order to be called correctly when a derived object is destroyed.

Finally, StreamReader's destructor is pure virtual. While this is legal C++, you still need to define it. You can do this in the .cpp file simply as this:

Quote
StreamReader::~StreamReader() {}

Doesn't need to be any more fancy than that.

Side note about inheriting virtual functions -- even in the derived class you should declare it virtual. This is more of a documentation type of deal than anything else but if you don't want the implementation to be overridden in a further derived class you can define the function final:

Code: [Select]
class FileStreamReader : public SeekableStreamReader {
public:
FileStreamReader(std::string fileName);
virtual ~FileStreamReader();

virtual void Serialize(char* buffer, size_t size) final;
/** Change position forward or backword in buffer. */
virtual void SeekRelative(int offset) final;

private:
std::ifstream file;
};

Perhaps inheritance isn't the best option. Perhaps class composition would work better. After all, inheritance is not the best option in every case involving classes.

No, in this case inheritance is the correct approach (and actually in this case multiple inheritance makes sense). The intent here is to inherit an interface, not an implementation.

The parent classes provide interface functions but no implementations. Implementations are provided by the derived classes by defining pure virtual functions.
« Last Edit: May 06, 2018, 10:59:37 PM by leeor_net »

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Re: Implementing Interfaces using Multiple Inheritance in C++
« Reply #3 on: May 07, 2018, 04:46:40 AM »
lordpalandus,

Thanks for the input, although I'm pretty certain that inheritance is the correct pattern here. I need to figure out what you mean by composition at some point as I've never really heard the term before.

Leeor,

With your tips I have the code compiling and running properly, so thank you. Inheritance in C++ is a tricky beast after enjoying C#'s streamlined version. I need to play a little more with the destructor chaining that you pointed out, especially through the interfaces and base classes.

StreamReader's destructor is defined in StreamReader.cpp, I should have posted the .cpp snippet here to make the example more complete.

I didn't know about the final keyword, so thanks for pointing it out. It appears to be similar to C#'s sealed keyword so I'm pretty familiar with the concept (I think). I like that it explicitly closes the ability to override the function's definition. I think this may also allow some minor optimization by the compiler since it will not have to check the virtual function table in some circumstances when final is used.

Random thoughts that can be ignored below
There remains some argument over when to use the sealed keyword in C# and I was reading the opinions of different programmers at Microsoft a few weeks ago. Some people prefer the flexibility of being able to inherit and override from any method/class. Rarely sealing anything allows quicker and easier coding than routinely sealing a lot of methods/classes.

Others prefer sealing whenever you are not certain that you want to fully support the effects of a specific override. Their argument is allowing a function/class to be overridden makes proper testing more difficult, introduces possible security concerns, and slightly reduces efficiency. They also argue it would be a breaking change to seal a function/class in a later revision to a library if a security concern was discovered, while unsealing a class later would not be a breaking change. I think I fall more in this camp than the previous one. This camp would have preferred in C# that all methods and classes were considered sealed by the compiler unless specifically marked otherwise, although I guess they lost the argument back in the day.

Thanks,
Brett

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2350
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: Implementing Interfaces using Multiple Inheritance in C++
« Reply #4 on: May 07, 2018, 09:53:21 AM »
lordpalandus,

Thanks for the input, although I'm pretty certain that inheritance is the correct pattern here. I need to figure out what you mean by composition at some point as I've never really heard the term before.

I mentioned it in another thread but composition is basically building an object using other objects. E.g., Inheritence means Class B IS A Class A versus Composition where Class C is MADE UP of Class A and Class B. Simply put, composition basically just means you're using classes as member variables of another class.

Leeor,

With your tips I have the code compiling and running properly, so thank you. Inheritance in C++ is a tricky beast after enjoying C#'s streamlined version. I need to play a little more with the destructor chaining that you pointed out, especially through the interfaces and base classes.

Tricky is an understatement. As Bjorne has stated about C++, it's harder to shoot yourself in the foot but when you do you take your whole leg off at the same time. There are definitely quirks that take some getting used to especially if you're coming from C# and Java.

The final keyword is mostly for documentation. I've never tried to override it in a derived class so I don't know if a compiler will barf on it, warn against it or ignore it and the derived class will simply hide the inherited implementation. As Arklon has said, C++'s standards committee seems to prefer insanity.

But I love the language and what I can do with it. Generic programming via Templates is the thing I love the most about it!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Re: Implementing Interfaces using Multiple Inheritance in C++
« Reply #5 on: May 09, 2018, 04:20:03 AM »
The problem seems to be you're inheriting from multiple classes which each define a Serialize method. You may need to scope things to avoid ambiguity. Though really, this could be an issue with the class hierarchy design, and complications of multiple inheritance.

Quote
Others prefer sealing whenever you are not certain that you want to fully support the effects of a specific override. Their argument is allowing a function/class to be overridden makes proper testing more difficult, introduces possible security concerns, and slightly reduces efficiency. They also argue it would be a breaking change to seal a function/class in a later revision to a library if a security concern was discovered, while unsealing a class later would not be a breaking change. I think I fall more in this camp than the previous one. This camp would have preferred in C# that all methods and classes were considered sealed by the compiler unless specifically marked otherwise, although I guess they lost the argument back in the day.

Well, I love efficiency gains, and I love being able to maintain backwards and binary compatibility. Though the problem with this argument, is what happens with library code. The person who wants to create a child class with an overridden member is often not the library maintainer. Having to make local changes to a public library is not a pleasant way to program. Plus, for a library author, any child classes won't have been written yet, and will likely be written by other people, so it can be hard to know what will need to be overridden in advance.


This discussion has reminded me of D's Mixins. They provide a way to implement functionality generically for a family of classes. It's similar to copying code by inheriting from a class, except the parent doesn't become a part of the class hierarchy. It's as if you re-implemented or copy/pasted the exact same code in all the child classes. This allows you to implement orthogonal features, without messy multiple inheritance. This would actually be a reasonable way to implement the StreamReader code. The stream direction, and stream seekability are orthogonal concerns. StreamReader and StreamWriter could be mixins which each define a const final function for the stream direction. Though how you express that in C++ I'm not exactly certain. The most obvious way is to use multiple inheritance. This might be where virtual inheritance comes in handy (which might solve your error), or perhaps macros.