2013-02-07 18 views
0

我有對象「的標識符」的列表導出(長枚舉列表,每個「識別符」的唯一值):C++保持指針到模板對象的集合,所有來自非模板

enum Identifier { 
    Enum0, // an identifier for a bool value 
    Enum1, // ... for a float value 
    Enum2, // ... for an int value 
    // etc. 
}; 

我希望維護與這些標識符關聯的Value對象的集合。 hese Value對象包含單個值,但此值可能是整數,浮點數,布爾值或其他(簡單)類型。這是在管理系統中的一組配置值的情況下。稍後,我計劃擴展這些值類型以支持內部值的驗證,並將一些值與其他值相關聯。

但是我希望爲這些Value類使用模板,因爲我想一般地在這些值上編寫操作。如果我要使用繼承,我會有BaseValue,然後從BaseValue派生IntValue,FloatValue等。相反,我有價值,價值等

但我也想存儲一個訪問機制,每個這些值在一個集合。我想讓一個類實例化它們並將它們保存在集合中。如果我使用繼承,我可以使用指向BaseValue的向量。但是因爲我使用的是模板,所以這些類彼此之間沒有多態關係。

所以我想到了讓他們基於一個(空?)抽象基類,它是參數化:

class BaseParameter { 
}; 

template<typename T> 
class Parameter : public BaseParameter { 
public: 
    explicit Parameter(T val) : val_(val) {} 
    void set(ParameterSource src) { val_ = extract<T>(src); } 
    T get() { return val_; }; 
private: 
    T val_; 
}; 

注意「設置」成員函數需要一個「ParameterSource」,這是由特定的「to_type」函數「重新解釋」的值的來源。這是一個不受我控制的API函數 - 我必須自己解釋這個類型,因爲我知道類型的意思,下面設置。這就是提取所做的 - 它專用於各種T類型,如float,int,bool。

然後,我可以將它們添加到一個std ::矢量這樣的:

std::vector<BaseParameter *> vec(10); 
vec[Enum0] = new Parameter<bool>(true); // this is where I state that it's a 'bool' 
vec[Enum1] = new Parameter<float>(0.5); // ... or a float ... 
vec[Enum2] = new Parameter<int>(42);  // ... or an int ... 

我知道,我也許應該使用的unique_ptr但現在我只是試圖讓這個工作。到目前爲止,這似乎工作正常。但是我很謹慎,因爲我不確定實例化模板的完整類型是否會在運行時保留。

通過任意的枚舉值後來我要編制索引的「VEC」,檢索參數並調用關於它的成員函數:其利用這些配置值的

void set_via_source(Identifier id, ParameterSource source) { 
    // if id is in range... 
    vec[id]->set(source); 
} 

和其他代碼(因此知道類型)可以訪問他們:

int foo = vec[Enum2]->get() * 7; 

這似乎工作,大部分時間。它編譯。我遇到了一些我無法解釋的奇怪的崩潰,這也導致調試器崩潰。但我對此非常懷疑,因爲我不知道C++是否能夠確定指向對象的實際類型(包括參數化類型),因爲基類本身沒有參數化。

不幸的是,在我看來,如果我參數化基類,那麼我基本上刪除了這些Value類之間的通用性,這些類允許它們存儲在一個容器中。

我看了一下boost :: any,看看是否有幫助,但我不確定它適用於這種情況。

在更高層次上,我試圖做的是連接來自外部源(通過API)的大量配置項目集合,該配置項目根據項目提供不同類型的值,並將其存儲在本地,以便我的其他代碼可以很容易地訪問它們,就好像它們是簡單的數據成員一樣。我也想避免寫一個巨大的開關語句(因爲這會起作用)。

Type Erasure可以幫助我嗎?

+0

我甚至不明白爲什麼'int foo = vec [Enum2] - > get()* 7;'永遠不會編譯。你正在使用一個沒有定義方法的'BaseClass'指針 - 因此你無法(合法地)調用任何你從'vector'中取出的東西。你甚至不能在這裏使用'dynamic_cast',因爲它沒有定義'virtual'方法。 – Yuushi

+0

你的懷疑是沒有根據的。這個設計是一個非常普遍的設計,它的工作原理(假設你有基本的虛擬獲取和設置方法)。如果你有崩潰,詢問你的具體崩潰。 –

+0

@ n.m。看看上面提供的'BaseParameter'的定義,並告訴我你在哪裏可以看到一個'virtual'函數聲明。 – Yuushi

回答

1

