如何在C++中实现具有不同列数据类型的数据表
How to implement a data table with different column data types in C++
我想实现一个数据表,其中的字段可能有不同的类型。一个字段可以是字符串的矢量。另一个字段可以是浮点向量。字段的类型在编译时是未知的,因为我希望能够从csv文件构造数据表。
我如何在C++中做到这一点?
使用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);
}
实例
因为数据类型在编译时是未知的,所以必须在运行时构造和存储这些信息。对于每行的每个字段,可能有三条信息需要编码:
- 字段的类型
- 字段的值(必须与#1中指定的类型匹配)
- (可选)字段的名称
您可以使用多态类型boost::any
或boost::variant
(或std::any
或std::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文件的过程是:
- 确定所有行的结构(模式)(可能通过读取第一行)
- 构建一个反映该结构的
RowSchema
对象 - 对于每一行:创建一个指向步骤2中的
RowSchema
的RowValue
对象;将每个字段读取为对应的CCD_ 13中指定的正确数据类型;并使用CCD_ 15将该值附加到CCD_
由于这是一个堆栈溢出的答案,而不是关于C++11的教科书,所以我不会详细介绍如何构造包含字符串或向量的并集,也不会介绍如何使用vector::emplace_back
。所有这些信息都可以在其他地方获得(例如,cppreference.com)。这也可以在C++03中完成,还可以进行额外的工作来模拟非平凡类型的并集(例如,使用boost::variant
)。
很明显,我遗漏了很多细节。我要提到的一个注意事项是,FieldValue
的析构函数不足以销毁并集中包含的字符串或向量。相反,您必须在模式中查找数据类型,并显式调用字段的正确析构函数。因此,RowValue
的析构函数必须对字段进行迭代,并分别销毁每个字段。C++17 std::variant
(或boost::variant
)在这里会有所帮助,但需要额外的内存。
我也尝试过类似的东西:
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
基类的指针实现。这允许记录包含子记录。
你应该看到Sean Parent关于"继承是邪恶的基类"的演讲。你可以在这里的"价值语义和基于概念的多态性"下看到它的打印格式
他提出了一个基于概念的对象类,定义了容器元素的接口。任何满足所需接口(即具有所需的独立功能)的对象都可以放入容器中。
您可能可以从下面的代码示例(取自我上面链接的文档)中获得要点。
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()
)都是为您的不同输入定义的。这很好,因为它为您提供了值语义,还使添加新类型变得非常简单。
- 防止主数据类型C++的隐式转换
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 在C++中打印指向不同基元数据类型的指针的内存地址
- C++浮点数据类型和字符串数据类型无法子到模板函数中
- 如何计算数据类型的范围,例如int
- C++中数据类型修饰符的顺序
- C++LinkedList问题.数据类型之间存在冲突?没有匹配的构造函数
- C++哈希表 - 如何解决自定义数据类型作为键的unordered_map冲突?
- 基于给定字符串数据类型的链表删除节点
- 结构内具有更多数据类型的单个链表
- 使用对数据类型的向量的哈希表中的分段错误错误
- 数据类型为结构的链表
- 如何在C++中实现具有不同列数据类型的数据表
- 将查找表/索引到数组中的数据类型
- C++读取具有不同数据类型的列式存储中的 CSV 表
- 数据类型匹配,但我的链表仍然出错
- 用于查询自定义数据类型列表的SQLite虚拟表
- 方形和对称表的数据类型
- 简单抽象数据类型链表
- 模板链表获取复杂数据类型的数据元素