C++ Templated Virtual Function
C++中不支持模板化虚拟成员函数,但我有一个理想的场景。我想知道是否有人有办法做到这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #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 <<" "; } }; struct DBStorage { static void store(int x) { std::cout <<"DBStorage:" << x <<" "; } }; 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 <<" "; 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实现,但使用不同的存储策略,我必须明确说明每种实现:
1 2 | cloneforDB() cloneforDisk() |
模板参数可以简化这个过程。有人能想出一个更干净的方法来做到这一点吗?请关注这个想法而不是例子,因为它显然是一个做作的例子。
通常,如果您想使用虚拟模板方法,这意味着类层次结构的设计有问题。高层次的原因如下。
模板参数必须在编译时知道,这就是它们的语义。它们用于保证代码的可靠性属性。
虚拟函数用于多态性,即运行时的动态调度。
因此,您不能将静态属性与运行时调度混合使用,如果您着眼于全局,这是没有意义的。
在这里,您在某个地方存储东西的事实不应该是方法类型的一部分,因为它只是一个行为特征,它可以在运行时改变。所以在方法的类型中包含这些信息是错误的。
这就是为什么C++不允许这样做:你必须依靠多态来实现这样的行为。
一个简单的方法是将指向
这样,类型签名就不依赖于方法的特定行为。并且您可以在运行时更改存储(在本例中)策略,这实际上是作为一个好的实践,您应该要求的。
有时,行为可以由模板参数(例如,Alexandrescu的策略模板参数)决定,但它在类型级别,而不是方法级别。
一直使用模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } |
现在称之为:
1 2 3 4 5 6 7 | int main() { Foo* foo1 = new FooImpl<DiskStorage>(); foo1->bar(5); Foo* foo2 = foo1->clonefor<DBStorage>(); foo2->bar(21); } |
我有时会用这样一个技巧来解决这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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; } }; |
在这里,我从基于模板的调度转到虚拟功能调度,再回到基于模板的调度。
接口是在我要分派的类型上模板化的。这种类型的有限集合通过虚拟调度系统转发,然后在编译时重新分配到实现中的单个方法。
我承认这很烦人,并且能够说"我希望这个模板是虚拟的,但只有以下类型"是很好的。
这种方法之所以有用,是因为它允许您编写类型不可知的模板粘合代码,这些代码统一地在这些方法上操作,而不必执行诸如传递方法指针之类的操作,或者编写类型特征包来提取要调用的方法。