web-dev-qa-db-ja.com

C ++ダイヤモンドの問題-基本メソッドを1回だけ呼び出す方法

私は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;
        }
};
_

これで問題が発生しました-アヒルのprintメソッドを呼び出すと、それぞれの基本メソッドが呼び出され、そのすべてがCreature::print()メソッドを呼び出すため、2回呼び出されます

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

基本メソッドが1回だけ呼び出されるようにする方法を見つけたいのですが。仮想継承のしくみに似たもの(最初の呼び出しで基本コンストラクターを呼び出し、次に他の派生クラスからの連続した呼び出しでのみそれにポインターを割り当てる)。

これを行う組み込みの方法はありますか、それとも自分で実装する必要がありますか?

もしそうなら、あなたはこれにどのように取り組みますか?

質問は印刷に固有のものではありません。呼び出しの順序を維持し、ひし形の問題を回避しながら、基本メソッドと機能を拡張するためのメカニズムがあるかどうか疑問に思いました。

現在、最も顕著な解決策はヘルパーメソッドを追加することであることを理解していますが、「よりクリーンな」方法があるかどうか疑問に思いました。

38
O. Aroesti

継承された関数を自動的に呼び出し、コードを追加するだけの関数レベルでの継承のようなものを求めています。また、クラスの継承と同じように仮想的に実行する必要があります。疑似構文:

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

だからあなたの質問への答え

これを行うための組み込みの方法はありますか?

noです。このようなものはC++には存在しません。また、私はこのような何かを持っている他の言語を知りません。しかし、それは興味深いアイデアです...

3
sebrockm

ほとんどの場合、これは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";
}

出力:

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
------------------
50
Swordfish

基本クラスに属性を追跡させることができます。

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

簡単な方法は、メイン階層の継承構造を模倣した一連のヘルパークラスを作成し、コンストラクターですべての印刷を行うことです。

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

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

次に、メイン階層の各印刷メソッドは、対応するヘルパークラスを作成するだけです。手動チェーンはありません。

保守性を高めるために、各プリンタークラスを対応するメインクラスにネストさせることができます。

当然のことながら、ほとんどの実際のケースでは、メインオブジェクトへの参照を引数としてヘルパーのコンストラクターに渡します。

9

printメソッドへの明示的な呼び出しは、問題の核心を形成します。

これを回避する1つの方法は、printの呼び出しを削除し、sayに置き換えることです。

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

そして、印刷メッセージをsetに蓄積します。次に、階層内のこれらの関数が2回以上呼び出されることは問題ではありません。

次に、Creatureの単一のメソッドでセットの印刷を実装します。

印刷の順序を維持したい場合は、setを挿入順序を尊重し、重複を拒否する別のコンテナに置き換える必要があります。

6
Bathsheba

その中間クラスのメソッドが必要な場合は、基本クラスのメソッドを呼び出さないでください。最も簡単で最も簡単な方法は、余分なメソッドを抽出することであり、その場合、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;
        }
};

あなたの実際の問題が何であるか詳細がないと、より良い解決策を思いつくのは困難です。

6
Marek R

使用する:

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には移植可能な定義がありません。それが標準的なコンセプトだったらいいのに。

4
Red.Wave