简体   繁体   中英

C++ diamond problem - How to call base method only once

I'm using multiple inheritance in C++ and extending base methods by calling their base explicitly. Assume the following hierarchy:

     Creature
    /        \
 Swimmer    Flier
    \        /
       Duck

Which corresponds to

class Creature
{
    public:
        virtual void print()
        {
            std::cout << "I'm a creature" << std::endl;
        }
};

class Swimmer : public virtual Creature
{
     public:
        void print()
        {
            Creature::print();
            std::cout << "I can swim" << std::endl;
        }
};

class Flier : public virtual Creature
{
     public:
        void print()
        {
            Creature::print();
            std::cout << "I can fly" << std::endl;
        }
};

class Duck : public Flier, public Swimmer
{
     public:
        void print()
        {
            Flier::print();
            Swimmer::print();
            std::cout << "I'm a duck" << std::endl;
        }
};

Now this presents a problem - calling the duck's print method calls its respective base methods, all of which in turn call the Creature::print() method, so it ends up being called twice-

I'm a creature
I can fly
I'm a creature
I can swim
I'm a duck

I would like to find a way to make sure the base method is called only once. Something similar to the way virtual inheritance works (calling the base constructor on the first call, then only assigning a pointer to it on successive calls from other derived classes).

Is there some built-in way to do this or do we need to resort to implementing one ourselves?

If so, how would you approach this?

The question isn't specific to printing. I wondered if there's a mechanism for extending base methods and functionality while keeping the call order and avoiding the diamond problem.

I understand now that the most prominent solution would be to add helper methods, but I just wondered if there's a "cleaner" way.

Most likely this is a XY problem. But ... just don't call it twice.

#include <iostream>

class Creature
{
public:
    virtual void identify()
    {
        std::cout << "I'm a creature" << std::endl;
    }
};

class Swimmer : public virtual Creature
{
public:
    virtual void identify() override
    {
        Creature::identify();
        tell_ability();
        std::cout << "I'm a swimmer\n";
    }

    virtual void tell_ability()
    {
        std::cout << "I can swim\n";
    }
};

class Flier : public virtual Creature
{
public:
    virtual void identify() override
    {
        Creature::identify();
        tell_ability();
        std::cout << "I'm a flier\n";
    }

    virtual void tell_ability()
    {
        std::cout << "I can fly\n";
    }
};

class Duck : public Flier, public Swimmer
{
public:
    virtual void tell_ability() override
    {
        Flier::tell_ability();
        Swimmer::tell_ability();
    }

    virtual void identify() override
    {
        Creature::identify();
        tell_ability();
        std::cout << "I'm a duck\n";
    }
};

int main()
{
    Creature c;
    c.identify();
    std::cout << "------------------\n";

    Swimmer s;
    s.identify();
    std::cout << "------------------\n";

    Flier f;
    f.identify();
    std::cout << "------------------\n";

    Duck d;
    d.identify();
    std::cout << "------------------\n";
}

Output:

I'm a creature
------------------
I'm a creature
I can swim
I'm a swimmer
------------------
I'm a creature
I can fly
I'm a flier
------------------
I'm a creature
I can fly
I can swim
I'm a duck
------------------

We can let the base class keep track of the attributes:

#include <iostream>
#include <string>
#include <vector>

using namespace std::string_literals;

class Creature
{
public:
    std::string const attribute{"I'm a creature"s};
    std::vector<std::string> attributes{attribute};
    virtual void print()
    {
        for (auto& i : attributes)
            std::cout << i << std::endl;
    }
};

class Swimmer : public virtual Creature
{
public:
    Swimmer() { attributes.push_back(attribute); }
    std::string const attribute{"I can swim"s};
};

class Flier : public virtual Creature
{
public:
    Flier() { attributes.push_back(attribute); }
    std::string const attribute{"I can fly"s};
};

class Duck : public Flier, public Swimmer
{
public:
    Duck() { attributes.push_back(attribute); }
    std::string const attribute{"I'm a duck"s};
};

int main()
{
    Duck d;
    d.print();
}

Likewise, if it is not just printing we're after, but rather the function calls, then we could let the base class keep track of the functions:

#include <iostream>
#include <functional>
#include <vector>

class Creature
{
public:
    std::vector<std::function<void()>> print_functions{[this] {Creature::print_this(); }};
    virtual void print_this()
    {
        std::cout << "I'm a creature" << std::endl;
    }
    void print()
    {
        for (auto& f : print_functions)
            f();
    }
};

class Swimmer : public virtual Creature
{
public:
    Swimmer() { print_functions.push_back([this] {Swimmer::print_this(); }); }
    void print_this()
    {
        std::cout << "I can swim" << std::endl;
    }
};

class Flier : public virtual Creature
{
public:
    Flier() { print_functions.push_back([this] {Flier::print_this(); }); }
    void print_this()
    {
        std::cout << "I can fly" << std::endl;
    }
};

