【问题标题】:boost multi_index_container, range mutating algorithms and constnessboost multi_index_container,范围变异算法和常量
【发布时间】:2015-02-08 22:34:30
【问题描述】:

我正在使用 boost multi_index_container,它由 equal_range 查询,并使用 range::join 和 boost::any_range 从函数返回结果
any_range Reference 参数被定义为对类型的 const 引用 - 由于 multi_index_container 的性质,必须是 const,对引用不太确定。示例:

typedef boost::any_range<TestData, boost::random_access_traversal_tag,
                         const TestData &, std::ptrdiff_t> TestRange;

现在我需要使用变异范围算法,例如 boost::sort、unique 等,由于范围内元素的常量性,这些算法显然无法在范围内运行。
除了将元素复制到新容器中之外,还有其他解决方法吗?

编辑 1:
struct 和 MIC 示例:

struct TestData {
  TestData()
      : m_strMem01("test"), m_intMem02(rand()),
        m_boolMem03(rand() < RAND_MAX / 2) {}
  std::string m_strMem01;
  int m_intMem02;
  bool m_boolMem03;
};

typedef boost::multi_index_container<
    TestData,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct RndKey1>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key1>,
            bmi::composite_key<
                TestData,
                bmi::member<TestData, std::string, &TestData::m_strMem01>,
                bmi::member<TestData, bool, &TestData::m_boolMem03>>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key4>,
            bmi::composite_key<
                TestData,
                bmi::member<TestData, std::string, &TestData::m_strMem01>,
                bmi::member<TestData, bool, &TestData::m_intMem02>>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key2>,
            bmi::member<TestData, int, &TestData::m_intMem02>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key3>,
            bmi::member<TestData, bool, &TestData::m_boolMem03>>>>
    TestDataContainer;

【问题讨论】:

  • 您无法真正对 MIC 进行排序。但是 MIC 会保持自己的排序,这就是您首先要付出的代价:)(您可以重新排列 RA 索引。)

标签: c++ algorithm boost boost-multi-index boost-range


【解决方案1】:

好的,一旦你有了你的范围,你真的不能对它进行排序或以某种方式重新排列它,因为元素的顺序是由索引固定的——这是由元素的常量强制执行的索引的绝对基本不变量,就像你会发现说std::set。您可以使用指针或对原始元素的引用创建一个更轻量级的 view ,而不是对外部容器进行完整复制,以后可以根据需要对其进行操作。这是一个示例,其视图构造为 std::reference_wrappers 的 std::vectors 到元素:

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/join.hpp>
#include <functional>
#include <iostream>
#include <vector>

using namespace boost::multi_index;

struct X
{
  int x,y;
};

std::ostream& operator<<(std::ostream& os,const X& a)
{
  return os<<"{"<<a.x<<","<<a.y<<"}";
}

typedef multi_index_container<
  X,
  indexed_by<
    ordered_non_unique<member<X,int,&X::x>>
  >
> multi_t;

struct view:std::vector<std::reference_wrapper<const X>>
{
  using base=std::vector<std::reference_wrapper<const X>>;

  template<typename InputIterator>
  view(InputIterator first,InputIterator last):base(first,last){}

  template<typename InputIterator>
  view(const std::pair<InputIterator,InputIterator> p):base(p.first,p.second){}  
};

int main()
{
  multi_t m={{0,1},{0,0},{0,2},{1,3},{1,1},{2,0},{2,1}};

  view v1=m.equal_range(0); // elements with x==0
  view v2=m.equal_range(2); // elements with x==2
  auto v3=boost::range::join(v1,v2); // join them
  boost::range::sort(v3,[](const X& a,const X& b){return a.y<b.y;}); // sort them by y
  for(const auto& x:v3)std::cout<<x<<" "; // output
}

输出:

{0,0} {2,0} {0,1} {2,1} {0,2} 

