2014-07-11 80 views
13

基本上我想要MyClass持有一個Hashmap,它將字段名稱(字符串)映射到任何類型的 值。爲此,我編寫了一個單獨的MyField類,其類型爲&價值信息..C++ std :: map保存任何類型的值

這是我到目前爲止有:

template <typename T> 
class MyField { 
    T m_Value; 
    int m_Size; 
} 


struct MyClass { 
    std::map<string, MyField> fields; //ERROR!!! 
} 

但你可以看到,地圖聲明失敗了,因爲我沒有爲MyField的類型參數...

所以我想它必須是某種東西像

std::map< string, MyField<int> > fields; 

std::map< string, MyField<double> > fields; 


但顯然這破壞了我的整個目的,因爲宣告地圖只能容納特定類型的MyField的..我想一個地圖,可容納任何類型的MyField分類..

有什麼辦法可以實現這個..?

+4

你需要某種類型的擦除。我建議'boost :: any'。 – chris

+0

你可以使用'std :: map >'。 –

+0

@sharth你有什麼理由不用簡單的(void *)來使用shared_ptr ? – user3794186

回答

16

Blindy的答案是很不錯的(+1),但只完成了答案:有另一種方式沒有圖書館這樣做,通過使用動態繼承:

class MyFieldInterface 
{ 
    int m_Size; // of course use appropriate access level in the real code... 
    ~MyFieldInterface() = default; 
} 

template <typename T> 
class MyField : public MyFieldInterface { 
    T m_Value; 
} 


struct MyClass { 
    std::map<string, MyFieldInterface* > fields; 
} 

優點:

  • 這是熟悉的任何C++程序員,
  • 它不強迫你使用升壓(在某些情況下,你不準);

缺點:

  • 你必須在堆上分配/自由存儲中的對象,並使用參考語義,而不是價值的語義來操縱他們。
  • 公開繼承暴露的方式可能會導致過度使用動態繼承,並且與您的類型相關的很多長期問題實際上過於相互依賴;
  • 指針的向量是有問題的,如果它必須自己的的對象,因爲你必須管理破壞;

因此,如果可以的話,使用boost :: any或boost :: variant作爲默認值,否則僅考慮此選項。

爲了解決這個問題最後利弊點你可以使用智能指針:

struct MyClass { 
    std::map<string, std::unique_ptr<MyFieldInterface> > fields; // or shared_ptr<> if you are sharing ownership 
} 

但是仍然有可能更成問題點:

它迫使你使用新創建的對象/刪除(或make_unique /共享)。這意味着實際的對象是在分配器提供的任何位置(大部分是默認的)在免費商店(堆)中創建的。因此,儘管經常列出對象的速度並不像因爲cache misses那樣快。

diagram of vector of polymorphic objects

如果你擔心通過這個列表循環往往儘可能快的性能(忽略下面如果沒有),那麼你最好使用任何的boost ::變種(如果您已經知道你將使用的所有具體類型)或者使用某種類型擦除的多態容器。

diagram of polymorphic container

的想法是,容器將管理相同類型的對象的數組,但仍然露出相同的接口。該接口可以是一個概念(使用鴨子鍵入技術)或動態接口(在我的第一個例子中是一個基類)。 其優點是容器將相同類型的對象保存在不同的向量中,因此通過它們很快。只從一種類型到另一種類型不是。

下面是一個例子(圖像是從那裏):http://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html

然而,這一技術失去它的興趣,如果你需要保持在該對象被插入的順序。

無論如何,有幾種可能的解決方案,這取決於您的需求。如果您對案例的經驗不足,我建議使用我在示例中首先解釋的簡單解決方案或boost :: any/variant。


爲補充這個答案,我想指出這總結,你可以使用所有的C++類型擦除技術非常好的博客文章,意見和優點/缺點:

15

使用boost::variant(如果您知道可以存儲的類型,它提供編譯時支持)或boost::any(對於真正的任何類型 - 但這種情況不太可能)。

http://www.boost.org/doc/libs/1_55_0/doc/html/variant/misc.html#variant.versus-any

編輯:我不能強調不夠,雖然滾動自己的解決方案看起來很酷,用一個完整的,正確的實施將會爲您節省大量的頭痛,從長遠來看。 boost::any實現RHS拷貝構造函數(C++ 11),安全(typeid())和不安全(啞投)值檢索,const corectness,RHS操作數以及指針和值類型。

一般情況下都是如此,但對於低級別的基礎類型,您可以構建整個應用程序。

5
class AnyBase 
{ 
public: 
    virtual ~AnyBase() = 0; 
}; 
inline AnyBase::~AnyBase() {} 

template<class T> 
class Any : public AnyBase 
{ 
public: 
    typedef T Type; 
    explicit Any(const Type& data) : data(data) {} 
    Any() {} 
    Type data; 
}; 

std::map<std::string, std::unique_ptr<AnyBase>> anymap; 
anymap["number"].reset(new Any<int>(5)); 
anymap["text"].reset(new Any<std::string>("5")); 

// throws std::bad_cast if not really Any<int> 
int value = dynamic_cast<Any<int>&>(*anymap["number"]).data; 
+0

你可以在基類中添加一個成員來獲取值並避免額外的強制轉換('(任何&)(* anymap [「number」]).data'看起來很可怕)。但是在那個時候你實現了一個基本的'boost :: any',不妨使用完整且經過嚴格測試的類。 +1雖然,創造是現貨! – Blindy

+0

我一定會試試這個!你可以向我解釋AnyBase的兩個析構函數嗎?我不太明白爲什麼你有兩個,爲什麼你需要他們..謝謝 – user3794186

+0

@ user3794186,只有一個desctructor,它是'虛擬的',因爲否則派生類型'desctructors不會被調用(所以'數據'析構函數不會被調用)。 – Blindy

0

您也可以使用void *並使用reinterpret_cast將該值轉換回正確的類型。它是C語言中常用的回調函數。

#include <iostream> 
#include <unordered_map> 
#include <string> 
#include <cstdint> // Needed for intptr_t 
using namespace std; 


enum TypeID { 
    TYPE_INT, 
    TYPE_CHAR_PTR, 
    TYPE_MYFIELD 
};  

struct MyField { 
    int typeId; 
    void * data; 
}; 

int main() { 

    std::unordered_map<std::string, MyField> map; 

    MyField anInt = {TYPE_INT, reinterpret_cast<void*>(42) }; 

    char cstr[] = "Jolly good"; 
    MyField aCString = { TYPE_CHAR_PTR, cstr }; 

    MyField aStruct = { TYPE_MYFIELD, &anInt }; 

    map.emplace("Int", anInt); 
    map.emplace("C String", aCString); 
    map.emplace("MyField" , aStruct ); 

    int   intval = static_cast<int>(reinterpret_cast<intptr_t>(map["Int"].data)); 
    const char *cstr2 = reinterpret_cast<const char *>(map["C String"].data); 
    MyField* myStruct = reinterpret_cast<MyField*>(map["MyField"].data); 

    cout << intval << '\n' 
     << cstr << '\n' 
     << myStruct->typeId << ": " << static_cast<int>(reinterpret_cast<intptr_t>(myStruct->data)) << endl; 
} 
+2

s /'reinterpret_cast' /'static_cast' / – chris

1

C++ 17有一個std::variant類型,它具有用於保存不同類型的工具,比聯合更好。

對於那些不在C++ 17上的人,boost::variant實現了相同的機制。

對於那些不使用升壓,https://github.com/mapbox/variant實現的variant更輕的版本,C++ 11和C++ 14看起來非常有前途,有據可查,重量輕,並有大量的應用實例。