web-dev-qa-db-ja.com

C ++で「仮想テンプレート機能」を実現する方法

最初に:私は読んで、今では仮想テンプレートメンバ関数がC++では(まだ?)可能でないことを知っています。回避策は、クラスをテンプレートにしてから、member-functionでもtemplate-argumentを使用することです。

しかし、OOPのコンテキストでは、クラスが実際にテンプレートである場合、以下の例はあまり「自然」ではないことがわかります。コードは実際には機能していませんが、gcc-4.3.4は次のように報告します:error: templates may not be ‘virtual’

#include <iostream>
#include <vector>

class Animal {
    public:
        template< class AMOUNT >
        virtual void eat( AMOUNT amount ) const { 
            std::cout << "I eat like a generic Animal." << std::endl; 
        }
        virtual ~Animal() { 
        }
};

class Wolf : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a wolf!" << std::endl; 
        }
        virtual ~Wolf() { 
        }
};

class Fish : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a fish!" << std::endl; 
        }
        virtual ~Fish() { 
        }
};

class GoldFish : public Fish {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a goldfish!" << std::endl; 
        }
        virtual ~GoldFish() { 
        }
};

class OtherAnimal : public Animal {
        virtual ~OtherAnimal() { 
        }
};

int main() {
    std::vector<Animal*> animals;
    animals.Push_back(new Animal());
    animals.Push_back(new Wolf());
    animals.Push_back(new Fish());
    animals.Push_back(new GoldFish());
    animals.Push_back(new OtherAnimal());

    for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->eat();
        delete *it;
    }

    return 0;
}

したがって、「Fish <Amount> foo」を作成するのは奇妙なことです。しかし、動物ごとに任意の量の食物を提供することが望ましいと思われます。

したがって、私は次のようなことを達成する方法についての解決策を探しています

Fish bar;
bar.eat( SomeAmount food );

これは、forループを見るときに特に役立ちます。特定の量(FoodAmount)をすべての異なる動物(eat()やbind1st()など)に与えたいと思うかもしれませんが、これは簡単にできませんでしたが、私は傷が非常に直観的であるため(ある程度までは) 「自然」。これはベクターの「均一な」性質によるものだと主張したい人もいるかもしれませんが、これを実現することが可能であるべきだと思います。かなり長い間私を困惑させました...

[編集]

おそらく私の質問の背後にある動機を明確にするために、Exporterクラスをプログラミングし、それから異なる、より専門的なクラスを派生させたいと思います。最上位のエクスポータークラスは通常、見た目や構造上の目的にのみ使用されますが、GraphExporterクラスが派生します。ただし、動物の例と同様に、特殊/派生クラス(SpecialGraphExplorerなど)でもGraphExporter *を定義できるようにしたいのですが、「write(out_file)」を呼び出すときは、代わりにSpecialGraphExporterの適切なメンバー関数を呼び出す必要がありますGraphExporter :: write(out_file)の。

たぶんこれは私の状況と意図をより明確にします。

ベスト、

39
Shadow

少し考えた後、これを古典的なmulti-method要件、つまりruntime type複数のパラメーター。通常の仮想関数はsingle dispatchの比較(およびthisのタイプでのみディスパッチします)。

以下を参照してください。

  • Andrei Alexandrescuは、「モダンC++デザイン」でジェネリックを使用してマルチメソッドを実装することについて書いています(C++の重要なビット?)。
    • 第11章:「マルチメソッド」 -基本的なマルチメソッドを実装し、対数(順序付けられたタイプリストを使用)にしてから、一定時間のマルチメソッドに進みます。非常に強力なもの!
  • codeproject article それはまさにそのような実装を持っているようです:
    • あらゆる種類の型キャスト(動的、静的、再解釈、constまたはCスタイル)を使用しない
    • rTTIの使用なし。
    • プリプロセッサを使用しません。
    • 強い型安全性;
    • 個別のコンパイル。
    • マルチメソッド実行の一定時間。
    • マルチメソッド呼び出し中に(newまたはmallocを介して)動的メモリ割り当てがありません。
    • 非標準ライブラリの使用なし;
    • 標準のC++機能のみが使用されます。
  • C++ Open Method Compiler 、Peter Pirkelbauer、Yuriy Solodkyy、およびBjarne Stroustrup
  • Lokiライブラリには A MultipleDispatcher があります
  • ウィキペディアには、C++での複数のディスパッチに関する例が記載された、すてきな 簡単な説明 があります。