【讨论】:

  • 嗨,很高兴在这里见到你 :) 我知道 constness,它在 MIC 文档中有所提及,但我想保留我的抱怨权 :)。撇开玩笑不谈,我对reference_wrapper 不是很熟悉,我猜它是一个指针容器?所以,正如我之前提到的,我总是有这个选项,但是每次创建一个 1M 指针的容器并不是我正在寻找的最优雅和面向性能的解决方案
  • 如果...在我最初的问题中我提到排序和唯一性,如果我为用户提供额外的索引会怎样?一旦用户知道他希望结果如何排序或唯一(并且它是有限数量的选项),他应该再次查询 MIC 并以他想要的方式获得排序或唯一(或两者)的范围。假设这将是 10 个额外的指标,您能否预测整个 MIC 结构的内存消耗/性能影响?
  • reference_wrapper 是,是的,一个伪装的指针。拥有 100 万个指针是您对数据进行任意操作所要付出的代价,而且我不知道您如何能免除它。另一种方法是使用random_access 类型的额外索引,然后通过rearrange 将视图的排列转移到该索引,如boost.org/libs/multi_index/doc/tutorial/indices.html#rearrange 中所述。至于有十个额外的索引,我真的不明白你的意思,而且开销无论如何都大于基于视图的方法的 1M 指针。
  • 如果我没听错的话,重新排列不是一个选项,一旦数据被加载和排列,它应该是 const 和只读的,因为它是从多个线程访问的,重新排列会将同步机制引入解决方案,IMO 将产生不可接受的性能影响。至于其他索引,请参阅问题编辑。假设您通过 Key1 查询 MIC,然后您需要按 m_intMem02 对其进行排序,因为您无法对结果进行排序,您只需通过 Key4 再次查询 MIC。
  • 无论如何,既然您提到开销大于基于视图的解决方案,我接受您的建议作为答案。谢谢!
【解决方案2】:

听起来好像您根本不希望将数据放在 multi_index 容器中。 multi_index 容器(顾名思义)旨在存储不可变数据,同时针对该数据维护多个索引(或具有约束的视图)。

如果您希望能够更新数据,您可以将部分数据设为mutable,但您必须确保mutable 部分不参与索引构建。

如果您真正想做的是改变数据并因此生成新索引,那么您有 2 个选项 - 删除并重新插入,或者(更好)不使用 boost::multi_index。

一种方法是使用向量并维护您自己的临时索引。

例如,字符串向量中唯一条目的索引(为了简单起见):

vector<const string*> unique_stable_index(const vector<string>& data)
{
    struct less_ptr {
        bool operator()(const string* l, const string* r) const {
            return *l < *r;
        }
    };

    vector<const string*> result;
    set<const string*, less_ptr> seen;
    for (const auto& s : data) {
        if(seen.insert(&s).second)
            result.push_back(&s);
    }

    return result;
}

【讨论】:

  • 所以也许我不够清楚,我确实希望数据不可变,并且考虑过 ad hoc,但发现其性能不如 MIC。回到常量,可变算法并没有真正改变类实例内部不变性的数据,只是它们必须取消引用和分配,这就是它们可变的原因,换句话说,数据保持“常量”
  • 排序和创建唯一视图在逻辑上与创建新索引没有区别——视图本身就是索引。您可以通过将引用(指针或迭代器)复制到容器中来避免复制,并提供自定义比较器以在比较之前执行取消引用(如上所述)。如果多索引比维护单独的索引“更快”,我会感到惊讶——当然更方便,但在我看来,多索引只满足您的部分要求。我错过了什么?
  • 至于“更快”、“更慢”,请查看 - stackoverflow.com/questions/26825538/…
  • 关于覆盖需求,有两个部分,infra,它保存数据并提供'查询'的数据给业务逻辑用户。那么业务逻辑开发人员需要根据自己的需求来操作数据,不幸的是,这些需求必须操作数据,比如过滤掉不需要可变性的不需要的数据。复制指针被认为是最后的手段 - 我们正在谈论 1M 项,我应该尽最大努力为数据操作提供微秒级延迟
  • 我不知道你的数据有多大。如果它不是很大,复制数据通常是 SMP 环境中最快的解决方案。另一种方法可能是在构建多索引容器时过滤数据。即,与其构建容器然后对其进行过滤,不如在项目存储在其中时对其进行过滤。这消除了对任何内容的副本的需要(从 boost 1.57 AFAIR 起,数据可以是 moved 到多索引中)
猜你喜欢
  • 2015-09-17
  • 2012-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-31
  • 2010-12-14
相关资源
最近更新 更多