简体   繁体   中英

Storing template abstract classes in std::vector

I am trying to translate some Java code into C++ and I am having some trouble with the type system.

I have a interface/pure virtual class which represents a column in a table, and I want the table to be a vector of these generic columns:

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_;
};

However, I am having issues when declaring the vector in the Table class:

class Table {
   std::vector<std::unique_ptr<IColumn>> columns;
};

I understand that IColumn is a template class, hence it complains about the missing template parameter, however I am not sure how to get around this issue, because the virtual functions T at() and std::vector<T> data() rely on T.

How could I fix this issue or could someone suggest an alternative design for this API?

EDIT: Using the std::variant<IntColumn, StringColumn> with std::visit as suggested:

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;
    }
}

The first std::visit works, but the second one doesn't. Thanks.

IColumn seems strange, you can indeed go with template:

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;
};

and std::variant in Table

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

Usage might be similar to

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

Maybe my approach is suitable for your needs. Here we add item_size() method and return void* instead of 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;
};

And then implement column with custom types

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_;
};

And then you can do something like

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';

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM