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

O. *_*sti 37 c++ multiple-inheritance diamond-problem

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

     Creature
    /        \
 Swimmer    Flier
    \        /
       Duck
Run Code Online (Sandbox Code Playgroud)

对应于

     Creature
    /        \
 Swimmer    Flier
    \        /
       Duck
Run Code Online (Sandbox Code Playgroud)

现在,这带来了一个问题-调用duck的print方法将调用其各自的基本方法,所有这些方法都依次调用该Creature::print()方法,因此最终被两次调用-

I'm a creature
I can fly
I'm a creature
I can swim
I'm a duck
Run Code Online (Sandbox Code Playgroud)

我想找到一种方法来确保基本方法仅被调用一次。与虚拟继承的工作方式类似(在第一次调用时调用基本构造函数,然后仅在来自其他派生类的后续调用中为其分配一个指针)。

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

如果是这样,您将如何处理?

这个问题并非特定于打印。我想知道是否有一种机制可以扩展基本方法和功能,同时保持调用顺序并避免出现钻石问题。

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

Swo*_*ish 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";
}
Run Code Online (Sandbox Code Playgroud)

输出:

#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";
}
Run Code Online (Sandbox Code Playgroud)


wal*_*lly 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();
}
Run Code Online (Sandbox Code Playgroud)

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

#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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Kevin我猜你也不会喜欢vtables。:) (6认同)

n. *_* m. 8

一种简单的方法是创建一堆帮助程序类,以模仿主层次结构的继承结构,并在其构造函数中进行所有打印。

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

 struct FlierPrinter: virtual CreaturePrinter ... 
 struct SwimmerPrinter: virtual CreaturePrinter ...
 struct DuckPrinter: FlierPrinter, SwimmerPrinter ...
Run Code Online (Sandbox Code Playgroud)

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

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

自然,在大多数实际情况下,您希望将对主对象的引用作为参数传递给其辅助函数的构造函数。


Bat*_*eba 5

您对这些print方法的显式调用构成了问题的症结。

解决此问题的一种方法是挂断print电话,并用语音替换

void queue(std::set<std::string>& data)
Run Code Online (Sandbox Code Playgroud)

并且您将打印消息累积到中set。然后,层次结构中的那些函数被多次调用并不重要。

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

如果要保留打印顺序,则需要set用另一个尊重插入顺序并拒绝重复项的容器替换。

  • 这一般不能解决问题 (2认同)

Mar*_*k R 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;
        }
};
Run Code Online (Sandbox Code Playgroud)

没有细节,您的实际问题是,很难找到更好的解决方案。


seb*_*ckm 3

您要求在函数级别上进行类似于继承的操作,它会自动调用继承的函数并仅添加更多代码。您还希望它以虚拟方式完成,就像类继承一样。伪语法:

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;
        }
};
Run Code Online (Sandbox Code Playgroud)

所以你的问题的答案

有一些内置的方法可以做到这一点吗?

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