简体   繁体   中英

C++ multiple inheritance and access specifiers: inherit from a base class and its derived

This is an unexpected issue I've run into. I'm writing a GUI application, and I use two classes from the GUI library: a Model class and a View class. The Model contents are rendered by the View class to the screen. At some point I decided to derive the Model class because I needed extended functionality. The library's classes are meant to be derived and I found many examples. It was easy and workd perfectly.

Now there's a problem: the Model class offers methods for direct editing of the model data. I don't want these methods to be public because I wrote wrappers, and these wrappers have to be the only way to edit. I do need the inheritance because my derived MyModel overrides many virtual methods of the Model class. So I was thinking what to do. Here's a summary with all details:

  • There's a BasicModel class which offers methods for iteration through the model data
  • There's a Model class, which inherits BasicModel, and extends it by also offering methods for editing the data (I think BaseModel is abstract and has no data, and Model defines internal data structures and implements the iteration interface offered by BasicModel)
  • There's a MyModel class which inherits Model. It overrides many virtual methods, has extended editing mechanism and wants to hide the lower-level editing methods offered by Model
  • There's a View class which stores a pointer to a BasicModel object. Thus the View uses only the iteration interface, and doesn't even know about any editing interfaces.

So I want to keep MyModel's iteration interface public, so that I can pass it to my View object, but hide the editing interface offered by Model.

Solutions I thought of:

Solution 1: Inheritance can't be avoided because I have to iverride virtual methods, but the solution itself doesn't have to use inheritance: I can write a wrapper to the MyModel class, which offers access to a const reference to the encapsulated MyModel object and offers wrappers to MyModel's editing mechanism. It may be ugly because of all the wrappers, but it avoids messing with multiple inheritance and access specifiers.

Solution 2: Have MyModel inherit Model. No multiple inheritance. Now go to MyModel's class definition in MyModel.hpp and write method declarations of all Model's editing methods, under protected ir private , so that they're hidden. This works very well, but there's one little problem with maintenance: If in future versions of the library, the Model interface changes, eg a new editing method is added, we'll have to add it manually to the MyModel class as a private/protected method. Of course I can track the library's changes, or at least go to its online API reference and have a glance at the Model class reference page, just to make sure nothing's changes, or update my code if necessary, before a production/stable version of my application is released.

Solution 3: Use multiple inheritance, and then I have no idea what's supposed to happen. Is it safe or not. Is the behavior compiler dependent or not. This is the idea: MyModel inherits from Model and from basicModel: public inheritance from BasicModel (for the iteration interface) and protected/private inheritance from Model (in order to hide Model's editing interface).

Notes:

Note 1 : My high-level editing mechanism uses Model's lower-level editing methods.

Note 2 : The virtual methods MyModel overrides, some of them are defined by BasicModel (thus inherited by Model too) and some don't exist in BasicModel and are defined by Model (eg methods related to drag-n-drop).

Note 3: The GUI library I use is gtkmm , the C++ binding of GTK+ , and the classes I'm talking about are Gtk::TreeModel, Gtk::TreeStore, Gtk::TreeView and my own MyModel class, which derives from Gtk::TreeStore. I ignored these names because the question is a general OO planning question, but I'm mentioning the real classes here so that people who are familiar them will understand the problem much more easily.

I'm not sure what would be the best design here. Definitely solution 3 is the one with the lowest maintenance cost. It's actually zero, because the inheritance access specifiers just do all the work automatically. The question is, does solution 3 always work as expected, eg for an iteration method, will compilers make it public (because of public inheritance from BasicModel) or private (because of private inheritance from Model, which is derived from BasicModel). I never used multiple inheritance like this...

Pseudo-Code

This is more or less how the library works:

namespace GUI
{
     class BasicModel
     {
      public:
          /* iteration interface for observing the model */
          iterator get_iter();
          // but no data here, it's just an interface which calls protected virtual methods
          // which are implemented by deriving classes, e.g. Model
      protected:    
          virtual iterator get_iter_vfunc() = 0;
          virtual void handle_signal();
     };