参照用のウィキペディアの記事からの「単純な」アプローチを以下に示します(より単純なアプローチは、派生型の数が多いほどよくなります)。

// Example using run time type comparison via dynamic_cast

struct Thing {
    virtual void collideWith(Thing& other) = 0;
}

struct Asteroid : Thing {
    void collideWith(Thing& other) {
        // dynamic_cast to a pointer type returns NULL if the cast fails
        // (dynamic_cast to a reference type would throw an exception on failure)
        if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
            // handle Asteroid-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Asteroid-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}

struct Spaceship : Thing {
    void collideWith(Thing& other) {
        if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
            // handle Spaceship-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Spaceship-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}
31
sehe

明らかに、仮想メンバー関数テンプレートは許可されておらず、理論的にも実現できませんでした。基本クラスの仮想テーブルを作成するには、有限数の仮想関数ポインターエントリが必要です。関数テンプレートは、無制限の量の「オーバーロード」(つまりインスタンス化)を許可します。

理論的に言えば、言語(C++など)は、インスタンス化の実際の(有限の)リストを指定するメカニズムがあれば、仮想メンバー関数テンプレートを許可できます。 C++にはそのメカニズム(つまり、明示的なテンプレートのインスタンス化)がありますので、新しいC++標準でこれを行うことは可能だと思います(コンパイラベンダーがこの機能を実装するのにどのようなトラブルが伴うかはわかりませんが)。しかし、それは単なる理論的な議論であり、実際には、これは単に許可されていません。事実は変わりませんが、仮想関数の数を有限にする必要があります(テンプレートは許可されません)。

もちろん、それはクラステンプレートが仮想関数を持つことができないことを意味するものでも、仮想関数が関数テンプレートを呼び出すことができないことを意味するものでもありません。そのため、その流れには多くの解決策があります(Visitorパターンや他のスキームなど)。

(理解するのは難しいですが)目的にかなう解決策の1つは、次のとおりです(基本的には訪問者パターンです)。

#include <iostream>
#include <vector>

struct Eater { 
  virtual void operator()(int amount) const = 0;
  virtual void operator()(double amount) const = 0;
};

template <typename EaterType>
struct Eater_impl : Eater {
  EaterType& data;
  Eater_impl(EaterType& aData) : data(aData) { };
  virtual void operator()(int amount) const { data.eat_impl(amount); };
  virtual void operator()(double amount) const { data.eat_impl(amount); };
};

class Animal {
  protected:
    Animal(Eater& aEat) : eat(aEat) { };
  public:
    Eater& eat;
    virtual ~Animal() { delete &eat; };
};

class Wolf : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a wolf!" << std::endl; 
    }

  public:
    friend struct Eater_impl<Wolf>;        
    Wolf() : Animal(*(new Eater_impl<Wolf>(*this))) { };
    virtual ~Wolf() { };
};

class Fish : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a fish!" << std::endl; 
    }
  public:
    friend struct Eater_impl<Fish>;
    Fish() : Animal(*(new Eater_impl<Fish>(*this))) { };
    virtual ~Fish() { };
};

int main() {
  std::vector<Animal*> animals;
  animals.Push_back(new Wolf());
  animals.Push_back(new Fish());

  for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
    (*it)->eat(int(0));
    (*it)->eat(double(0.0));
    delete *it;
  };

  return 0;
};

上記は、1つの場所(Eater_implクラステンプレート内)でのみ必要な有限数のオーバーロードを定義でき、派生クラスで必要なのは関数テンプレート(および場合によっては追加のオーバーロード)特殊なケース)。もちろん、少しのオーバーヘッドがありますが、オーバーヘッドを減らすためにもう少し考えを入れることができると思います(追加の参照ストレージとEater_implの動的割り当て)。不思議なことに、繰り返し発生するテンプレートパターンは、おそらくこの目的のために何らかの形で使用できると思います。

12
Mikael Persson

訪問者パターンが解決策になると思います。

