C ++模板多态性
我有这个类的结构。
class Interface{ ... } class Foo : public Interface{ ... } template <class T> class Container{ ... }
我有一些其他类Bar的构造函数。
Bar(const Container<Interface> & bar){ ... }
当我这样调用构造函数时,我得到“没有匹配函数”的错误。
Container<Foo> container (); Bar * temp = new Bar(container);
哪里不对? 模板不是多态的吗?
我认为你所需要的确切术语是“模板协方差”,这意味着如果B从Ainheritance,那么T<B>
从T<A>
inheritance。 在C ++中情况并非如此,Java和C#generics也是如此。
有一个很好的理由来避免模板协方差:这将简单地删除模板类中的所有types安全。 让我用下面的例子来解释一下:
//Assume the following class hierarchy class Fruit {...}; class Apple : public Fruit {...}; class Orange : public Fruit {...}; //Now I will use these types to instantiate a class template, namely std::vector int main() { std::vector<Apple> apple_vec; apple_vec.push_back(Apple()); //no problem here //If templates were covariant, the following would be legal std::vector<Fruit> & fruit_vec = apple_vec; //push_back would expect a Fruit, so I could pass it an Orange fruit_vec.push_back(Orange()); //Oh no! I just added an orange in my apple basket! }
因此,无论A和B之间的关系如何,都应该把T<A>
和T<B>
看作完全不相关的types。
那么你怎么能解决你所面临的问题呢? 在Java和C#中,可以分别使用有界的通配符和约束 :
//Java code Bar(Container<? extends Interface) {...} //C# code Bar<T>(Container<T> container) where T : Interface {...}
下一个C ++标准(称为C ++ 1x(以前称为C ++ 0x))最初包含了一个更强大的机制 – Concepts ,它可以让开发人员对模板参数强制实施语法和/或语义要求,但不幸的是推迟到以后的日子 不过,Boost有一个概念检查库 ,可能会让你感兴趣。
不过,对于你遇到的问题,概念可能有点矫枉过正,使用@gf提出的简单的静态断言可能是最好的解决scheme。
*更新:自.NET Framework 4以来,可以标记通用参数是协变还是逆变 。
这里有两个问题:默认的结构有MyClass c;
的formsMyClass c;
; 用圆括号看起来像编译器的函数声明。
另一个问题是, Container<Interface>
只是一个不同的types,然后Container<Foo>
– 你可以做,而不是实际获得多态:
Bar::Bar(const Container<Interface*>&) {} Container<Interface*> container; container.push_back(new Foo); Bar* temp = new Bar(container);
或者当然,你可以让Bar
或者它的构造函数成为Kornel所展示的模板。
如果你真的想要一些types安全的编译时多态性,你可以使用Boost.TypeTraits is_base_of或者一些等价的:
template<class T> Bar::Bar(const Container<T>& c) { BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value)); // ... will give a compile time error if T doesn't // inherit from Interface }
不。想象一下,容器参数被“硬编码”到它所定义的类中(实际上它是如何工作的)。 因此,容器types是Container_Foo
,与Container_Interface
不兼容。
你可能会尝试的是这样的:
template<class T> Bar(const Container<T> & bar){ ... }
然而,你松散的直接types检查方式。
其实STL的方式(可能更有效和通用)将是
template<class InputIterator> Bar(InputIterator begin, InputIterator end){ ... }
…但我假设你没有在容器中实现迭代器。
可以为容器创buildinheritance树,反映数据的inheritance树。 如果您有以下数据:
class Interface { public: virtual ~Interface() {} virtual void print() = 0; }; class Number : public Interface { public: Number(int value) : x( value ) {} int get() const { return x; } void print() { std::printf( "%d\n", get() ); }; private: int x; }; class String : public Interface { public: String(const std::string & value) : x( value ) {} const std::string &get() const { return x; } void print() { std::printf( "%s\n", get().c_str() ); } private: std::string x; };
你也可以有以下容器:
class GenericContainer { public: GenericContainer() {} ~GenericContainer() { v.clear(); } virtual void add(Interface &obj) { v.push_back( &obj ); } Interface &get(unsigned int i) { return *v[ i ]; } unsigned int size() const { return v.size(); } private: std::vector<Interface *> v; }; class NumericContainer : public GenericContainer { public: virtual void add(Number &obj) { GenericContainer::add( obj ); } Number &get(unsigned int i) { return (Number &) GenericContainer::get( i ); } }; class TextContainer : public GenericContainer { public: virtual void add(String &obj) { GenericContainer::add( obj ); } String &get(unsigned int i) { return (String &) GenericContainer::get( i ); } };
这不是performance最好的代码; 这只是一个想法。 这种方法唯一的问题是每次添加一个新的Data类时,你也必须创build一个新的Container。 除此之外,你有多态性“再次工作”。 你可以是特定的或一般的:
void print(GenericContainer & x) { for(unsigned int i = 0; i < x.size(); ++i) { x.get( i ).print(); } } void printNumbers(NumericContainer & x) { for(unsigned int i = 0; i < x.size(); ++i) { printf( "Number: " ); x.get( i ).print(); } } int main() { TextContainer strContainer; NumericContainer numContainer; Number n( 345 ); String s( "Hello" ); numContainer.add( n ); strContainer.add( s ); print( strContainer ); print( numContainer ); printNumbers( numContainer ); }
我提出了下面的解决方法,它使用了一个模板函数。 虽然这个例子使用Qt的QList,但是没有什么能够阻止这个解决scheme直接转换到任何其他的容器上。
template <class D, class B> // D (Derived) inherits from B (Base) QList<B> toBaseList(QList<D> derivedList) { QList<B> baseList; for (int i = 0; i < derivedList.size(); ++i) { baseList.append(derivedList[i]); } return baseList; }
优点:
- 一般
- types安全
- 如果这些项是指针或其他廉价的可复制构件(如隐式共享的Qt类)
缺点:
- 需要创build一个新的容器,而不是重新使用原来的容器
- 意味着一些内存和处理器开销都会创build和填充新的容器,这在很大程度上取决于复制构造函数的成本
容器是Foo对象的容器而不是Interface对象的容器
而且它也不可能是多态的,指向事物的可以是,但不是它们的对象。 如果你可以把任何从接口派生出来的东西放在容器中的话,容器中的槽将有多大的容量
你需要
container<Interface*>
或更好
container<shared_ptr<Interface> >