2012-02-01 84 views
5

接口:C++模板多態性障礙物

template <class T> 
class Interface{ 
    public: 
    typedef T Units; 
    virtual T get() = 0; 
}; 

Implementation1:

class Implementation1: public Interface<float> { 
    public: 

    float get() { 
     return 0.0f; 
    } 

}; 

Implementation2:

class Implementation2: public Interface<int> { 
    public: 

    int get() { 
     return 0; 
    } 

}; 

容器(有錯誤):

class Container{ 
    private: 
    Interface* floatGetter; 
    int n; 
    Timer::Units* array; 

    public: 
    Container(Interface* floatGetter, int n) { 
     this->floatGetter= floatGetter; 
     this->n = n; 
     array = new Timer::Units[n]; 
    } 

    ~Container() { 

    } 

}; 

有關更多詳細信息,我有一個模板接口和派生類從這個接口沒有模板。其他一些類採用派生類的對象,但它將該對象作爲接口(換句話說,依賴注入)。但是這個類中的接口類型是由接口實現定義的。如何在C++中實現這個想法?

EDIT1:

例子:

Interface<float> myInterface1 = new Implementation1(); 
Interface<int> myInterface2 = new Implementation2(); 
Container container1 = new Container(myInterface1, 10); 
Container container2 = new Container(myInterface2, 10); 

我需要從容器的實施瞭解到界面模板參數。

回答

6

好的,首先,這裏解釋一下這個問題。需要的是一個接口,它定義了一個虛擬方法,用於獲取帶有模板類型的值。由於我們想要的是一個接口,get方法必須是虛擬的。另一方面,我們希望能夠返回不同的類型,所以我們想要對它進行神聖化。然而,虛擬方法不能被神化,因爲編譯器不知道該方法的哪個實例包含在vtable中。

一個解決方案是完成問題所做的工作,即對接口類進行構造。模板類型的一個重要特性是同一類的不同實例是完全不同的類型。他們沒有共同的基礎,他們不能相互轉換。我們根本不能在正則函數中使用Interface<Generic>指針,並調用它們的get()方法。考慮一下:Interface模板類型的每個實例對get()方法都有不同的簽名。這意味着在調用該方法的同時,堆棧上不得不發生不同的事情。如果編譯器只有一個Interface<Generic>指針,編譯器如何知道要調用哪個版本的get()方法(如何爲函數調用準備堆棧)。

我可以想到兩個通用的解決方案。

  1. 刪除所有模板天書,使get()方法返回一個類型擦除的對象,如boost ::變種或升壓::任何。糾正我,如果我在這裏(*)錯了,但boost :: variant就像是一個聯盟,它記得聯盟的哪種類型被分配,而boost :: any像是一個void *,但它記得它指向的是什麼類型。此解決方案路徑意味着兩件事情: a)返回對象的類型將在運行時解析,並且在操作這些類型時會有一些開銷。 b)Interface的子類將不得不管理這些刪除類型的對象之一,這使得它們更加複雜。

  2. 將模板mumbo-jumbo放到極限位置,並始終在廟會環境中引用Interface對象,以便編譯器在這些上下文的實例化過程中生成正確的函數調用。我舉了一個例子,下面是這條路。該示例創建了一個容器,用於將不同類型的接口對象保持在一起,同時啓用神聖化功能的應用(將這種通稱爲「訪問者」的應用程序是否正確)。請注意,在該示例中,具有不同類型參數的Interface對象實際上保存在該容器類中的不同std :: lists中,因此在運行時不需要解析它們的類型。

免責聲明:以下是矯枉過正...

這裏是你如何能有不同的模板參數的「接口」模板類的容器。我用std :: list來保存實例,但是你可以改變它。

#include<boost/fusion/container/vector.hpp> 
#include<boost/fusion/algorithm.hpp> 
#include<boost/mpl/transform.hpp> 
#include<boost/mpl/contains.hpp> 
#include<boost/utility/enable_if.hpp> 
#include<boost/type_traits/add_reference.hpp> 
#include<list> 
#include<algorithm> 
#include <iostream> 

using namespace boost; 

