C++ Inheritance Diamond Problem: Preventing Duplicate Function Calls

116 views Asked by At
#include <iostream>

class Base {
public:
    virtual void printInfo() {
        std::cout << "Base class info" << std::endl;
    }
};

class Class1 : virtual public Base {
public:
    void printInfo() override {
        Base::printInfo();
        std::cout << "Class1 info" << std::endl;
    }
};

class Class2 : virtual public Base {
public:
    void printInfo() override {
        Base::printInfo();
        std::cout << "Class2 info" << std::endl;
    }
};

class Class3 : public Class1, public Class2 {
public:
    void printInfo() override {
        Class1::printInfo();
        Class2::printInfo();
        std::cout << "Class3 info" << std::endl;
    }
};

int main() {
    Class3 obj;
    obj.printInfo();

    return 0;
}

Here is the general idea of what my code looks like, hierarchy untouched, code simplified. I want Class 3 printInfo function to output

Base class info
Class1 info
Class2 info
Class3 info

But I obviously get

Base class info
Class1 info
Base class info
Class2 info
Class3 info

What can you do about it?

Of course, you could just write it simply, without calling to other printInfo functions from parents and grandparents, it does work, but that makes maintaining and scaling hard.

Is there any way to automate this in C++ without using additional libraries?

3

There are 3 answers

2
Ted Lyngmo On

You explicitly call Class1::printInfo(); and Class2::printInfo(); and those functions explictly call Base::printInfo(); so they are doing just want you've ordered them to do. You could add a bool to the printInfo function to decide if the base(s) should be called.

Example:

class Base {
public:
    virtual ~Base() = default;
    virtual void printInfo(bool = {}) {
        std::cout << "Base class info" << std::endl;
    }
};

class Class1 : virtual public Base {
public:
    void printInfo(bool callBase = true) override {
        if(callBase) Base::printInfo();
        std::cout << "Class1 info" << std::endl;
    }
};

class Class2 :  virtual public Base {
public:
    void printInfo(bool callBase = true) override {
        if(callBase) Base::printInfo();
        std::cout << "Class2 info" << std::endl;
    }
};

class Class3 :  public Class1,  public Class2 {
public:
    void printInfo(bool callBases = true) override {
        if(callBases) {
            Class1::printInfo();
            Class2::printInfo(false);
        }
        std::cout << "Class3 info" << std::endl;
    }
};

Demo


Another option is to keep a printed state in each class, but then you'll have to reset that manually after printing.

Example:

class Base {
public:
    virtual ~Base() = default;
    virtual void printInfo() {
        if(printed) return;
        printed = true;        
        std::cout << "Base class info" << std::endl;
    }
    virtual void resetPrinted() {
        printed = false;
    }
private:
    bool printed = false;
};

class Class1 : virtual public Base {
public:
    void printInfo() override {
        if(printed) return;
        printed = true;
        Base::printInfo();
        std::cout << "Class1 info" << std::endl;
    }
    void resetPrinted() override {
        if(!printed) return;
        printed = false;
        Base::resetPrinted();        
    }
private:
    bool printed = false;    
};

class Class2 :  virtual public Base {
public:
    void printInfo() override {
        if(printed) return;
        printed = true;
        Base::printInfo();
        std::cout << "Class2 info" << std::endl;
    }
    void resetPrinted() override {
        if(!printed) return;
        printed = false;
        Base::resetPrinted();        
    }    
private:
    bool printed = false;    
};

class Class3 :  public Class1,  public Class2 {
public:
    void printInfo() override {
        if(printed) return;
        printed = true;
        Class1::printInfo();
        Class2::printInfo();
        std::cout << "Class3 info" << std::endl;
    }
    void resetPrinted() override {
        if(!printed) return;
        printed = false;
        Class1::resetPrinted();
        Class2::resetPrinted();
    }    
private:
    bool printed = false;
};

Demo

0
RandomBits On

Here are a couple of options, neither great. The first uses a flag in the Base class the other passes a flag down the call hierarchy. I don't think there is a compile-time strategy to accomplish this goal.

#include <iostream>

class Base {
public:
    virtual void printInfo() {
        if (not printed_flag)
            std::cout << "Base class info" << std::endl;
        printed_flag = true;
    }
    bool printed_flag{false};
};

class Class1 : virtual public Base {
public:
    void printInfo() override {
        Base::printInfo();
        std::cout << "Class1 info" << std::endl;
    }
};

class Class2 : virtual public Base {
public:
    void printInfo() override {
        Base::printInfo();
        std::cout << "Class2 info" << std::endl;
    }
};

class Class3 : public Class1, public Class2 {
public:
    void printInfo() override {
        printed_flag = false;
        Class1::printInfo();
        Class2::printInfo();
        std::cout << "Class3 info" << std::endl;
    }
};
#include <iostream>

class Base {
public:
    virtual bool printInfo(bool printed_flag = false) {
        if (not printed_flag)
            std::cout << "Base class info" << std::endl;
        return true;
    }
};

class Class1 : virtual public Base {
public:
    bool printInfo(bool printed_flag = false) override {
        printed_flag = Base::printInfo(printed_flag);
        std::cout << "Class1 info" << std::endl;
        return printed_flag;
    }
};

class Class2 : virtual public Base {
public:
    bool printInfo(bool printed_flag = false) override {
        printed_flag = Base::printInfo(printed_flag);
        std::cout << "Class2 info" << std::endl;
        return printed_flag;
    }
};

class Class3 : public Class1, public Class2 {
public:
    bool printInfo(bool printed_flag = false) override {
        printed_flag = Class1::printInfo(printed_flag);
        printed_flag = Class2::printInfo(printed_flag);
        std::cout << "Class3 info" << std::endl;
        return printed_flag;
    }
};
0
Pepijn Kramer On

Example of (crtp) mixin using inheritance to avoid diamond inheritance altogether.

#include <iostream>

class foo_itf
{
    virtual void foo() = 0;
};

class bar_itf
{
    virtual void bar() = 0;
};

// do NOT put reusable code in a baseclass
// but in a mixin class
template<typename class_t>
class foo_impl :
    public foo_itf
{
public:
    void foo() override
    {
        std::cout << "foo\n";
    }
};

template<typename class_t>
class bar_impl :
    public bar_itf
{
public:
    void bar() override
    {
        std::cout << "bar\n";
    }
};

// "left diamond" class replacement
class foo_t :
    public foo_impl<foo_t>
{
};

// "right diamond" class replacement
class bar_t :
    public bar_impl<bar_t>
{
};

// class that can do both
// but is NOT derived from a common single parent class
// so there is no diamond.
class foo_bar_t :
    public foo_impl<foo_bar_t>,
    public bar_impl<foo_bar_t>
{
};


int main()
{
    foo_t foo;
    bar_t bar;
    foo_bar_t foobar;

    foo.foo();
    bar.bar();

    foobar.foo();
    foobar.bar();

    return 0;
}