5

テンプレート化された仮想メンバー関数は C++ ではサポートされていませんが、理想的なシナリオがあります。誰かがこれを達成する方法についてアイデアを持っているかどうか疑問に思っています。

#include <iostream>


class Foo {
public:
    virtual void bar(int ){}
    // make a clone of my existing data, but with a different policy
    virtual Foo* cloneforDB() = 0;
};


struct DiskStorage {
    static void store(int x) { std::cout << "DiskStorage:" << x << "\n"; }
};

struct DBStorage {
    static void store(int x) { std::cout << "DBStorage:" << x << "\n"; }
};

template<typename Storage>
class FooImpl : public Foo {
public:
    FooImpl():m_value(0) {}
    template<typename DiffStorage>
    FooImpl(const FooImpl<DiffStorage>& copyfrom) {
        m_value = copyfrom.m_value;
    }
    virtual void bar(int x) {
        Storage::store(m_value);
        std::cout << "FooImpl::bar new value:" << x << "\n";
        m_value = x;
    }
    virtual Foo* cloneforDB() {
        FooImpl<DBStorage> * newfoo = new FooImpl<DBStorage>(*this);
        return newfoo;
    }
    int m_value;
};

int main()
{
    Foo* foo1 = new FooImpl<DiskStorage>();
    foo1->bar(5);
    Foo* foo2 = foo1->cloneforDB();
    foo2->bar(21);
}

ここで、Foo 実装を複製したいが、別の Storagepolicy を使用する場合は、そのような各実装を明示的に記述する必要があります。

cloneforDB()
cloneforDisk()

テンプレート パラメーターを使用すると、それが簡単になります。誰でもこれを行うためのよりクリーンな方法を考えることができますか? 明らかに不自然な例であるため、例ではなくアイデアに注目してください。

4

3 に答える 3

8

通常、仮想テンプレート メソッドを使用する場合は、クラス階層の設計に何か問題があることを意味します。その大まかな理由は次のとおりです。

テンプレート パラメーターは、コンパイル時に認識されている必要があります。これがセマンティクスです。これらは、コードの健全性を保証するために使用されます。

仮想関数はポリモーフィズムに使用されます。実行時の動的ディスパッチ。

したがって、静的プロパティとランタイム ディスパッチを混在させることはできません。全体像を見ると意味がありません。

ここで、何かをどこかに保存するという事実は、メソッドのの一部であってはなりません。これは単なる動作特性であり、実行時に変更される可能性があるためです。そのため、その情報をメソッドの型に含めるのは間違っています。

そのため、C++ ではそれが許可されていません。このような動作を実現するには、ポリモーフィズムに依存する必要があります。

簡単な方法の 1 つは、Storageオブジェクトへのポインターを引数 (クラスごとに 1 つのオブジェクトだけが必要な場合はシングルトン) として渡し、仮想関数でそのポインターを操作することです。

そうすれば、型シグネチャはメソッドの特定の動作に依存しません。また、ストレージ (この例では) ポリシーを実行時に変更することもできます。これは、グッド プラクティスとして実際に求めるべきものです。

テンプレート パラメーター (Alexandrescu のポリシー テンプレート パラメーターなど) によって動作を指定できる場合もありますが、それはメソッド レベルではなく、タイプ レベルです。

于 2012-12-20T16:50:31.303 に答える
2

この問題を回避するために私が時々使用するトリックは次のとおりです。

template<typename T>
using retval = std::vector<T const*>;
struct Bob {};

// template type interface in Base:
struct Base {
  template<typename T>
  retval<T> DoStuff();

  virtual ~Base() {};

// Virtual dispatch so children can implement it:
protected:
  virtual retval<int> DoIntStuff() = 0;
  virtual retval<double> DoDoubleStuff() = 0;
  virtual retval<char> DoCharStuff() = 0;
  virtual retval<Bob> DoBobStuff() = 0;
};

// forward template interface through the virtual dispatch functions:
template<> retval<int> Base::DoStuff<int>() { return DoIntStuff(); }
template<> retval<double> Base::DoStuff<double>() { return DoDoubleStuff(); }
template<> retval<char> Base::DoStuff<char>() { return DoCharStuff(); }
template<> retval<Bob> Base::DoStuff<Bob>() { return DoBobStuff(); }

// CRTP helper so the virtual functions are implemented in a template:
template<typename Child>
struct BaseHelper: public Base {
private:
  // In a real project, ensuring that Child is a child type of Base should be done
  // at compile time:
  Child* self() { return static_cast<Child*>(this); }
  Child const* self() const { return static_cast<Child const*>(this); }
public:
  virtual retval<int> DoIntStuff() override final { self()->DoStuff<int>(); }
  virtual retval<double> DoDoubleStuff() override final { self()->DoStuff<double>(); }
  virtual retval<char> DoCharStuff() override final { self()->DoStuff<char>(); }
  virtual retval<Bob> DoBobStuff() override final { self()->DoStuff<Bob>(); }
};

// Warning: if the T in BaseHelper<T> doesn't have a DoStuff, infinite
// recursion results.  Code and be written to catch this at compile time,
// and I would if this where a real project.
struct FinalBase: BaseHelper<FinalBase> {
  template<typename T>
  retval<T> DoStuff() {
    retval<T> ret;
    return ret;
  }
};

テンプレートベースのディスパッチから仮想関数のディスパッチへ、そしてテンプレートベースのディスパッチへと戻ります。

インターフェイスは、ディスパッチしたいタイプでテンプレート化されています。このような型の有限セットは、仮想ディスパッチ システムを介して転送され、コンパイル時に実装内の単一のメソッドに再ディスパッチされます。

これが面倒なことは認めますが、「このテンプレートを仮想にしたいのですが、次のタイプのみを使用してください」と言えると便利です。

これが便利な理由は、これらのメソッドで一様に動作する型に依存しないテンプレート グルー コードを記述できるためです。メソッドへのポインターのパス スルーなどを実行したり、どのメソッドを抽出する型特性バンドルを記述したりする必要はありません。電話する。

于 2012-12-20T18:03:10.753 に答える
2

すべての方法でテンプレートを使用するだけです:

class Foo {
public:
    virtual void bar(int ){}

    template <class TargetType>
    Foo* clonefor() const;
};

class FooImpl { ... };

template 
inline <class TargetType>
Foo* Foo::clonefor() const
{
    return new FooImpl<TargetType>(*this);
}

今それを呼び出します:

int main()
{
    Foo* foo1 = new FooImpl<DiskStorage>();
    foo1->bar(5);
    Foo* foo2 = foo1->clonefor<DBStorage>();
    foo2->bar(21);
}
于 2012-12-20T17:03:41.590 に答える