[〜#〜] update [〜#〜]

私の例を終えました:

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

class Animal;
class Wolf;
class Fish;

class Visitor
{
    public:
    virtual void visit(const Animal& p_animal) const = 0;
    virtual void visit(const Wolf& p_animal) const = 0;
    virtual void visit(const Fish& p_animal) const = 0;
};

template<class AMOUNT>
class AmountVisitor : public Visitor
{
    public:
    AmountVisitor(AMOUNT p_amount) : m_amount(p_amount) {}
    virtual void visit(const Animal& p_animal) const
    {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual void visit(const Wolf& p_animal) const
    {
        std::cout << "I eat like a wolf!" << std::endl;
    }
    virtual void visit(const Fish& p_animal) const
    {
        std::cout << "I eat like a fish!" << std::endl;
    }


    AMOUNT m_amount;
};

class Animal {
    public:

        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }

        virtual ~Animal() {
        }
};

class Wolf : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

class Fish : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

int main()
{
    typedef boost::shared_ptr<Animal> TAnimal;
    std::vector<TAnimal> animals;
    animals.Push_back(TAnimal(new Animal()));
    animals.Push_back(TAnimal(new Wolf()));
    animals.Push_back(TAnimal(new Fish()));

    AmountVisitor<int> amount(10);

    for (std::vector<TAnimal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->Accept(amount);
    }

    return 0;
}

これは印刷します:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!

Mikaelの投稿ごとに、CRTPを使用し、明示的なサブクラス参照にderived()を使用するEigenのスタイルに従って、別の派生物を作成しました。

// Adaptation of Visitor Pattern / CRTP from:
// http://stackoverflow.com/a/5872633/170413

#include <iostream>
using std::cout;
using std::endl;

class Base {
public:
  virtual void tpl(int x) = 0;
  virtual void tpl(double x) = 0;
};

// Generics for display
template<typename T>
struct trait {
  static inline const char* name() { return "T"; }
};
template<>
struct trait<int> {
  static inline const char* name() { return "int"; }
};
template<>
struct trait<double> {
  static inline const char* name() { return "double"; }
};

// Use CRTP for dispatch
// Also specify base type to allow for multiple generations
template<typename BaseType, typename DerivedType>
class BaseImpl : public BaseType {
public:
  void tpl(int x) override {
    derived()->tpl_impl(x);
  }
  void tpl(double x) override {
    derived()->tpl_impl(x);
  }
private:
  // Eigen-style
  inline DerivedType* derived() {
    return static_cast<DerivedType*>(this);
  }
  inline const DerivedType* derived() const {
    return static_cast<const DerivedType*>(this);
  }
};

// Have Child extend indirectly from Base
class Child : public BaseImpl<Base, Child> {
protected:
  friend class BaseImpl<Base, Child>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "Child::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};

// Have SubChild extend indirectly from Child
class SubChild : public BaseImpl<Child, SubChild> {
protected:
  friend class BaseImpl<Child, SubChild>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "SubChild::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};


template<typename BaseType>
void example(BaseType *p) {
  p->tpl(2);
  p->tpl(3.0);
}

int main() {
  Child c;
  SubChild sc;

  // Polymorphism works for Base as base type
  example<Base>(&c);
  example<Base>(&sc);
  // Polymorphism works for Child as base type
  example<Child>(&sc);
  return 0;
}

出力:

Child::tpl_impl<int>(2)
Child::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)

このスニペットは、ソースにあります: repro:c808ef0:cpp_quick/virtual_template.cc

4
eacousineau

仮想テンプレート機能は許可されていません。ただし、ここで1つORを使用できます。

仮想メソッドを使用してインターフェイスを作成し、食べるインターフェイスを持つという点でさまざまな動物を実装できます。 (すなわちPIMPL)

直感的ではない人は、任意の動物へのテンプレート化されたconst参照を取得し、それに応じて食べさせることができる無料の機能として、非メンバー非友人テンプレート機能を持つことになります。

記録のために、ここでテンプレートは必要ありません。基本クラスの純粋な仮想抽象メソッドは、すべての動物が食べなければならない場所を強制してインターフェイスし、オーバーライドする方法を定義します特定の方法であれば、このデフォルトの方法を使用できます。

2
AJG85

仮想関数を使用してテンプレートクラスを作成し、次の方法でテンプレートを使用せずに派生クラスに関数を実装できます。

a.h:

template <class T>
class A
{
public:
    A() { qDebug() << "a"; }

    virtual A* Func(T _template) { return new A;}
};


b.h:

class B : public A<int>
{
public:
    B();
    virtual A* Func(int _template) { return new B;}
};


and the function CTOR and call 

  A<int>* a1=new B;
    int x=1;
    a1->Func(x);

残念ながら、クラスをテンプレートとして宣言せずに、テンプレートパラメータを使用して仮想関数を作成する方法が見つかりませんでした。

2
user3103989

私はあなたのコードをコピーして修正したので、あなたが望むように正確に動作するはずです:

        #include <iostream>
        #include <vector>

        //defined new enum type
        enum AnimalEnum
        {
           animal,
           wolf,
           fish,
           goldfish,
           other
        };

        //forward declarations
        class Wolf;
        class Fish;
        class GoldFish;
        class OtherAnimal;

        class Animal {
            private:
            AnimalEnum who_really_am_I;
            void* animal_ptr;
            public:
                //declared new constructors overloads for each type of animal
                Animal(const Animal&);
                Animal(const Wolf&);
                Animal(const Fish&);
                Animal(const GoldFish&);
                Animal(const OtherAnimal&);
                template< class AMOUNT >
                /*removed the virtual keyword*/ void eat( AMOUNT amount ) const { 
                    switch (this->who_really_am_I)
                    {
                       case AnimalEnum::other: //You defined OtherAnimal so that it doesn't override the eat action, so it will uses it's Animal's eat
                       case AnimalEnum::animal: std::cout << "I eat like a generic Animal." << std::endl; break;
                       case AnimalEnum::wolf: ((Wolf*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::fish: ((Fish*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::goldfish: ((GoldFish*)this->animal_ptr)->eat(amount) break;
                    }
                }
                void DeleteMemory() { delete this->animal_ptr; }
                virtual ~Animal() { 
                   //there you can choose if whether or not to delete "animal_ptr" here if you want or not
                }
        };

        class Wolf : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a wolf!" << std::endl; 
                }
                virtual ~Wolf() { 
                }
        };

        class Fish : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a fish!" << std::endl; 
                }
                virtual ~Fish() { 
                }
        };

        class GoldFish : public Fish {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a goldfish!" << std::endl; 
                }
                virtual ~GoldFish() { 
                }
        };

        class OtherAnimal : public Animal {
                //OtherAnimal constructors must be defined here as Animal's constructors
                OtherAnimal(const Animal& a) : Animal(a) {}
                OtherAnimal(const Wolf& w) : Animal(w) {}
                OtherAnimal(const Fish& f) : Animal(f) {}
                OtherAnimal(const GoldFish& g) : Animal(g) {}
                OtherAnimal(const OtherAnimal& o) : Animal(o) {}
                virtual ~OtherAnimal() { 
                }
        };
        //OtherAnimal will be useful only if it has it's own actions and members, because if not, typedef Animal OtherAnimal or using OtherAnimal = Animal can be used, and it can be removed from above declarations and below definitions

//Here are the definitions of Animal constructors that were declared above/before:    
        Animal::Animal(const Animal& a) : who_really_am_I(AnimalEnum::animal), animal_ptr(nullptr) {}

        Animal::Animal(const Wolf& w) : who_really_am_I(AnimalEnum::wolf), animal_ptr(new Wolf(w)) {}

        Animal::Animal(const Fish& f) : who_really_am_I(AnimalEnum::fish), animal_ptr(new Fish(f)) {}

        Animal::Animal(const GoldFish& g) : who_really_am_I(AnimalEnum::goldfish), animal_ptr(new GoldFish(g)) {}

        Animal::Animal(const OtherAnimal& o) :
    who_really_am_I(AnimalEnum::other), animal_ptr(new OtherAnimal(o)) {}

        int main() {
            std::vector<Animal> animals;
            animals.Push_back(Animal());
            animals.Push_back(Wolf()); //Wolf is converted to Animal via constructor
            animals.Push_back(Fish()); //Fish is converted to Animal via constructor
            animals.Push_back(GoldFish()); //GoldFish is converted to Animal via constructor
            animals.Push_back(OtherAnimal()); //OtherAnimal is converted to Animal via constructor

            for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
                it->eat(); //this is Animal's eat that invokes other animals eat
                //delete *it; Now it should be:
                it->DeleteMemory();
            }
            animals.clear(); //All animals have been killed, and we don't want full vector of dead animals.

            return 0;
        }
1
user2133061

あなたのシナリオでは、コンパイル時ポリモーフィズムとランタイムポリモーフィズムを混在させようとしていますが、この「方向」では実行できません。

基本的に、AMOUNTテンプレート引数は、eatの各実装が使用するすべての操作の和集合に基づいて、実装する型の期待されるインターフェースを表します。これらの各操作を宣言した抽象型を作成し、必要に応じて仮想化すると、eat型を異なる型(AMOUNTインターフェイスから派生)で呼び出すことができます。そして、期待どおりに動作します。

0
nate