class Duck : public Flier, public Swimmer
{
public:
    Duck() { print_functions.push_back([this] {Duck::print_this(); }); }
    void print_this()
    {
        std::cout << "I'm a duck" << std::endl;
    }
};

int main()
{
    Duck d;
    d.print();
}

An easy way is to create a bunch of helper classes that mimick the inheritance structure of your main hierarchy and do all the printing in their constructors.

 struct CreaturePrinter {
    CreaturePrinter() { 
       std::cout << "I'm a creature\n";
    }
 };

 struct FlierPrinter: virtual CreaturePrinter ... 
 struct SwimmerPrinter: virtual CreaturePrinter ...
 struct DuckPrinter: FlierPrinter, SwimmerPrinter ...

Then each print method in the main hierarchy just creates the corresponding helper class. No manual chaining.

For maintainability you can make each printer class nested in its corresponding main class.

Naturally in most real world cases you want to pass a reference to the main object as an argument to the constructor of its helper.

Your explicit calls to the print methods form the crux of the issue.

One way round this would be to drop the print calls, and replace them with say

void queue(std::set<std::string>& data)

and you accumulate the print messages into the set . Then it doesn't matter those functions in the hierarchy get called more than once.

You then implement the printing of the set in a single method in Creature .

If you want to preserve the order of printing, then you'd need to replace the set with another container that respects the order of insertion and rejects duplicates.

If you want that middle class method, do not call the base class method. The easiest and simplest way is to extract extra methods, and then reimplementing Print is easy.

class Creature
{
    public:
        virtual void print()
        {
            std::cout << "I'm a creature" << std::endl;
        }
};

class Swimmer : public virtual Creature
{
     public:
        void print()
        {
            Creature::print();
            detailPrint();
        }

        void detailPrint()
        {
            std::cout << "I can swim" << std::endl;
        }
};

class Flier : public virtual Creature
{
     public:
        void print()
        {
            Creature::print();
            detailPrint();
        }

        void detailPrint()
        {
            std::cout << "I can fly" << std::endl;
        }
};

class Duck : public Flier, public Swimmer
{
     public:
        void print()
        {
            Creature::Print();
            Flier::detailPrint();
            Swimmer::detailPrint();
            detailPrint();
        }

        void detailPrint()
        {
            std::cout << "I'm a duck" << std::endl;
        }
};

Without details what is your actual problem is, it hard to come up with a better solution.

Use:

template<typename Base, typename Derived>
bool is_dominant_descendant(Derived * x) {
    return std::abs(
        std::distance(
            static_cast<char*>(static_cast<void*>(x)),
            static_cast<char*>(static_cast<void*>(dynamic_cast<Base*>(x)))
        )
    ) <= sizeof(Derived);
};

class Creature
{
public:
    virtual void print()
    {
        std::cout << "I'm a creature" << std::endl;
    }
};

class Walker : public virtual Creature
{
public:
    void print()
    {
        if (is_dominant_descendant<Creature>(this))
            Creature::print();
        std::cout << "I can walk" << std::endl;
    }
};

class Swimmer : public virtual Creature
{
public:
    void print()
    {
        if (is_dominant_descendant<Creature>(this))
            Creature::print();
        std::cout << "I can swim" << std::endl;
    }
};

class Flier : public virtual Creature
{
public:
    void print()
    {
        if (is_dominant_descendant<Creature>(this))
            Creature::print();
        std::cout << "I can fly" << std::endl;
    }
};

class Duck : public Flier, public Swimmer, public Walker
{
public:
    void print()
    {
        Walker::print();
        Swimmer::print();
        Flier::print();
        std::cout << "I'm a duck" << std::endl;
    }
};

And with Visual Studio 2015 the output is:

I'm a creature
I can walk
I can swim
I can fly
I'm a duck

But is_dominant_descendant does not have a portable definition. I wish it were a standard concept.

You are asking for something like inheritance on a function level that automatically calls the inherited function and just adds more code. Also you want it to be done in a virtual way just like class inheritance. Pseudo syntax:

class Swimmer : public virtual Creature
{
     public:
        // Virtually inherit from Creature::print and extend it by another line of code
        void print() : virtual Creature::print()
        {
            std::cout << "I can swim" << std::endl;
        }
};

class Flier : public virtual Creature
{
     public:
        // Virtually inherit from Creature::print and extend it by another line of code
        void print() : virtual Creature::print()
        {
            std::cout << "I can fly" << std::endl;
        }
};

class Duck : public Flier, public Swimmer
{
     public:
        // Inherit from both prints. As they were created using "virtual function inheritance",
        // this will "mix" them just like in virtual class inheritance
        void print() : Flier::print(), Swimmer::print()
        {
            std::cout << "I'm a duck" << std::endl;
        }
};

So the answer to your question

Is there some built-in way to do this?

is no . Something like this does not exist in C++. Also, I'm not aware of any other language that has something like this. But it is an interesting idea...

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