web-dev-qa-db-ja.com

C ++で抽象クラステンプレートのインターフェイスを作成する

私は以下のようなコードを持っています。抽象テンプレートクラスFooと、テンプレートのインスタンス化から派生した2つのサブクラス(Foo1とFoo2)があります。 Foo1またはFoo2タイプのオブジェクトを指すことができるポインターをプログラムで使用したいので、インターフェースIFooを作成しました。

テンプレートのインスタンス化に依存しているため、インターフェイスにfunctionBを含める方法がわからないのが私の問題です。インターフェイスを介してfunctionBにアクセスできるようにすることも可能ですか、それとも不可能を試みていますか?

ご助力ありがとうございます。

class IFoo {
    public:
        virtual functionA()=0;

};

template<class T>
class Foo : public IFoo{
    public:
        functionA(){ do something; };
        functionB(T arg){ do something; };
};

class Foo1 : public Foo<int>{
...
};

class Foo2 : public Foo<double>{
...
};
23
bishboshbash

あなたは実際に不可能を試みています。

問題の核心は単純です:virtualtemplateはうまく混ざりません。

  • templateは、コンパイル時のコード生成に関するものです。あなたはそれをある種のタイプ認識マクロ+メタプログラミングのためのいくつかのちりばめられたトリックと考えることができます。
  • virtualは実行時の決定に関するものであり、これにはいくつかの作業が必要です。

virtualは通常、仮想テーブルを使用して実装されます(メソッドをリストするテーブルを考えてください)。メソッドの数はコンパイル時に既知である必要があり、基本クラスで定義されます。

ただし、お客様の要件に応じて、無限のサイズの仮想テーブルが必要になります。これには、まだ見たことのない型のメソッドが含まれており、これは今後数年間で定義されるだけです...残念ながら不可能です。

そしてそれが可能だったら?

まあ、それは意味がありません。 intを指定してFoo2を呼び出すとどうなりますか?それのためのものではありません!したがって、Foo2IFooのすべてのメソッドを実装するという原則に違反します。

ですから、本当の問題を述べた方がいいでしょう。このようにして、技術レベルではなく設計レベルであなたを助けることができます:)

18
Matthieu M.

最も簡単な方法は、インターフェースをテンプレート化することです。

template <class T>
class IFoo {
    public:
        virtual void functionA()=0;
        virtual void functionB(T arg){ do something; };
};

template<class T>
class Foo : public IFoo<T>{
    public:
        void functionA(){ do something; };
        void functionB(T arg){ do something; };
};
8
Igor Zevaka

FunctionBの引数の型は事前にわかっている必要があるため、選択肢は1つだけです。それを保持できる型にする可能なすべての引数にします。これは「トップタイプ」と呼ばれることもあり、ブーストライブラリにはanyタイプがあり、トップタイプの動作に非常に近くなります。これが機能する可能性があるものです:

_#include <boost/any.hpp>
#include <iostream>
using namespace boost;

class IFoo {
    public:
    virtual void functionA()=0;
    virtual void functionB(any arg)=0; //<-can hold almost everything
};

template<class T>
class Foo : public IFoo{
    public:
        void functionA(){  };
        void real_functionB(T arg)
        {
         std::cout << arg << std::endl;
        };
        // call the real functionB with the actual value in arg
        // if there is no T in arg, an exception is thrown!

        virtual void functionB(any arg)
        {
            real_functionB(any_cast<T>(arg));
        }
};

int main()
{
    Foo<int> f_int;
    IFoo &if_int=f_int;

    if_int.functionB(10);

    Foo<double> f_double;
    IFoo &if_double=f_double;
if_int.functionB(10.0);

}
_

残念ながら、_any_cast_は通常の変換を認識していません。たとえば、any_cast<double>(any(123))は、整数123をdoubleに変換しようとさえしないため、例外をスローします。とにかく変換​​のすべてを複製することは不可能なので、変換は気にしない。したがって、いくつかの制限がありますが、必要に応じて回避策を見つけることができます。

4

あなたが望むものを手に入れることはできないと思います。提案を実装する場合は、このことを考えてください。IFooインスタンスへのポインターがあり、functionB()を呼び出す場合、どの型パラメーターを指定する必要がありますか?根本的な問題はFoo1::functionBおよびFoo2::functionBは異なる署名を持ち、異なることを行います。

0
Karmastan

IFoo *ポインターをクラスでラップし、テンプレート化されていないラッパークラスの汎用テンプレート関数を介して機能を公開することで、同等の機能を実現できます。

#include <assert.h>

// interface class
class IFoo {
public:
    virtual int type() const = 0; // return an identifier for the template parameter
    virtual bool functionA() = 0;
};

// This function returns a unique identifier for each supported T
template <typename T> static int TypeT() { static_assert("not specialized yet"); }
template <> static int TypeT<bool>() { return 0; }
template <> static int TypeT<double>() { return 1; }
//template <> static int TypeT<...>() { ... }

// templated class
template <typename T> class FooT : public IFoo {
public:
    int type() const override { return TypeT<T>(); }

    bool functionA() override { return true; }

    // not in interface
    bool functionB(T arg) { return arg == T(); }
};

// function to create an instance of FooT (could also be static function in FooT)
static IFoo* CreateFooT(int type)
{
    switch (type)
    {
    case 0: return new FooT<bool>();
    case 1: return new FooT<double>();
    //case ...: return new FooT<...>();
    default: return nullptr;
    }
}


// Non-templated wrapper class
class FooWrapper {
private:
    IFoo *pFoo;
public:
    FooWrapper(int type) : pFoo(CreateFooT(type)) { assert(pFoo != nullptr); }
    ~FooWrapper() { delete pFoo; }

    bool functionA() { return pFoo->functionA(); }

    template <typename T> bool functionB(T arg)
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return false;
        }
        return static_cast<typename FooT<T>*>(pFoo)->functionB(arg);
    }



    // fun stuff:
    // (const pendants omitted for readability)

    bool changeType(int type)
    {
        delete pFoo;
        pFoo = CreateFooT(type);
        return pFoo != nullptr;
    }

    IFoo* Interface() { return pFoo; }

    IFoo* operator->() { return pFoo; }

    operator IFoo&() { return *pFoo; }

    template <typename T> FooT<T> *InterfaceT()
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return nullptr;
        }
        return static_cast<typename FooT<T>*>(pFoo);
    }
};

int main(int argc, char *argv[])
{
    FooWrapper w1(TypeT<bool>());
    FooWrapper w2(TypeT<double>());

    w1.functionA(); // ok
    w2.functionA(); // ok

    w1.functionB(true); // ok
    w1.functionB(0.5); // runtime error!

    w2.functionB(true); // runtime error!
    w2.functionB(0.5); // ok


    // fun stuff
    w2.changeType(TypeT<bool>()); // older changes will be lost
    w2.functionB(true); // -> now ok

    w1.Interface()->functionA();
    w1->functionA();

    IFoo &iref = w1;
    iref.functionA();

    FooT<bool> *ref = w1.InterfaceT<bool>();
    ref->functionB(true);

    return 0;
}

もちろん、正しい型で関数を呼び出すのはあなたの責任ですが、いくつかのエラー処理を簡単に追加できます。

0
Martin Hennings