2017-07-31 25 views
0

我正在爲一個以隨機順序返回自然類型值的系統(有些可以是int,其他float,其他字符串[很好,幾乎很自然])寫一個客戶端。問題是,我不知道在編譯時會有什麼類型的值。類型不可知的getter方法

因爲我不知道在查詢遠程系統之前要返回的值的類型,提供統一接口的最好方法是什麼,它允許客戶端庫的用戶提取值正確的類型?

如果查詢遠程系統返回一個字符串,我想我的get_value()返回一個字符串。如果一個int,使它返回一個int。或者,如何讓客戶端庫使用正確的類型調用getter?

我想模板的類型提示將是一個很好的方法來實現這一目標?

+0

如何從遠程系統獲取值?是收到的數據中收到的價值類型? – bracco23

+0

我得到他們作爲一個字節數組和類型確實帶有值。 – ruipacheco

+0

您能提供一個您將從遠程系統收到的原始數據的例子嗎? – bracco23

回答

3

有兩種不同的用例。如果客戶端程序可以預先知道它想要的值的類型,則可以爲每種可能的類型使用不同的getter(例如,使用getIntgetDoublegetString)或使用模板getter(現代C++方式):

template <class T> 
T get(char *byte_array) { 
    T value; 
    # manage to extract the value 
    return T; 
} 

並明確地實例化它們以確保它們可用。

在客戶端庫,使用將是:

int i = get<int>(byte_array); 

如果客戶端程序可以是在編譯​​時不明的順序接收到的數據,你必須找到一個方法來返回變種數據類型(舊的Basic程序員記得那個)。您可以在升壓或C++ 17的實現,但是一個簡單的實現可能是:

struct variant { 
    enum Type { INT, DOUBLE, STRING, ... } type; 
    union { 
     int int_val; 
     double d_val; 
     std::string str_val; 
     ... 
    }; 
}; 

在這種情況下,客戶端程序將使用

variant v = get(byte_array); 
switch v.type { 
case INT: 
    ... 
} 
+0

所有評論都很棒,但對這篇文章的第一個建議更適合我的用例。 – ruipacheco

6

如果存在支持類型的有限列表,則檢查boost或std變體。

如果不是有限的列表,boost或std any(或包含any的變體)。

你也可以找到其他的實現。標準版本在C++ 17中。

變體的簡化版本可能會寫入100或兩行代碼。

這裏是一個粗C++ 14變體:

constexpr std::size_t max() { return 0; } 
template<class...Ts> 
constexpr std::size_t max(std::size_t t0, Ts...ts) { 
    return (t0<max(ts...))?max(ts...):t0; 
} 
template<class T0, class...Ts> 
struct index_of_in; 
template<class T0, class...Ts> 
struct index_of_in<T0, T0, Ts...>:std::integral_constant<std::size_t, 0> {}; 
template<class T0, class T1, class...Ts> 
struct index_of_in<T0, T1, Ts...>: 
    std::integral_constant<std::size_t, 
     index_of_in<T0, Ts...>::value+1 
    > 
{}; 

struct variant_vtable { 
    void(*dtor)(void*) = 0; 
    void(*copy)(void*, void const*) = 0; 
    void(*move)(void*, void*) = 0; 
}; 
template<class T> 
void populate_vtable(variant_vtable* vtable) { 
    vtable->dtor = [](void* ptr){ static_cast<T*>(ptr)->~T(); }; 
    vtable->copy = [](void* dest, void const* src){ 
    ::new(dest) T(*static_cast<T const*>(src)); 
    }; 
    vtable->move = [](void* dest, void* src){ 
    ::new(dest) T(std::move(*static_cast<T*>(src))); 
    }; 
} 
template<class T> 
variant_vtable make_vtable() { 
    variant_vtable r; 
    populate_vtable<T>(&r); 
    return r; 
} 
template<class T> 
variant_vtable const* get_vtable() { 
    static const variant_vtable table = make_vtable<T>(); 
    return &table; 
} 
template<class T0, class...Ts> 
struct my_variant { 
    std::size_t index = -1; 
    variant_vtable const* vtable = 0; 
    static constexpr auto data_size = max(sizeof(T0),sizeof(Ts)...); 
    static constexpr auto data_align = max(alignof(T0),alignof(Ts)...); 
    template<class T> 
    static constexpr std::size_t index_of() { 
     return index_of_in<T, T0, Ts...>::value; 
    } 
    typename std::aligned_storage< data_size, data_align >::type data; 
    template<class T> 
    T* get() { 
    if (index_of<T>() == index) 
     return static_cast<T*>((void*)&data); 
    else 
     return nullptr; 
    } 
    template<class T> 
    T const* get() const { 
    return const_cast<my_variant*>(this)->get<T>(); 
    } 
    template<class F, class R> 
    using applicator = R(*)(F&&, my_variant*); 
    template<class T, class F, class R> 
    static applicator<F, R> get_applicator() { 
    return [](F&& f, my_variant* ptr)->R { 
     return std::forward<F>(f)(*ptr->get<T>()); 
    }; 
    } 
    template<class F, class R=typename std::result_of<F(T0&)>::type> 
    R visit(F&& f) & { 
    if (index == (std::size_t)-1) throw std::invalid_argument("variant"); 
    static const applicator<F, R> table[] = { 
     get_applicator<T0, F, R>(), 
     get_applicator<Ts, F, R>()... 
    }; 
    return table[index](std::forward<F>(f), this); 
    } 
    template<class F, 
    class R=typename std::result_of<F(T0 const&)>::type 
    > 
    R visit(F&& f) const& { 
    return const_cast<my_variant*>(this)->visit(
     [&f](auto const& v)->R 
     { 
     return std::forward<F>(f)(v); 
     } 
    ); 
    } 
    template<class F, 
    class R=typename std::result_of<F(T0&&)>::type 
    > 
    R visit(F&& f) && { 
    return visit([&f](auto& v)->R { 
     return std::forward<F>(f)(std::move(v)); 
    }); 
    } 
    explicit operator bool() const { return vtable; } 
    template<class T, class...Args> 
    void emplace(Args&&...args) { 
    clear(); 
    ::new((void*)&data) T(std::forward<Args>(args)...); 
    index = index_of<T>(); 
    vtable = get_vtable<T>(); 
    } 
    void clear() { 
    if (!vtable) return; 
    vtable->dtor(&data); 
    index = -1; 
    vtable = nullptr; 
    } 
    ~my_variant() { clear(); } 

