输入不可知的getter方法

时间:2017-07-31 15:13:54

标签: c++ c++11

我正在为一个系统编写一个客户端,该系统以随机顺序返回自然类型的值(有些可以是int,有些可以是浮点数,有些则是[很好,几乎是自然的])。问题是,我不知道编译时值是什么类型。

由于我不知道在查询远程系统之前要返回的值的类型,提供统一接口的最佳方法是什么,允许客户端库的用户提取值。对吗?

如果查询远程系统一次返回一个字符串,我希望我的get_value()返回一个字符串。如果是int,则使其返回int。或者,如何让客户端库使用正确的类型调用getter?

我认为带有类型提示的模板是实现这一目标的好方法吗?

4 个答案:

答案 0 :(得分:6)

如果存在受支持类型的有限列表,请检查boost或std变体。

如果不是有限列表,则提升或标准任何(或包含任意的变体)。

您也可以找到其他实现。 std版本在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只取一个变量并返回void,以及其他原因。

代码几乎完全未经测试,但设计合理。

答案 1 :(得分:3)

有两种不同的用例。如果客户端程序可以事先知道它想要的值的类型,则可以为每种可能的类型使用不同的getter(例如getIntgetDouble,{{ 1}}),或使用模板化的getter(现代C ++方式):

getString

并明确地将它们设置为确保它们可用。

在客户端库中,用法为:

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

如果客户端程序可能按照编译时未知的顺序接收数据,则必须找到一种方法来返回 variant 数据类型(旧的Basic程序员记住这一点)。你可以在boost或C ++ 17中找到实现,但是一个简单的实现可能是:

int i = get<int>(byte_array);

在这种情况下,客户端程序将使用

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

答案 2 :(得分:2)

我在HDF5库中遇到了同样的问题。文件中数据集的类型可以是任何本机类型(暂时忽略结构)。我的解决方案如下:

  1. 创建抽象基类
  2. 创建一个派生自抽象类的模板类,其中类型是您需要的运行时类型
  3. 在基类中创建将从系统中读取类型的静态方法,然后决定实例化的内容。
  4. 例如:

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

    这有很多优点,包括你可以创建一个基类方法来获取所有类型的字符串值,并对所有类型使用cool std::to_string。如果你需要做一些特定类型的事情,你只需要专业化。

答案 3 :(得分:1)

你说你在使用C ++ 11工作,所以如果你不想使用Boost它的Variant类型那么你可以使用标准的C-Style union type是一组有限的类型。

如果你想要一个变量,不受限制的返回类型,那么你可能想要研究基于概念的多态性&#39;或者&#39; Type Erasure&#39;设计模式。

它也值得研究&#39;模板专业化,除非你在通话时知道返回类型,否则它不会有任何用处,但它是一个很好的技巧来获得具体的具有相同签名的类型处理程序。