c++inheritanceobject-design

Force a derived class without implementing pure virtuals again


I have defined an interface class A which defines some basic functions. In my implementation I have a base class A0 which implements this interface and from this base class I have derived several other classes in a hierarchy.

#include <iostream>
#include <string>

class IContainer
{
public:
    IContainer() {};
    virtual ~IContainer() {};

    virtual void loadDefaults() = 0;
    virtual void storeDefaults() = 0;

    virtual bool open() = 0;
    virtual bool close() = 0;
};

class IContainerReader
{
public:
    IContainerReader() {};
    virtual ~IContainerReader() {};

    virtual bool read() = 0;
};

class IContainerWriter
{
public:
    IContainerWriter() {};
    virtual ~IContainerWriter() {};

    virtual bool write() = 0;
};

class ContainerBase : public IContainer
{
public:
    ContainerBase() {}
    virtual ~ContainerBase() {}

    void loadDefaults() {}
    void storeDefaults() {}
};

class CSVBase : public ContainerBase
{
public:
    CSVBase() {}
    virtual ~CSVBase() {}

    void setFilename() {}
    bool open() { return true; }
    bool close() { return true; }
};


class CSVReader : public CSVBase, public IContainerReader
{
public:
    CSVReader() {}
    virtual ~CSVReader() {}

    bool read() { return true; }
};

class CSVWriter : public CSVBase, public IContainerWriter
{
public:
    CSVWriter() {}
    virtual ~CSVWriter() {}

    bool write() { return true; }
};

int main(int argc, char *argv[])
{
    CSVReader r;
    CSVWriter w;
    IContainerReader *ir = &r;
    IContainerWriter *iw = &w;

    ir->open();
    iw->open();

    ir->read();
    iw->write();

    ir->close();
    iw->close();

    return 0;
}

As you can see I defined a IContainerReader and a IContainerWriter class which defines special functions only relevant to the respective implementation.

But now I have a problem, because I want to be sure that a Reader or a writer always has the container base as well. So the logical solution would be to derive IContainerReader/-Writer from IContainer. But when I do this, thge compiler complains, because it expects that now a Reader/Writer object implements again the base functions as well, which are already defined via the base class. But if I let IContainerReader not derive from IContainer a pointer to one of those objects is not guruanteed to have the IContainer functionality as well.

If I try to compile it like this I get errors, because IContainerReader is not a IContainer

||=== Build: Debug in CPPMingW (compiler: GNU GCC Compiler) ===|
D:\src\c\Tests\CPPMingW\main.cpp||In function 'int main(int, char**)':|
D:\src\c\Tests\CPPMingW\main.cpp|83|error: 'class IContainerReader' has no member named 'open'|
D:\src\c\Tests\CPPMingW\main.cpp|84|error: 'class IContainerWriter' has no member named 'open'|
D:\src\c\Tests\CPPMingW\main.cpp|89|error: 'class IContainerReader' has no member named 'close'|
D:\src\c\Tests\CPPMingW\main.cpp|90|error: 'class IContainerWriter' has no member named 'close'|
||=== Build failed: 4 error(s), 0 warning(s) (0 minute(s), 5 second(s)) ===|

However, if I derive IContainerReader from IContainer as it should be, I get the following errors:

||=== Build: Debug in CPPMingW (compiler: GNU GCC Compiler) ===|
D:\src\c\Tests\CPPMingW\main.cpp||In function 'int main(int, char**)':|
D:\src\c\Tests\CPPMingW\main.cpp|78|error: cannot declare variable 'r' to be of abstract type 'CSVReader'|
D:\src\c\Tests\CPPMingW\main.cpp|58|note:   because the following virtual functions are pure within 'CSVReader':|
D:\src\c\Tests\CPPMingW\main.cpp|11|note:     virtual void IContainer::loadDefaults()|
D:\src\c\Tests\CPPMingW\main.cpp|12|note:     virtual void IContainer::storeDefaults()|
D:\src\c\Tests\CPPMingW\main.cpp|14|note:     virtual bool IContainer::open()|
D:\src\c\Tests\CPPMingW\main.cpp|15|note:     virtual bool IContainer::close()|
D:\src\c\Tests\CPPMingW\main.cpp|79|error: cannot declare variable 'w' to be of abstract type 'CSVWriter'|
D:\src\c\Tests\CPPMingW\main.cpp|67|note:   because the following virtual functions are pure within 'CSVWriter':|
D:\src\c\Tests\CPPMingW\main.cpp|11|note:     virtual void IContainer::loadDefaults()|
D:\src\c\Tests\CPPMingW\main.cpp|12|note:     virtual void IContainer::storeDefaults()|
D:\src\c\Tests\CPPMingW\main.cpp|14|note:     virtual bool IContainer::open()|
D:\src\c\Tests\CPPMingW\main.cpp|15|note:     virtual bool IContainer::close()|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 6 second(s)) ===|

So the compiler expects me to reiplment all the functions from the base classes in the derived class as well.

So is there a solution to this, without having to define all these functions in the Reader/Writer class again? Of course I could implement dummies which just go down to the base class, but I consider this a bit clumsy and unneccessary overhead and I hope that there may be a better solution for this.


Solution

  • I hope I got it right. You have a kind of diamond problem. You inherit from the IContainer via two paths .

    Normally in such cases you would have two instances of IContainer created per one instance of CSVReader, and the calls to IContainer methods would be ambiguous. In your case the path through IContainerReader does not have defined the mentioned functions. Virtual inheritance makes that only one instance is created.

    Inheritance from IContainer should be declared as virtual. Virtual inheritance makes that every derivation from a class "will get combined together" (sorry for not very technical terms but this is how I understand it in simple english). In your case, only one copy of IContainer for both paths will be created, and the vtables will get filled from both paths.

    This code compiles:

    #include <iostream>
    #include <string>
    
    class IContainer
    {
    public:
        IContainer() {};
        virtual ~IContainer() {};
    
        virtual void loadDefaults() = 0;
        virtual void storeDefaults() = 0;
    
        virtual bool open() = 0;
        virtual bool close() = 0;
    };
    
    class IContainerReader : virtual public IContainer
    {
    public:
        IContainerReader() {};
        virtual ~IContainerReader() {};
    
        virtual bool read() = 0;
    };
    
    class IContainerWriter : virtual public IContainer
    {
    public:
        IContainerWriter() {};
        virtual ~IContainerWriter() {};
    
        virtual bool write() = 0;
    };
    
    class ContainerBase : virtual public IContainer
    {
    public:
        ContainerBase() {}
        virtual ~ContainerBase() {}
    
        void loadDefaults() {}
        void storeDefaults() {}
    };
    
    class CSVBase : public ContainerBase
    {
    public:
        CSVBase() {}
        virtual ~CSVBase() {}
    
        void setFilename() {}
        bool open() { return true; }
        bool close() { return true; }
    };
    
    
    class CSVReader : public CSVBase, public IContainerReader
    {
    public:
        CSVReader() {}
        virtual ~CSVReader() {}
    
        bool read() { return true; }
    };
    
    class CSVWriter : public CSVBase, public IContainerWriter
    {
    public:
        CSVWriter() {}
        virtual ~CSVWriter() {}
    
        bool write() { return true; }
    };
    
    int main(int argc, char *argv[])
    {
        CSVReader r;
        CSVWriter w;
        IContainerReader *ir = &r;
        IContainerWriter *iw = &w;
    
        ir->open();
        iw->open();
    
        ir->read();
        iw->write();
    
        ir->close();
        iw->close();
    
        return 0;
    }