2010-02-04 59 views
28

我有這種類的結構。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); 

出了什麼問題?模板不是多態嗎?

+0

模板不是多態的。不像在運行時綁定的多態對象,模板在編譯時綁定。 – 2010-02-04 22:12:43

+0

相關問題:http://stackoverflow.com/questions/1289167/template-polymorphism-not-working http://stackoverflow.com/questions/639248/c-covariant-templates – 2010-02-05 10:41:30

回答

38

我想確切的術語因爲你需要的是「模板協方差」,這意味着如果B繼承自A,那麼T<B>繼承自T<A>。在C++中情況並非如此,Java和C#泛型也是如此。

有一個很好的理由來避免模板協方差:這將簡單地刪除模板類中的所有類型安全。讓我用下面的例子說明一下:

//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! 
} 

因此,你應該考慮T<A>T<B>作爲完全無關的類型,無論A和B

之間的關係

那麼你怎麼能解決您遇到的問題」重新面對?在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有一個Concept Check library可能會讓你感興趣。

儘管如此,對於您遇到的問題,概念可能有點矯枉過正,但使用@gf建議的簡單靜態斷言可能是最佳解決方案。

*更新:自.NET Framework 4以來,有可能標記通用參數爲covariant or contravariant

5

不是。想象一下,容器參數被「硬編碼」到它定義的類中(實際上它是如何工作的)。因此,容器類型爲Container_Foo,與Container_Interface不兼容。

但是什麼你可以嘗試是這樣的:

template<class T> 
Bar(const Container<T> & bar){ 
... 
} 

然而,你鬆直下式檢查方式。

其實STL的方式(可能是更有效和通用)是做

template<class InputIterator> 
Bar(InputIterator begin, InputIterator end){ 
... 
} 

...但我認爲你沒有在容器中實現迭代器。

+0

這是非常傷心的。謝謝你的建議。我不喜歡這個解決方案,但恐怕只剩下了。 – 2010-02-04 21:45:02

+0

你假設沒錯。我不需要那種特殊的方式。坦率地說,不知道如何實施它們,現在沒有時間去學習它。 – 2010-02-04 21:58:00

11

這裏有兩個問題:默認結構的形式有MyClass c;;帶圓括號,它看起來像編譯器的函數聲明。

的另一個問題是,Container<Interface>簡直是一個不同的類型,然後Container<Foo> - 你可以做到以下幾點,而不是真正得到多態性

Bar::Bar(const Container<Interface*>&) {} 

Container<Interface*> container; 
container.push_back(new Foo); 
Bar* temp = new Bar(container); 

或者你當然可以使Bar或它的構造模板,科內爾已經表明。

如果你確實需要一些類型安全的編譯時多態,你可以使用Boost.TypeTraitsis_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 
} 
+0

謝謝。我會嘗試。 – 2010-02-04 21:53:16

+0

+1:來認爲這將是一個更好的解決方案。 – 2010-02-04 21:56:02

+0

這真的很好,正是我需要的。我不需要改變很多已經實現的代碼。再次感謝。 – 2010-02-04 22:02:37

-1

容器是美孚的容器對象未接口對象的容器

它也不能是多態的,指向事物的指針可以是,但不是對象themselvs。多大會在容器中的插槽都爲容器,如果你可以把從接口派生的任何東西在裏面

你需要

container<Interface*> 

或更好

container<shared_ptr<Interface> > 
+1

@ pm100,shared_ptr ?你有沒有使用過shared_ptr? – 2010-02-04 21:43:35

+1

@ kornel可能是一個錯字... – msi 2010-02-04 21:44:15

+0

@msiemeri - 是的,但請注意,答案的其餘部分假設容器存儲值,而它可能實際上一直存儲Interface * – 2010-02-04 21:45:58

2

可以爲容器創建繼承樹,以反映數據的繼承樹。如果你有以下數據:

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); } 
}; 

這是不是最好的執行代碼;這只是一個想法。這種方法唯一的問題是每次添加一個新的Data類時,你還必須創建一個新的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); 
} 
2

我提出了以下解決方法,它使用了模板函數。雖然這個例子使用Qt的QList,但是沒有什麼能夠阻止這個解決方案直接轉換到任何其他容器。

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

優點:

  • 一般
  • 類型安全
  • 相當有效,如果項目是指針或一些其他廉價地拷貝構造元素(如隱式共享Qt類)

缺點:

  • 需要創建一個新的容器,而不是使原始容器的重用
  • 意味着一些內存和處理器開銷都創建和填充新的容器,這在很大程度上取決於副本的成本-constructor