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 实现,但使用不同的存储策略,我必须明确说明每个这样的实现:

cloneforDB()
cloneforDisk()

模板参数会简化这一点。谁能想到一种更清洁的方法来做到这一点?请专注于这个想法而不是例子,因为它显然是一个人为的例子。

4

3 回答 3

8

通常,如果您想使用虚拟模板方法,则意味着您的类层次结构设计中有问题。其高层原因如下。

模板参数必须在编译时知道,这就是它们的语义。它们用于保证代码的健全性。

虚函数用于多态性,即。运行时动态调度。

因此,您不能将静态属性与运行时调度混为一谈,纵观全局,这是没有意义的。

在这里,您将某些东西存储在某处的事实不应该成为您的方法类型的一部分,因为它只是一种行为特征,它可能会在运行时改变。因此,在方法类型中包含该信息是错误的。

这就是 C++ 不允许这样做的原因:您必须依靠多态性来实现这种行为。

一种简单的方法是将指向Storage对象的指针作为参数传递(如果您只想为每个类提供一个对象,则为单例),并在虚函数中使用该指针。

这样,您的类型签名不依赖于您的方法的特定行为。您可以在运行时更改存储(在此示例中)策略,这确实是您应该要求的一种良好做法。

有时,行为可以由模板参数(例如 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 回答