template <class T> 
class Interface{ 
    public: 
    typedef T Units; 
    virtual T get() = 0; 
}; 

class Implementation1: public Interface<float> { 
    public: 

    float get() { 
     return 0.0f; 
    } 

}; 

class Implementation2: public Interface<int> { 
    public: 

    int get() { 
     return 5; 
    } 

}; 

template<class element> 
struct to_list { 
    typedef std::list<Interface<element> *> type; 
}; 

template<class elementVector> 
struct to_containers { 
    typedef typename mpl::transform<elementVector,to_list<mpl::_1> >::type type; 
}; 

class Container{ 
    typedef fusion::vector<int,float> AllowedTypes; 
    typename to_containers<AllowedTypes>::type containers; 

public: 
    template<class type> typename enable_if<mpl::contains<AllowedTypes,type>,void>::type 
    /*void*/ add(Interface< type/*included in AllowedTypes*/ > & floatGetter) { 
     fusion::deref(fusion::find<typename to_list<type>::type >(containers)) 
      /*<type> container*/.push_back(&floatGetter); 
    } 

    template<class functional> 
    void apply(functional f) { 
     fusion::for_each(containers,applyFunctional<functional>(f)); 
    } 

private: 
    template<class functional> 
    struct applyFunctional { 
     functional f; 
     applyFunctional(functional f): f(f){} 
     template<class T> void operator()(T & in) const { 
      std::for_each(in.begin(), in.end(),f); 
     } 
    }; 

}; 

struct printValueFunctional { 
    template<class element> 
    void operator()(Interface<element> * in) const { 
     std::cout<<"Hi, my value is:"<<in->get()<<"\n"; 
    } 
}; 

int main() { 

    Implementation1 impl1; 
    Implementation2 impl2; 
    Interface<float> &myInterface1 = impl1; 
    Interface<int> &myInterface2 = impl2; 
    Container container; 
    container.add(myInterface1); 
    container.add(myInterface2); 
    container.apply(printValueFunctional()); 
    return 0; 
} 

,輸出是:

Hi, my value is:5 
Hi, my value is:0 

好了,這確實是大多數應用程序的巨大矯枉過正,但你問它:)

如果你只是想一個接口,這可以返回不同的東西,你也可以考慮boost.variant。上面的例子對於它使用的所有靜態多態都是有價值的。

編輯:大衛已經指出了一些重要的事情,這可能是一個陷阱,如果你,由於某種原因,假設不然。這個容器並不真正保持項目插入的順序。您的功能調用的順序可能不會按照項目的插入次序發生,即,假設迭代將以「隨機」順序進行。

(*)的boost ::變種和boost ::任何討論here

+1

對於一段精細的程序設計而言+1。我不認爲這是一個很好的解決方案,但它值得代表:) – 2012-02-01 20:37:57

+0

謝謝:)我不認爲這是一個很好的解決方案,一般來說,但它只是表明模板元編程允許這沒有類型擦除。您還可以獲得迭代速度非常快的混合容器。 – enobayram 2012-02-01 21:23:02

+0

它不是一個真正的混合容器(或者它?)...而是一個在內部容納多個容器的類型。對我來說,區別在於不同的類型在內部仍然是分開的,即使您有印象,它們不是,這意味着在使用類型擦除時,您可以維護容器不變量(例如,插入順序在序列容器),你可以做不一樣的這種方法(說實話,這只是一種預感,我已經閱讀過的代碼,但還沒有編譯/試過) – 2012-02-01 21:30:37

4

Interface是一個模板,而不是一個類型。在類的變量應該是與特定類型的模板實例,如:

class Container { 
    Interface<float> *floatGetter; 

同樣地,對於參數的構造函數。

注意:你的析構函數應該釋放你的類處理的資源。注意事項2:編寫直接管理多個資源的類型非常困難,請考慮使用智能指針來保存數據。

備註3:學習和使用初始化列表。

+0

你* *構造函數應該釋放的資源? – 2012-02-01 01:20:01

+0

@jesse感謝您的錯誤...當然,析構函數應該釋放資源,而不是構造函數。 – 2012-02-01 01:22:30

+0

尋找更新,請 – itun 2012-02-01 01:27:43