    my_variant() {} 
    void copy_from(my_variant const& o) { 
    if (this == &o) return; 
    clear(); 
    if (!o.vtable) return; 
    o.vtable->copy(&data, &o.data); 
    vtable = o.vtable; 
    index = o.index; 
    } 
    void move_from(my_variant&& o) { 
    if (this == &o) return; 
    clear(); 
    if (!o.vtable) return; 
    o.vtable->move(&data, &o.data); 
    vtable = o.vtable; 
    index = o.index; 
    } 
    my_variant(my_variant const& o) { 
    copy_from(o); 
    } 
    my_variant(my_variant && o) { 
    move_from(std::move(o)); 
    } 
    my_variant& operator=(my_variant const& o) { 
    copy_from(o); 
    return *this; 
    } 
    my_variant& operator=(my_variant&& o) { 
    move_from(std::move(o)); 
    return *this; 
    } 
    template<class T, 
    typename std::enable_if<!std::is_same<typename std::decay<T>::type, my_variant>{}, int>::type =0 
    > 
    my_variant(T&& t) { 
    emplace<typename std::decay<T>::type>(std::forward<T>(t)); 
    } 
}; 

Live example

轉換爲C++ 11將包含一堆替換帶助手的lambdas。我不喜歡用C++ 11編寫,而這個C++ 14是遠離它的大多數機械轉換。

原因很簡單,因爲visit只需要一個變體並返回無效等等原因。

代碼幾乎完全未經測試,但設計很完善。

+0

我是在C++ 11上。標準中的任何東西都適用於它? – ruipacheco

+1

@ruip no。使用boost變體,或者找到一個C++ 11等價物。 – Yakk

+0

@ruipacheco如果您正在使用最近的GCC版本,您可以使用它。 – user0042

2

我對HDF5庫有這個完全相同的問題。文件中數據集的類型可以是任何本機類型(現在忽略結構)。我的解決辦法如下:

  1. 創建一個抽象基類
  2. 創建從抽象類,其中類型是運行時類型需要
  3. 建立在基類的靜態方法派生的模板類它會從你的系統中讀取這個類型,然後決定實例化什麼。

例如:

static std::shared_ptr<Base> GetVariable() 
{ 
    switch(mytype) 
    { 
    case INT16: 
     return std::make_shared<Derived<uint16_t>>(value); 
    case INT32: 
     return std::make_shared<Derived<uint32_t>>(value); 
    //etc... 
    } 
} 

這方面有很多優點,包括你可以做一個基類的方法獲取字符串值,爲您的所有類型,並使用所有的酷std::to_string類型。如果你需要做一些特定類型的事情,你只需要專業化。

1

你說你工作在C++ 11所以如果您不想使用Boost作爲Variant類型,那麼如果返回類型是有限的一組類型,則可以使用標準C-Style union

如果你想要一個變量,不受限制,返回類型,那麼你可能會想看看「基於概念的多態性」或「類型擦除」設計模式。

這也是值得研究'模板專業化',它不會有任何用處,除非你知道返回類型時調用,但它是一個很好的技巧,以獲得具有相同簽名的特定類型處理程序。

+0

從技術上說是UB,但是可行......應該使用reinterpret_cast代替.. – Swift