C ++钻石问题 - 如何只调用一次基本方法

问题描述 投票:37回答:7

我在C ++中使用多重继承,并通过显式调用它们的基础来扩展基本方法。假设以下层次结构:

     Creature
    /        \
 Swimmer    Flier
    \        /
       Duck

哪个对应

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;
        }
};

现在这提出了一个问题 - 调用duck的print方法调用它各自的基本方法,所有这些方法又调用Creature::print()方法,因此它最终被调用两次 -

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

我想找到一种方法来确保只调用一次base方法。类似于虚拟继承的工作方式(在第一次调用时调用基础构造函数,然后在其他派生类的连续调用中仅指定指向它的指针)。

是否有一些内置的方法可以做到这一点,还是我们需要自己实现一个?

如果是这样,你会怎么做?

问题不是特定于印刷。我想知道是否有一种扩展基本方法和功能的机制,同时保持调用顺序并避免钻石问题。

我现在明白,最突出的解决方案是添加辅助方法,但我只是想知道是否有一种“更清洁”的方式。

c++ multiple-inheritance diamond-problem
7个回答
50
投票

这很可能是XY问题。但是......只是不要两次打电话。

#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
------------------

22
投票

我们可以让基类跟踪属性:

#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();
}

同样,如果它不仅仅是打印,而是函数调用,那么我们可以让基类跟踪函数:

#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();
}

8
投票

一种简单的方法是创建一组辅助类,它们模仿主层次结构的继承结构,并在其构造函数中执行所有打印。

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

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

然后,主层次结构中的每个print方法都会创建相应的帮助程序类。没有手动链接。

为了可维护性,您可以将每个打印机类嵌套在相应的主类中。

当然,在大多数现实世界中,您希望将对主对象的引用作为其助手的构造函数的参数传递。


5
投票

您对print方法的明确调用构成了问题的关键。

这方面的一种方法是放弃print呼叫,并用say替换它们

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

并将打印消息累积到set中。然后,层次结构中的那些函数不止一次被调用无关紧要。

然后,您可以在Creature中使用单个方法实现集合的打印。

如果您想保留打印顺序,那么您需要将set替换为另一个尊重插入顺序的容器并拒绝重复。


5
投票

如果您想要那个中间类方法,请不要调用基类方法。最简单和最简单的方法是提取额外的方法,然后重新实现Print很容易。

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;
        }
};

没有详细信息您的实际问题是什么,很难找到更好的解决方案。


4
投票

使用:

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;
    }
};

使用Visual Studio 2015,输出为:

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

is_dominant_descendant没有便携式定义。我希望这是一个标准的概念。


2
投票

您要求在函数级别继承自动调用继承函数并添加更多代码。您也希望它像类继承一样以虚拟方式完成。伪语法:

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;
        }
};

所以你的问题的答案

有没有一些内置的方法来做到这一点?

没有。 C ++中不存在这样的东西。另外,我不知道有任何其他语言有这样的东西。但这是一个有趣的想法......

© www.soinside.com 2019 - 2024. All rights reserved.