如果您在編譯時知道與每個枚舉關聯的類型,您可以通過boost::variant「輕鬆」執行此操作,而無需類型擦除或甚至繼承。 (編輯:將第一溶液使用了大量的C++ 11個特徵我把較少的自動但C++ 03符合的溶液在末端。)

#include <string> 
#include <vector> 
#include <boost/variant.hpp> 
#include <boost/variant/get.hpp> 

// Here's how you define your enums, and what they represent: 
enum class ParameterId { 
    is_elephant = 0, 
    caloric_intake, 
    legs, 
    name, 
    // ... 
    count_ 
}; 
template<ParameterId> struct ConfigTraits; 

// Definition of type of each enum 
template<> struct ConfigTraits<ParameterId::is_elephant> { 
    using type = bool; 
}; 
template<> struct ConfigTraits<ParameterId::caloric_intake> { 
    using type = double; 
}; 
template<> struct ConfigTraits<ParameterId::legs> { 
    using type = int; 
}; 
template<> struct ConfigTraits<ParameterId::name> { 
    using type = std::string; 
}; 
// ... 

// Here's the stuff that makes it work. 

class Parameters { 
    private: 
    // Quick and dirty uniquifier, just to show that it's possible 
    template<typename...T> struct TypeList { 
     using variant = boost::variant<T...>; 
    }; 

    template<typename TL, typename T> struct TypeListHas; 
    template<typename Head, typename...Rest, typename T> 
    struct TypeListHas<TypeList<Head, Rest...>, T> 
     : TypeListHas<TypeList<Rest...>, T> { 
    }; 
    template<typename Head, typename...Rest> 
    struct TypeListHas<TypeList<Head, Rest...>, Head> { 
     static const bool value = true; 
    }; 
    template<typename T> struct TypeListHas<TypeList<>, T> { 
     static const bool value = false; 
    }; 

    template<typename TL, typename T, bool B> struct TypeListMaybeAdd; 
    template<typename TL, typename T> struct TypeListMaybeAdd<TL, T, false> { 
     using type = TL; 
    }; 
    template<typename...Ts, typename T> 
    struct TypeListMaybeAdd<TypeList<Ts...>, T, true> { 
     using type = TypeList<Ts..., T>; 
    }; 
    template<typename TL, typename T> struct TypeListAdd 
     : TypeListMaybeAdd<TL, T, !TypeListHas<TL, T>::value> { 
    }; 

    template<typename TL, int I> struct CollectTypes 
     : CollectTypes<typename TypeListAdd<TL, 
              typename ConfigTraits<ParameterId(I)>::type 
              >::type, I - 1> { 
    }; 
    template<typename TL> struct CollectTypes<TL, 0> { 
     using type = typename TypeListAdd<TL, 
             typename ConfigTraits<ParameterId(0)>::type 
             >::type::variant; 
    }; 

    public: 
    using value_type = 
     typename CollectTypes<TypeList<>, int(ParameterId::count_) - 1>::type; 

    template<ParameterId pid> 
    using param_type = typename ConfigTraits<pid>::type; 

    // It would be better to not initialize all the values twice, but this 
    // was easier. 
    Parameters() : values_(size_t(ParameterId::count_)) { 
     clear(std::integral_constant<int, int(ParameterId::count_) - 1>()); 
    } 

    // getter for when you know the id at compile time. Should have better 
    // error checking. 
    template<ParameterId pid> 
    typename ConfigTraits<pid>::type get() { 
     // The following will segfault if the value has the wrong type. 
     return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]); 
    } 

    // setter when you know the id at compile time 
    template<ParameterId pid> 
    void set(typename ConfigTraits<pid>::type new_val) { 
     values_[int(pid)] = new_val; 
    } 

    // getter for an id known only at runtime; returns a boost::variant; 
    value_type get(ParameterId pid) { 
     return values_[int(pid)]; 
    } 

    private: 
    // Initialize parameters to default values of the correct type 
    template<int I> void clear(std::integral_constant<int, I>) { 
     values_[I] = param_type<ParameterId(I)>(); 
     clear(std::integral_constant<int, I - 1>()); 
    } 
    void clear(std::integral_constant<int, 0>) { 
     values_[0] = param_type<ParameterId(0)>(); 
    } 

    std::vector<value_type> values_; 
}; 

