我想实现一个数据表,其中字段可能有不同的类型。一个字段可以是字符串的向量。另一个字段可以是浮点数的向量。并且在编译时字段的类型是未知的,因为我希望能够从csv文件构造数据表。
如何在C ++中完成?
答案 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);
}
答案 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)
由于数据类型在编译时是未知的,因此必须在运行时构造和存储该信息。对于每行的每个字段,可能有三条信息需要编码:
您可以使用多态类型,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
对象。RowValue
的{{1}}对象;将每个字段读入相应RowSchema
中指定的正确数据类型;并使用FieldSchema
将值附加到m_fields
数组的末尾。由于这是一个Stack Overflow的答案而不是关于C ++ 11的教科书,我不会详细介绍如何构建包含字符串或向量的联合,也不会详细介绍如何使用{ {1}}。所有这些信息都可以在其他地方获得(例如cppreference.com)。这也可以在C ++ 03中完成,还可以模拟非平凡类型的并集(例如,使用emplace_back
)。
显然,我遗漏了很多细节。我要提到的一个警告是vector::emplace_back
的析构函数不足以破坏联合中包含的字符串或向量。相反,您必须在模式中查找数据类型并显式调用该字段的正确析构函数。因此,boost::variant
的析构函数必须遍历字段并逐个销毁。一个C ++ 17 FieldValue
(或RowValue
)在这里会有所帮助,代价是额外的内存。