4

Structure

I have created a diamond inheritance problem. It looks like this

inheritance diagram

I thought I understood virtual inheritance fairly well, however I now think that I have slightly missunderstood it.

  • It was my understanding that virtual inheritance tells the compiler to ignore any member data or functions which appear twice with the same name as a result of a diamond inheritance pattern, thus only the "non virtual" inherited components would be contained in the derived class.

  • However I now think this understanding of how the compiler implements inheritance is wrong.

I have 2 diamond inheritance patterns in my inheritance hierarchy. They are marked using the notes included.

I have also added some notes to show where I attempted to put virtual to resolve the compiler errors, but a different compiler error resulted. The note briefly describes what the problem was. (See the final section of this question if you are interested)

Useage

The intended usage is a std::list<GUIObject*> is created. All gui objects should be able to Draw and ProcessEvent. Not all gui objects will contain the container contained inside SingleLineBuffer.

Buffer and FileBuffer inherit from SingleLineBuffer to change how the container inside SingleLineBuffer behaves. (FileBuffer actually only added new file IO functions.)

One could create an instance of one of the buffers, however I don't in the context I am working with.

One cannot create an instance of any of the abstract GUI* classes. Thinking about it, there should probably be an additional abstract base class below GUIMultilineTextEntry which inherits from FileBuffer.

The actual objects that the user may create an instance of are Label, Inputbox and Textbox. I intend to add more in the future, such as a multiline label. This would have to inherit from a base class which inherited from Buffer and GUITextObject, probably.

This inheritance structure quickly became quite complicated. I wrote it as I went along, guided by what my code was instructing me to do. For example, I wrote a Textbox class, then said "the container in Textbox is essentially the same as the container in Label, therefore they should inherit from a common object". The difference was that a Textbox has file IO, an additional inheritance step is dictated, and a Textbox can contain the new line character in the container, so an additional inheritance step is dictated here too.

Questions

  • Can this inheritance problem be resolved?

  • Which classes should inherit virtually from which other classes.

My attempts

  • No virtual inheritance

Compiler error: (multiple versions of)

error: request for member ‘Size’ is ambiguous
 status_text << "Save: " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->GetFilename() << ", " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->Size() << " bytes";

Size is defined in SingleLineBuffer. It is a non-virtual function, as the container exists only in SingleLineBuffer, and therefore Size is written to work correctly for both Buffer and FileBuffer.

  • Break diamond 1: Put virtual between GUITextObject and GUITextEntry to stop Size being "pulled down via GUITextObject" before it is overridded in Buffer. (Blue mark)

Compiler error (1):

error: no matching function for call to ‘GUITextObject::GUITextObject()’

I can fix this by calling the required constructor from GUIMultilineTextEntry. I don't understand why this is needed. (Second Question) Fix shown below:

