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> >