各种类型的容器 - C++

Container of Miscellaneous Types - C++

所以我试图在 C++ 中创建一个 "Table" class 结构如下:

Table.h

class Table
{
private:
    class TableImpl;
    TableImpl* impl;
};

Table.cpp

class Table::TableImpl
{
private:
    class Row
    {
    private:
        template <typename T>
        class RowDataCell
        {
            T data;
        }
        std::vector<RowDataCell*> data;
    };
    std::vector<Row*> rows;
};

TableImpl 包含 std::vectorRow 个对象,每个 Row 个对象包含 std::vector 个通用 RowDataCell对象。唯一的问题是,我无法创建 std::vector,因为我需要为 RowDataCell* 提供模板参数,这将阻碍我拥有一个杂项对象容器的目标。

有没有一种方法可以使用标准 C++ 来实现这个目标。

有两种合理的方法。

第一个是判别联合,另一个是旧 C 风格的类型安全变体 void* "anything could be here".

我将首先提及它们的两个 boost 实现:

boost::variant<A,B,C>(和传入的 std::experimental::variant)是一个有区别的联合。它可以存储 ABC 类型的事物。有多种类型安全的方法可以取出元素,或通过 "visiting" 对它们执行操作。 Variant 对它可以容纳的类型有一些限制,更多的限制取决于你如何注入这些类型。

boost::any(和传入的 std::experimental::any)是具有值语义的类型安全 void*。几乎任何东西都可以存储在其中(any 要求你的对象是 CopyConstructable),但只有当你知道存储在其中的东西的确切类型并请求它时,你才能访问它。

自己写一个是可行的,但我建议使用它们,或者至少理解它们并克隆它们的大部分界面和模式。

variant 可以在其内部存储实例 "internally",这通常是一种更好的方法。您可以使用 union、类型列表和该列表的索引以及一堆元编程样板来模拟它。顺便说一句,对齐问题很棘手。

any 比较容易写,但还是有点挑战。它是一个真正的基本类型擦除对象,只有一个 "cast to type X"(通过 typeid 或等价物)和暴露的副本。如果您见过 std::function 的实现,那么您已经完成了一半。

是的,有一种标准的 C++ 方法可以实现 table 条记录,但它可能会变得很丑陋。我试过。

我们假设 table 是一个记录容器。
记录可以包含字段或记录。
字段可以是不同的类型,例如整数、字符串和 BLOB。

objective,至少对我来说,是让事情尽可能通用,直到最低级别,即专业领域。这意味着字段值通过字符串传递。

所以,这是一个简化的模型:

struct Component; // The base of everything.
struct Record : public Component {
  std::vector<Component *> components;
};
struct Field : public Component {
  std::string  name;
  virtual std::string  get_value_as_string(void) = 0;
};
struct Field_String : public Field;
//... And so on.
struct Table {
  std::vector<Record *> rows;
};

根本问题是 table 的每个单元格都可能属于不同类型。 table 可以有无限的列和行。

我还推荐阅读有关数据库的内容。许多数据库接口可以解决 tables.

的问题

有多种方法可以满足您的需求。一种方法是为要放入容器的类型定义一个通用接口,然后将指向该接口的指针放入向量中:

class RowDataCellInterface
{
public:
    virtual ~RowDataInterface() {}
    virtual std::string toString() const = 0;
    virtual std::unique_ptr<RowDataInterface> clone() const = 0;
};

然后您甚至可以以模板化的方式派生该接口的实现:

template <typename T>
class RowDataCell final : public RowDataCellInterface
{
public:
    virtual std::string toString() const
    { 
        std::stringstream ss; 
        ss << data; 
        return ss.str();
    }

    virtual std::unique_ptr<RowDataInterface> clone() const
    {
        std::make_unique<RowDataCell>(*this);
    }

private:
    T data;
};

现在可以像这样将这些东西放入向量中:

std::vector<std::unique_ptr<RowDataCellInterface>>

一切都应该正常。要复制向量,您应该使用接口的 clone() 成员函数。有几种图书馆解决方案,可以帮助您完成您想要的。其中之一是我建议的 boost::variant。我对一个非常相似的问题给出了非常详细的回答here。另一个库解决方案是 boost::any,起初看起来可能更简单,但是因为为了用它做一些有意义的事情,你必须知道包含的东西的类型,它在访问时被删除。大多数时候我更喜欢 boost::variant 而不是 boost::any