如何在C ++中实现具有不同列数据类型的数据表

时间:2015-03-19 20:45:34

标签: c++ templates generic-programming

我想实现一个数据表,其中字段可能有不同的类型。一个字段可以是字符串的向量。另一个字段可以是浮点数的向量。并且在编译时字段的类型是未知的,因为我希望能够从csv文件构造数据表。

如何在C ++中完成?

4 个答案:

答案 0 :(得分:3)

使用boost::variant,它可以代表一组类型之一:

std::vector<boost::variant<std::string, float>> values;

然后,您可以将访问者应用于变体:

struct visitor_t : boost::static_visitor<> {
    void operator()(std::string const& x) const {
        std::cout << "got string: " << x << '\n';
    }

    void operator()(float x) const {
        std::cout << "got float: " << x << '\n';
    }
};

visitor_t visitor;
for (auto&& value : values) {
    boost::apply_visitor(visitor, value);
}

Live Example!

答案 1 :(得分:0)

我尝试过类似的东西:

class Component;

class Field : public Component
{
  // Common interface methods
  public:  
    virtual std::string get_field_name() const = 0;
    virtual std::string get_value_as_string() const = 0;
};

class Record : public Component
{
  // Common interface methods
  std::vector< std::unique_ptr<Component> > fields;
};

class Integer_Field : public Field;

这个想法是Record可以包含各种字段。 各种字段由指向Component基类的指针实现。这允许记录包含子记录。

答案 2 :(得分:0)

你应该看到Sean Parent关于“Inheritance Is the Base Class of Evil”的谈话。您可以在“值语义和基于概念的多态性”下的打印格式here中看到它。

他提出了一个基于概念的对象类,它定义了容器元素的接口。任何符合所需界面的对象(即具有所需的独立功能)都可以放入容器中。

您可以通过查看下面的代码示例(摘自我上面链接的文档)来获取要点。

class object_t {
  public:
    template <typename T>
    object_t(T x) : self_(make_shared<model<T>>(move(x)))
    { }

    friend void draw(const object_t& x, ostream& out, size_t position)
    { x.self_->draw_(out, position); }

  private:
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void draw_(ostream&, size_t) const = 0;
    };
    template <typename T>
    struct model : concept_t {
        model(T x) : data_(move(x)) { }
        void draw_(ostream& out, size_t position) const 
        { draw(data_, out, position); }

        T data_;
    };

   shared_ptr<const concept_t> self_;
};

您的字段将是这些object_t类型中的一种,可以采用任何类型(std::vector<int>std::deque<float>std::string等等。您只需要确保object_t支持的任何方法(在示例中,它只是draw())都是针对您的不同输入定义的。这很好,因为它为您提供了值语义,并且使添加新类型变得非常简单。

答案 3 :(得分:0)

由于数据类型在编译时是未知的,因此必须在运行时构造和存储该信息。对于每行的每个字段,可能有三条信息需要编码:

  1. 该字段的类型。
  2. 字段的值(必须与#1中指定的类型匹配)
  3. (可选)字段名称。
  4. 您可以使用多态类型,boost::anyboost::variant(或std::anystd::variant,如C ++ 17中所定义的那样,但更优雅,更健壮和内存有效的解决方案将利用每一行具有相同结构的事实。

    您正在做的基本上是创建一个数据库程序。在数据库中,架构对数据结构进行编码,但与数据本身是分开的。你想要的是一种在运行时编码模式的方法,如下所示:

    enum class FieldType {
      // Scalar types:
      Boolean, Integer, FloatingPoint, String,
    
      // Array types:
      ArrayBit = 0x1000, // This bit set for array types
      Boolean_Array = Boolean | ArrayBit,
      Integer_Array, FloatingPoint_Array, String_Array
    };
    
    class FieldSchema {
      FieldType   m_type;
      std::string m_name;  // Optional, if fields are named
      ...
    };
    
    class RowSchema {
      std::vector<FieldSchema> m_fields;
      ...
    };
    

    数据字段本身只是可能数据类型的联合。 (请注意,将字符串或向量放入联合需要C ++ 11或更高版本。)

    union FieldValue {
      bool                     m_boolean;
      int                      m_integer;
      double                   m_floatingpoint;
      std::string              m_string;
      std::vector<bool>        m_boolean_array;
      std::vector<int>         m_integer_array;
      std::vector<double>      m_floatingpoint_array;
      std::vector<std::string> m_string_array;
    
      // Constructors for each type go here
    };
    

    数据行只是数据字段的向量,带有指向模式的指针:

    class RowValue {
      RowSchema*               m_schama;
      std::vector<FieldValue>  m_fields;
      ...
    };
    

    现在,对于每个CSV文件,整个表将有一个RowSchema对象,但每行有一个RowValue个对象。给定文件的所有RowValue对象将共享(指向)相同的RowSchema对象。 读取CSV文件的过程是:

    1. 确定所有行的结构(模式)(可能通过读取第一行)。
    2. 构建反映该结构的RowSchema对象。
    3. 对于每一行:从步骤2创建指向RowValue的{​​{1}}对象;将每个字段读入相应RowSchema中指定的正确数据类型;并使用FieldSchema将值附加到m_fields数组的末尾。
    4. 由于这是一个Stack Overflow的答案而不是关于C ++ 11的教科书,我不会详细介绍如何构建包含字符串或向量的联合,也不会详细介绍如何使用{ {1}}。所有这些信息都可以在其他地方获得(例如cppreference.com)。这也可以在C ++ 03中完成,还可以模拟非平凡类型的并集(例如,使用emplace_back)。

      显然,我遗漏了很多细节。我要提到的一个警告是vector::emplace_back的析构函数不足以破坏联合中包含的字符串或向量。相反,您必须在模式中查找数据类型并显式调用该字段的正确析构函数。因此,boost::variant的析构函数必须遍历字段并逐个销毁。一个C ++ 17 FieldValue(或RowValue)在这里会有所帮助,代价是额外的内存。