![](/img/trans.png)
[英]How can I implement a data structure support different types element just like an array in 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);
}
我嘗試過類似的東西:
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()
)都是為你的不同輸入定義的。 這很好,因為它為您提供了值語義,並且使添加新類型變得非常簡單。
由於數據類型在編譯時是未知的,因此必須在運行時構造和存儲該信息。 對於每行的每個字段,可能有三條信息要編碼:
你可以使用多態類型, 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
對象。 RowSchema
的RowValue
對象; 將每個字段讀入相應FieldSchema
指定的正確數據類型; 並使用emplace_back
將值附加到m_fields
數組的emplace_back
。 由於這是一個Stack Overflow的答案,而不是關於C ++ 11的教科書,我不會詳細介紹如何構建包含字符串或向量的聯合,也不會介紹如何使用vector::emplace_back
。 所有這些信息都可以在其他地方獲得(例如, cppreference.com )。 這也可以在C ++ 03中完成,還可以模擬非平凡類型的並集(例如,通過使用boost::variant
)。
顯然,我遺漏了很多細節。 我要提到的一個警告是, FieldValue
的析構函數不足以銷毀union中包含的字符串或向量。 相反,您必須在模式中查找數據類型並顯式調用該字段的正確析構函數。 因此, RowValue
的析構RowValue
必須迭代字段並單獨銷毀每個字段。 一個C ++ 17 std::variant
(或boost::variant
)在這里會有所幫助,代價是額外的內存。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.