     class Model : public BasicModel
     {
         /* inherits public iteration interface*/
         /* implements observation virtual methods from BasicModel*/
         virtual iterator get_iter_vfunc() { /*...*/ }
         /* has private data structures for the model data */
         std::vector<Row> data;
         /* has public editing interface, e.g. insert_row(), erase(), append()
         /* has protected drag-n-drop related virtual methods*/
         virtual void handle_drop();
     };
}

My code:

class MyModel : public GUI::Model
{
     /* implements virtual methods from BasicModel, e.g. default signal handlers*/
     virtual void handle_signal() { /*...*/ }
     /* implements drag-n-drop virtual methods from Model*/
     virtual void handle_drop() { *...* }
     /* inherits public iteration interface*/
     /* inherits public editing interface (***which I want to hide***)
     /* implements its own editing mechanism*/
     void high_level_edit (int param);
};

Experiment with GCC

I tried the following code, compiled with warnings off (otherwise GCC complains):

#include <iostream>

class Base
{
public:
    void func ()
    {
        std::cout << "Base::func() called" << std::endl;
        func_vfunc ();
    }
protected:
    virtual void func_vfunc ()
    {
        std::cout << "Base::func_vfunc() called" << std::endl;
    }
};

class Derived : public Base
{
protected:
    virtual void func_vfunc ()
    {
        std::cout << "Derived::func_vfunc() called" << std::endl;
    }
};

class MyClass : public Base, private Derived
{
};

int main (int argc, char* argv[])
{
    Base base;
    Derived derived;
    MyClass myclass;

    base.func ();
    derived.func ();
    myclass.func ();
    return 0;
}

For some reason GCC insists the call myclass.func() is ambigous, but one of the func() s us supposed to be private because of the private inheritance, so I don't understand why it cannot compile. Bottom line, assuming it's not a bug but just me not understanding how things work - the suggested solution of multiple inheritance is impossible. The only way to fix this problem, if I'm not mistaken, is virtual inheritance, but I can't use it because the classes I use are library classes and they don't use virtual inheritance. And even then, since I use private and public inheritance together, it may not solve the problem and still be an ambigous call.

EDIT: I tried making Derived and MyClass derive virtually from Base, and it fully solves the problem. But in my case, I can't change library classes, so it's not an option.

If I understand your problem correctly, you should probably use a mix of inheritance (MyModel derives from BaseModel) and composition (MyModel contains a private instance of Model).

Then in MyModel use your own implementation of the base virtual methods, and for the ones you don't want to reimplement just make them a proxy to the corresponding Model methods.

Anyway, IMHO you should avoid multiple inheritance if you can. It can get pretty hairy over time otherwise.


EDIT: Now that I can see the code, I'll give it another shot.

What you need to do is reimplement whatever you need in a class MyModelImpl (derives from Model) which will be hidden inside a proxy class MyModel (derives from BaseModel). My first idea was very similar except I didn't understood that you needed to reimplement some parts of Model.

Something along the lines of:

class MyModel : public BaseModel {
public:
  void high_level_edit(int param) { m_impl.high_level_edit(param); }
protected:
  virtual iterator get_iter_vfunc() { return m_impl.get_iter_vfunc(); }
  virtual void handle_signal() { m_impl.handle_signal(); }

private:
  class MyModelImpl : public Model {
  public:
    void high_level_edit(int param);
    // reimplement whatever you need (methods present in BaseModel,
    // you need to call them from MyModel proxies)
    virtual iterator get_iter_vfunc() { /*...*/ }
  protected:
    // reimplement whatever you need (methods present only in Model,
    // you don't need to call them from MyModel proxies)
    virtual void handle_drop();
  };
  MyModelImpl m_impl;
};

I believe that should work correctly since there is no actual state (data) in BaseModel, unless there is something I misunderstood again ...

You can do something like

class MyModel : protected Model

{

public:
    using Model::iterator;
    //it will make iterator public in MyModel
}

if I understand the case correctly, and provided Model doesn't inherit privately from iterable class, which must be the case here, I guess. Pardon me if I said something stupid. I'm not a very experienced coder.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM