从具有运行时索引的元组中选择一组值

时间:2019-07-04 09:44:05

标签: c++ containers c++17

我的问题的简短介绍: 我正在尝试使用stl容器实现“某种”关系数据库。这只是出于娱乐/教育目的,因此不需要诸如“使用此库”,“这绝对没有用”之类的答案。 我知道标题在这一点上有些混乱,但是我们会达到目的的(确实欢迎对标题进行改进的建议)。

我进行了一些小步骤:

  1. 我可以将表构建为从列名到其值=> std::vector<std::map<std::string, some_variant>>的映射向量。很简单,它代表了我的需求。
  2. 等等,我只可以存储一次列名,并使用其索引访问值。 => std::vector<std::vector<some_variant>>。与第1点一样简单,但比第1点要快。
  3. wait wait,在数据库中,表从字面上看是一个元组序列=> std::vector<std::tuple<args...>>。这很酷,它可以准确地代表我在做什么,没有类型的正确类型,甚至比其他语言还要快。

注意:“快于”是针对 1000000条记录具有如下所示的简单循环:

std::random_device dev;
std::mt19937 gen(dev());
std::uniform_int_distribution<long> rand1_1000(1, 1000);
std::uniform_real_distribution<double> rand1_10(1.0, 10.0);

void fill_1()
{
    using my_variant = std::variant<long, long long, double, std::string>;
    using values = std::map<std::string, my_variant>;
    using table = std::vector<values>;

    table t;
    for (int i = 0; i < 1000000; ++i)
        t.push_back({ {"col_1", rand1_1000(gen)}, {"col_2", rand1_1000(gen)}, {"col_3", rand1_10(gen)} });
    std::cout << "size:" << t.size() << "\n";//just to prevent optimization
}
  1. 2234101600ns-平均:2234

  2. 446344100ns-平均:446

  3. 132075400ns-平均:132

插入: 这些解决方案都没有问题,插入就像示例中的推回元素一样简单。

选择: 1和2很简单,但3却很棘手。

最后,问题出现了:

  1. 内存使用量:使用解决方案1和2会消耗大量内存。因此,此处3似乎再次是正确的选择。 对于具有2个longdouble的100万条记录的示例,我期望长整型为4MB * 2左右,双精度为8MB左右,以及使用矢量,映射和变体的一些开销。相反,我们有(我使用Windows任务管理器进行了测量,不是很准确):

    1.340 MB

    2.120 MB

    3.31 MB

    我是否遗漏了一些东西?除了预先保留正确的大小还是在插入循环后保留shrink_to_fit之外?

  2. 是否有一种方法可以像select语句那样在运行时检索一些元组字段?

using my_tuple = std::tuple<long, long, string,  double>;
std::vector<my_tuple> table;
int to_select;//this could be a vector of columns to select obviosly
std::cin>>to_select;
auto result = select (table, to_select);

您是否有机会以任何方式实施最后一行? 对于我所看到的,我们有两个问题:结果类型应该采用起始元组的类型,然后实际执行所需字段的选择。

我读到了很多答案,他们都谈论使用make_index_sequence或编译时已知索引的连续索引。 我还发现this article非常有趣,但在这种情况下并没有太大用处。

2 个答案:

答案 0 :(得分:2)

这是可行的,但很奇怪:

template<size_t candidate, typename ...T>
constexpr std::variant<T...> helperTupleValueAt(const std::tuple<T...>& t, size_t index)
{
    if constexpr (candidate >= sizeof...(T)) {
        throw std::logic_error("out of bounds");
    } else {
        if (candidate == index) {
            return std::variant<T...>{ std::in_place_index<candidate>, std::get<candidate>(t) };
        } else {
            return helperTupleValueAt<candidate + 1>(t, index);
        }
    }
}

template<typename ...T>
std::variant<T...> tupleValueAt(const std::tuple<T...>& t, size_t index)
{
    return helperTupleValueAt<0>(t, index);
}

https://wandbox.org/permlink/FQJd4chAFVSg5eSy

答案 1 :(得分:0)

关于内存使用情况。

在解决方案1中,您有1个std::vector和1百万个std::map:开销巨大。

在解决方案2中,您有1 + 1百万std::vector:开销很大。
假设向量大致由3个指针(数据,容量,大小)组成,则这24个字节几乎与内容(3*(max(sizeof(long),sizeof(double))+sizeof(discriminant)))一样大。

在解决方案3中,您有1个std::vector直接包含有用的数据:开销可以忽略不计。