GUIMultilineTextEntry(const FontTextureManager& ftm, const int width, const int height)
    : GUITextEntry(ftm, width, height)
    , GUITextObject(ftm, width, height) // also call the constructor for the class which was inherited virtual in the previous step
{ ...

This same fix is needed in Inputbox and Textbox.

However this results in a further error

error: cannot convert from pointer to base class ‘GUIObject’ to pointer to derived class ‘Textbox’ via virtual base ‘GUITextObject’
 static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->SetFilename(filename);

I believe I could resolve this by using dynamic_cast instead of static_cast, however I am not sure this is a good idea as I have heard that dynamic casting can slow down code significantly.

  • Break diamond 1, second attempt:

I made a second attempt at resolving the problem by inheriting virtually between Buffer and SingleLineBuffer. (See red dot) However when I did this the compiler error changed to

error: no unique final overrider for ‘virtual void SingleLineBuffer::SetText(const string&)’ in ‘Textbox’

My guess is this is equivalent to the compiler telling me "you overrided some functions in Buffer by inheriting, but you inherited virtually, and the functions you have overridden are also present in a derived class via non-virtual inheritance, so I don't know which one should take precidence" - but this really is a guess.

  • I tried similar things to break diamond 2 but encountered similar compiler errors.

Since this is now quite a long question, I will not list all the details of that attempt here.

Community
  • 1
  • 1
FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • Will anyone ever want to use a `GUITextObject` as their `SingleLineBuffer`? – Galik Jun 02 '18 at 10:41
  • @Galik No. The functions Draw and ProcessEvent will be pure virtual in GUITextObject – FreelanceConsultant Jun 02 '18 at 10:44
  • But will they use its `SingleLineBuffer` methods? – Galik Jun 02 '18 at 10:45
  • @Galik - No one should ever use SingleLineBuffer either. One might want to use FileBuffer for "tabs" in a "Textbox" but other than that none of the buffers should really be used directly – FreelanceConsultant Jun 02 '18 at 10:45
  • @Galik The classes Label, Textbox and Inputbox will use the methods in SingleLineBuffer – FreelanceConsultant Jun 02 '18 at 10:45
  • And Textbox will use an overridden method of SingleLineBuffer, which is overridded in Buffer – FreelanceConsultant Jun 02 '18 at 10:46
  • Will the *user* of class `Label` want to call the methods it inherits from `SingleLineBuffer`? – Galik Jun 02 '18 at 10:47
  • 2
    *"virtual inheritance tells the compiler to ignore any member data or functions which appear twice with the same name"* - Actually specifically **one** of the multiple versions is kept, so it will be present. – Galik Jun 02 '18 at 10:50
  • @Galik, yes, the user will want to call the functions: `Insert`, `Delete`, `Size`, `GetText`, `SetText`, etc – FreelanceConsultant Jun 02 '18 at 10:52
  • It looks like you are using multiple inheritance to provide both *interfaces* and then, later down the inheritance chain, *implementations* of those same *interfaces*. – Galik Jun 02 '18 at 10:58
  • I am not entirely sure why *virtual inheritance* is not working for you, but then I never build my inheritance hierarchies this complex. My feeling is that `GUITextObject` is the only *textual gui component* that should *inherit* from `SingleLineBuffer` and that `SingleLineBuffer` should be a **pure virtual interface**. – Galik Jun 02 '18 at 11:07
  • Then perhaps `FileBuffer` should **not** inherit from `Buffer` but be a separate implementation type. After that I would probably use **composition** so that *textual gui objects* would contain the relevant *buffer derivative* as its means to implement the `SingleLineBuffer` part of its interface. – Galik Jun 02 '18 at 11:07
  • I mean I don't consider myself a total expert in this but you do seem to be overusing multiple inheritance and maybe using inheritance where *composition* would be more flexible and appropriate. – Galik Jun 02 '18 at 11:08
  • 1
    Composition over inheritance. Inheritance means "is". This seems false to me: *TextBox is SingleLinebuffer*. Re-design time. Or at least rename classes so the names make no false claims. – hyde Jun 02 '18 at 11:20
  • @Galik The issue with that would be that the functions of the buffer types would not automatically be exposed to the user. One could not call "Label::SetText" for example – FreelanceConsultant Jun 02 '18 at 11:20
  • I think @hyde has a point. But anything that inherits from `SingleLineBuffer` will be forced to provide an implementation at some point. You can then decide if you are going to use `Buffer` or `FileBuffer` as data members to fulfil that implementation (or maybe switch between the two at runtime???). – Galik Jun 02 '18 at 11:26
  • BTW this is probably not the right place to deal with this kind of question due to it being rather **broad** and to some degree opinion based. It is difficult to provide a definitive answer without a lot of discussion. – Galik Jun 02 '18 at 11:29
  • There are no hard and fast rules on how to go about OO design, imo most of it is just experience and studying other people's designs that work. As a **guide** I think it is instructive to completely separate the design of the interfaces from the design on the implementations. Do them as two distinct phases. – Galik Jun 02 '18 at 11:33
  • What do you mean by “cannot be virtual” in your diagram? Not that I see a reason for anything to inherit virtually from any of the `GUI` classes here. – Davis Herring Jun 02 '18 at 14:43
  • @DavisHerring These things are explained by the short notes themselves and more detail is given in the question. – FreelanceConsultant Jun 05 '18 at 09:52
  • @user3728501: Perhaps you meant "I tried making this `virtual`", then? I took them for constraints, which added to the considerable confusion here. – Davis Herring Jun 05 '18 at 16:55
  • Please post a [mcve]. That is, *one* block of C++ code as compiled, and a list of compiler messages with line numbers that reference sald block of code. – n. m. could be an AI Jun 05 '18 at 17:26

1 Answers1

0

The answer to the titular question is no. (Normally the MCVE comes in the question, but I guess here it really is an answer.) As for the detailed questions:

  • An inheritance needs to be virtual if it is directly from the common ancestor (the "top of the diamond") of which you want only one copy in the complete object. (So here, you need 4 virtuals, just by counting converging arrows.)
  • You need to call the constructor of every virtual base in every concrete class, because the most-derived class initializes them directly.
  • You really can't use static_cast from (or via, as it says) a virtual base, since the class layout varies between instances (because of differing other base classes). However, the cost of one dynamic_cast per GUI operation is surely immeasurable.
  • Your "unique final overrider" error analysis is probably right, but the answer is more cowbellvirtual.
Davis Herring
  • 36,443
  • 4
  • 48
  • 76