web-dev-qa-db-ja.com

C ++複数のタイプのテンプレートクラスを含む1つのstd :: vector

単一のベクターに複数のタイプのテンプレートクラスを保存する必要があります。

例:

template <typename T>
class templateClass{
     bool someFunction();
};

次のすべてを保存する1つのベクトルが必要です。

templateClass<int> t1;
templateClass<char> t2;
templateClass<std::string> t3;
etc

私の知る限り、これは不可能です。誰かがどのように言うことができますか?

それが不可能な場合、誰かが次の作業を行う方法を説明できますか?

回避策として、ベースの非テンプレートクラスを使用し、そこからテンプレートクラスを継承しようとしました。

 class templateInterface{
     virtual bool someFunction() = 0;
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction();
 };

次に、ベースの "templateInterface"クラスを格納するベクターを作成しました。

std::vector<templateInterface> v;
templateClass<int> t;
v.Push_back(t);

これにより、次のエラーが発生しました。

error: cannot allocate an object of abstract type 'templateInterface'
note: because the following virtual functions are pure within 'templateInterface'
note: virtual bool templateInterface::someFunction()

このエラーを修正するために、関数本体を提供することによりtemplateInterfaceの関数を純粋な仮想ではなく、コンパイルしましたが、関数を呼び出すときにオーバーライドは使用されず、代わりに仮想関数の本体を使用しました。

例えば:

 class templateInterface{
     virtual bool someFunction() {return true;}
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction() {return false;}
 };

 std::vector<templateInterface> v;
 templateClass<int> i;
 v.Push_back(i);
 v[0].someFunction(); //This returns true, and does not use the code in the 'templateClass' function body

オーバーライドされた関数が使用されるようにこれを修正する方法はありますか、または単一のベクトルに複数のテンプレートタイプを保存する別の回避策がありますか?

40
jtedit

コードが機能しない理由:

valueで仮想関数を呼び出しても、ポリモーフィズムは使用されません。これは、ランタイムタイプではなく、コンパイラから見たこの正確なシンボルのタイプに対して定義されている関数を呼び出します。サブタイプをベースタイプのベクトルに挿入すると、値はベースタイプにconvertedになります(「タイプスライシング」)が、これは望ましくありません。それらの関数を呼び出すと、ベースタイプに対して定義された関数が呼び出されるようになりました。そのタイプのisではないためです。

これを修正する方法は?

同じ問題は、このコードスニペットで再現できます。

templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

多態性は、pointerまたはreferenceタイプでのみ機能します。次に、ポインター/参照の背後にあるオブジェクトのruntime typeを使用して、(vtableを使用して)呼び出す実装を決定します。

ポインタの変換は、タイプのスライシングに関して完全に「安全」です。 actualの値はまったく変換されず、ポリモーフィズムは期待どおりに機能します。

上記のコードスニペットに類似した例:

templateInterface *x = new templateClass<int>();  // No type slicing takes place
x->someFunction();  // -> templateClass<int>::someFunction() is called!

delete x;  // Don't forget to destroy your objects.

ベクターはどうですか?

そのため、これらの変更をコードで採用する必要があります。値を直接保存する代わりに、ベクターの実際の型にpointersを保存するだけです。

ポインタを使用する場合、割り当てられたオブジェクトの削除にも注意する必要があります。このために、スマートポインターを使用して、削除を自動的に処理できます。 unique_ptrは、そのようなスマートポインタータイプの1つです。スコープ(「固有の所有権」-スコープが所有者である)から外れると、ポイント先を削除します。オブジェクトの存続期間がスコープにバインドされていると仮定すると、これは使用すべきものです。

std::vector<std::unique_ptr<templateInterface>> v;

templateClass<int> *i = new templateClass<int>();    // create new object
v.Push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector

v.emplace_back(new templateClass<int>());   // "direct" alternative

次に、これらの要素のいずれかで、次の構文を使用して仮想関数を呼び出します。

v[0]->someFunction();

すべての関数virtualを作成し、サブクラスでオーバーライドできるようにしてください。そうでない場合、オーバーライドされたバージョンは呼び出されません。しかし、すでに「インターフェース」を導入しているので、抽象的な機能を使用していると確信しています。

代替アプローチ:

あなたが望むことをする別の方法は、ベクトルでvariantタイプを使用することです。バリアント型の実装がいくつかあります。 Boost.Variant は非常に人気のあるものです。型の階層がない場合(プリミティブ型を格納する場合など)、このアプローチは特に便利です。次に、std::vector<boost::variant<int, char, bool>>のようなベクトル型を使用します

28
leemes

多態性は、ポインタまたは参照を介してのみ機能します。非テンプレートベースが必要になります。さらに、コンテナ内の実際のオブジェクトがどこに存在するかを決定する必要があります。それらがすべて静的オブジェクト(十分な存続期間)である場合、_std::vector<TemplateInterface*>_を使用し、v.Push_back(&t1);などで挿入するだけでうまくいくはずです。そうでない場合は、おそらくクローンをサポートし、ベクター内にクローンを保持する必要があります。できればBoostポインターコンテナーを使用しますが、_std::shared_ptr_も使用できます。

2
James Kanze

これまでのソリューションは問題ありませんが、例でbool以外のテンプレートタイプを返す場合、vtableスロットを事前に測定できないため、これらはいずれも役に立ちません。実際には、デザインの観点から、テンプレート指向のポリモーフィックソリューションの使用には制限があります。

2

複数の型を格納するコンテナを探している場合、人気のあるBoostライブラリの boost variant を調べる必要があります。

1
jbo5112

ソリューション番号1

このソリューションは、Sean ParentのC++ Seasoningトークに触発されました。 YouTubeで確認することを強くお勧めします。私のソリューションは少し簡略化され、キーはメソッド自体にオブジェクトを保存することです。

1つの方法のみ

格納されたオブジェクトのメソッドを呼び出すクラスを作成します。

struct object {
    template <class T>
    object(T t)
    : someFunction([t = std::move(t)]() { return t.someFunction(); })
    { }

    std::function<bool()> someFunction;
};

次に、このように使用します

std::vector<object> v;

// Add classes that has 'bool someFunction()' method
v.emplace_back(someClass());
v.emplace_back(someOtherClass());

// Test our vector
for (auto& x : v)
    std::cout << x.someFunction() << std::endl;

いくつかの方法

いくつかのメソッドでは、共有ポインタを使用してメソッド間でオブジェクトを共有します

struct object {
    template <class T>
    object(T&& t) {
        auto ptr = std::make_shared<std::remove_reference_t<T>>(std::forward<T>(t));
        someFunction = [ptr]() { return ptr->someFunction(); };
        someOtherFunction = [ptr](int x) { ptr->someOtherFunction(x); };
    }

    std::function<bool()> someFunction;
    std::function<void(int)> someOtherFunction;
};

その他の種類

プリミティブ型(intfloatconst char*など)またはクラス(std::stringなど)は、objectクラスと同じ方法でラップできますが、振る舞いが異なります。例えば:

struct otherType {
    template <class T>
    otherType(T t)
    : someFunction([t = std::move(t)]() {
            // Return something different
            return true;
        })
    { }

    std::function<bool()> someFunction;
};

そのため、someFunctionメソッドを持たない型を追加できるようになりました。

v.emplace_back(otherType(17));      // Adding an int
v.emplace_back(otherType("test"));  // A string

ソリューション番号2

最初のソリューションで基本的に行ったことをいくつか考えた後、呼び出し可能な関数の配列が作成されます。それでは、代わりに次のことをしないでください。

// Example class with method we want to put in array
struct myclass {
    void draw() const {
        std::cout << "myclass" << std::endl;
    }
};

// All other type's behaviour
template <class T>
void draw(const T& x) {
    std::cout << typeid(T).name() << ": " << x << std::endl;
}

int main()
{
    myclass x;
    int y = 17;

    std::vector<std::function<void()>> v;

    v.emplace_back(std::bind(&myclass::draw, &x));
    v.emplace_back(std::bind(draw<int>, y));

    for (auto& fn : v)
        fn();
}

結論

ソリューション番号1は間違いなく、継承も仮想関数も必要としない興味深い方法です。また、後で使用するテンプレート引数を保存する必要がある他の要素にも使用できます。

ソリューション番号一方、2は、よりシンプルで柔軟性があり、おそらくこちらの方が良い選択です。

0