// And finally, a little test 
#include <iostream> 
int main() { 
    Parameters parms; 
    std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' ' 
      << parms.get<ParameterId::is_elephant>() << ' ' 
      << parms.get<ParameterId::caloric_intake>() << ' ' 
      << parms.get<ParameterId::legs>() << std::endl; 
    parms.set<ParameterId::is_elephant>(true); 
    parms.set<ParameterId::caloric_intake>(27183.25); 
    parms.set<ParameterId::legs>(4); 
    parms.set<ParameterId::name>("jumbo"); 
    std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' ' 
      << parms.get<ParameterId::is_elephant>() << ' ' 
      << parms.get<ParameterId::caloric_intake>() << ' ' 
      << parms.get<ParameterId::legs>() << std::endl; 

    return 0; 
} 

對於受益那些誰也沒有使用C++ 11,這裏是其中使用非類枚舉和它不是足夠聰明自身打造boost::variant型版本,所以你必須手動提供它:

#include <string> 
#include <vector> 
#include <boost/variant.hpp> 
#include <boost/variant/get.hpp> 

// Here's how you define your enums, and what they represent: 
struct ParameterId { 
    enum Id { 
    is_elephant = 0, 
    caloric_intake, 
    legs, 
    name, 
    // ... 
    count_ 
    }; 
}; 
template<int> struct ConfigTraits; 

// Definition of type of each enum 
template<> struct ConfigTraits<ParameterId::is_elephant> { 
    typedef bool type; 
}; 
template<> struct ConfigTraits<ParameterId::caloric_intake> { 
    typedef double type; 
}; 
template<> struct ConfigTraits<ParameterId::legs> { 
    typedef int type; 
}; 
template<> struct ConfigTraits<ParameterId::name> { 
    typedef std::string type; 
}; 
// ... 

// Here's the stuff that makes it work. 

// C++03 doesn't have integral_constant, so we need to roll our own: 
template<int I> struct IntegralConstant { static const int value = I; }; 

template<typename VARIANT> 
class Parameters { 
    public: 
    typedef VARIANT value_type; 

    // It would be better to not initialize all the values twice, but this 
    // was easier. 
    Parameters() : values_(size_t(ParameterId::count_)) { 
     clear(IntegralConstant<int(ParameterId::count_) - 1>()); 
    } 

    // getter for when you know the id at compile time. Should have better 
    // error checking. 
    template<ParameterId::Id pid> 
    typename ConfigTraits<pid>::type get() { 
     // The following will segfault if the value has the wrong type. 
     return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]); 
    } 

    // setter when you know the id at compile time 
    template<ParameterId::Id pid> 
    void set(typename ConfigTraits<pid>::type new_val) { 
     values_[int(pid)] = new_val; 
    } 

    // getter for an id known only at runtime; returns a boost::variant; 
    value_type get(ParameterId::Id pid) { 
     return values_[int(pid)]; 
    } 

    private: 
    // Initialize parameters to default values of the correct type 
    template<int I> void clear(IntegralConstant<I>) { 
     values_[I] = typename ConfigTraits<I>::type(); 
     clear(IntegralConstant<I - 1>()); 
    } 
    void clear(IntegralConstant<0>) { 
     values_[0] = typename ConfigTraits<0>::type(); 
    } 

    std::vector<value_type> values_; 
}; 

// And finally, a little test 
#include <iostream> 
int main() { 
    Parameters<boost::variant<bool, int, double, std::string> > parms; 
    std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' ' 
      << parms.get<ParameterId::is_elephant>() << ' ' 
      << parms.get<ParameterId::caloric_intake>() << ' ' 
      << parms.get<ParameterId::legs>() << std::endl; 
    parms.set<ParameterId::is_elephant>(true); 
    parms.set<ParameterId::caloric_intake>(27183.25); 
    parms.set<ParameterId::legs>(4); 
    parms.set<ParameterId::name>("jumbo"); 
    std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' ' 
      << parms.get<ParameterId::is_elephant>() << ' ' 
      << parms.get<ParameterId::caloric_intake>() << ' ' 
      << parms.get<ParameterId::legs>() << std::endl; 

    return 0; 
} 
+0

看起來很有前途,謝謝。大部分代碼是通過遞歸模板元編程來設置一個靜態構建的列表? – meowsqueak

+0

結構專業化的順序是否必須嚴格匹配枚舉的順序? – meowsqueak

+0

哦,可悲的是,我在其中運行的SDK環境不支持強類型枚舉(C++ 11功能)。不過,我真的不知道我是否可以用boost :: variant來實現我所需要的,只要我總是知道參數的類型,而使用這些參數的代碼總是會。即使用boost :: get ()來檢索當前值。對於setter方面,我可以使用boost :: static_visitor(因爲只有少數類型需要處理),以便使用依賴於類型的API函數從外部值源提取適當的值。 – meowsqueak