在 std::vector 中存储模板摘要 类

Storing template abstract classes in std::vector

我正在尝试将一些 Java 代码翻译成 C++,但我在使用类型系统时遇到了一些问题。

我有一个 interface/pure 虚拟 class,它表示 table 中的一个列,我希望 table 是这些通用列的向量:

template<typename T>
class IColumn {
public:
    virtual const std::string& name() = 0;
    virtual size_t size() = 0;
    virtual T at(size_t idx) = 0;
    virtual std::vector<T> data() = 0;
    virtual ~IColumn() = default;
};

class StringColumn : public IColumn<std::string> {
public:
    StringColumn(std::string name, std::vector<std::string> data)
            : name_(std::move(name)), data_(std::move(data)) {}

    const std::string& name() override { return name_; }
    size_t size() override { return data_.size(); }

    const std::vector<std::string>& data() override { return data_; }
    std::string at(size_t idx) override { return data_[idx]; }

private:
    std::string name_;
    std::vector<std::string> data_;
};

class IntColumn : public IColumn<int> {
public:
    IntColumn(std::string name, std::vector<int> data)
            : name_(std::move(name)), data_(std::move(data)) {}

    const std::string& name() override { return name_; }
    size_t size() override { return data_.size(); }

    const std::vector<int>& data() override { return data_; }
    int at(size_t idx) override { return data_[idx]; }

private:
    std::string name_;
    std::vector<int> data_;
};

但是,我在 Table class:

中声明向量时遇到问题
class Table {
   std::vector<std::unique_ptr<IColumn>> columns;
};

我知道 IColumn 是一个模板 class,因此它抱怨缺少模板参数,但是我不确定如何解决这个问题,因为虚函数 T at()std::vector<T> data() 依赖于 T.

我该如何解决这个问题,或者有人可以为此提出替代设计 API?

编辑:按照建议将 std::variant<IntColumn, StringColumn>std::visit 一起使用:

for (auto& col: columns_) {
    std::cout << std::visit([](auto&& arg) { return arg.name(); }, col) << '\t';
}
std::cout << '\n';
for (int i = 0; i < this->rowCount(); i++) {
    for (auto& col : columns_) {
       auto v = std::visit([i](auto&& arg) { return arg.at(i); }, col);
       std::cout << v;
    }
}

第一个 std::visit 有效,但第二个无效。 谢谢。

也许我的方法适合您的需要。 这里我们添加 item_size() 方法和 return void* 而不是 T

class IColumn {
public:
    virtual const std::string& name() = 0;
    virtual size_t size() = 0;
    virtual size_t item_size() = 0;
    virtual void* at(size_t idx) = 0;
    virtual void* data() = 0;
    virtual ~IColumn() = default;
};

然后使用自定义类型实现列

template <class T>
class TColumn : public IColumn {
public:
    TColumn(std::string name, std::vector<T> data) : name_(std::move(name)), data_(std::move(data)) {}

    const std::string& name() override { /* */}
    virtual size_t size() override { /* */ }
    size_t item_size() override {
        return sizeof(T);
    }
    void* at(size_t idx) override {
        return &data_.at(idx);
    }
    void* data() override {
        return data_.data();
    }
private:
    std::string name_;
    std::vector<T> data_;
};

然后你可以做类似的事情

Table t;
std::vector<int> column = { 1, 2, 3 };
t.columns.emplace_back(std::make_unique<TColumn<int>>("name", column));

int item;
void* res = t.columns[0]->at(0);
std::memcpy(&item, res, sizeof(item));

std::cout << item << '\n';

IColumn 看起来很奇怪,你确实可以使用模板:

template <typename T>
class Column {
public:
    Column(std::string name, std::vector<T> data) :
       m_name(std::move(name)),
       m_data(std::move(data))
    {}

    const std::string& name() const { return m_name; }
    std::size_t size() const { return m_data.size(); }
    const T& at(size_t idx) const { return m_data.at(idx); }
    std::vector<T>& data() { return m_data; }
private:
    std::string m_name;
    std::vector<T> m_data;
};

std::variantTable

struct Table
{
   std::vector<std::variant<Column<std::string>, Column<int>>> columns;
};

用法可能类似于

Table table;
table.columns.push_back(Column<std::string>("string column", {"data1", "data2", "data3"}));
table.columns.push_back(Column<int>("int column", {4, 8, 15}));

const char* sep = "";
for (auto& col: table.columns) {
    std::visit([&sep](const auto& arg) { std::cout << sep << arg.name(); }, col);
    sep = "\t";
}
std::cout << "\n";
for (std::size_t i = 0; i < table.rowCount(); i++) {
    const char* sep = "";
    for (const auto& col : table.columns) {
        std::visit([i, &sep](const auto& arg) { std::cout << sep << arg.at(i); }, col);
        sep = "\t";
    }
    std::cout << std::endl;
}

Demo