повысить multi_index_container, алгоритмы изменения диапазона и константность

Я использую boost multi_index_container, который запрашивается с помощью equal_range, а результат возвращается функцией с помощью range::join и boost::any_range
Аргумент ссылки any_range определяется как константная ссылка на тип — должен быть константным из-за характер multi_index_container, не совсем уверен в ссылке. Пример:

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

Теперь мне нужно использовать алгоритмы мутирующего диапазона, такие как boost::sort, unique и т. д., которые, очевидно, не могут работать в диапазоне из-за постоянства элементов в диапазоне.
Есть ли какой-либо обходной путь, кроме копирования элементов в новый контейнер?

РЕДАКТИРОВАТЬ 1:
структура и пример 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;

person kreuzerkrieg    schedule 10.12.2014    source источник
comment
Вы не можете действительно сортировать MIC. Но MIC будет держать себя в порядке, это то, за что вы платите в первую очередь :) (Вы можете изменить индекс RA.)   -  person sehe    schedule 11.12.2014


Ответы (2)


Хорошо, когда у вас есть диапазон, вы действительно не можете его отсортировать или каким-то образом изменить, потому что порядок элементов фиксируется индексом — это абсолютно фундаментальный инвариант индексов, обеспечиваемый константностью элементов, во многом так же, как если бы вы найти, скажем, std::set. Вместо того, чтобы делать полную копию во внешний контейнер, вы можете создать более легкое представление из указателей или ссылок на исходные элементы, которыми впоследствии можно будет манипулировать по своему усмотрению. Это пример с представлениями, построенными как std::vectors из std::reference_wrappers для элементов:

Жить на 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} 
person Joaquín M López Muñoz    schedule 10.12.2014
comment
Привет, рад видеть вас здесь :) Я знал о константности, это было упомянуто в документации MIC, однако я хочу сохранить свое право жаловаться :). Шутки в сторону, я не очень знаком с reference_wrapper, я думаю, под капотом это контейнер указателей? Итак, как я уже упоминал ранее, у меня всегда есть этот вариант, но опять же создание контейнера с указателями 1M каждый раз - не самое элегантное и ориентированное на производительность решение, которое я ищу. - person kreuzerkrieg; 10.12.2014
comment
что, если... В моем первоначальном вопросе я упомянул сортировку и уникальность, что, если я предоставлю пользователю дополнительные индексы? как только пользователь знает, как он хочет, чтобы результат был отсортирован или уникален (и это конечное число вариантов), он должен запросить MIC еще раз и получить диапазон, отсортированный или уникальный (или оба) так, как он хочет. и скажем, это будет 10 дополнительных индексов. Не могли бы вы предсказать влияние потребления памяти/производительности всей структуры MIC? - person kreuzerkrieg; 10.12.2014
comment
reference_wrapper это, да, замаскированный указатель. Наличие 1 млн указателей — это цена, которую приходится платить за произвольные манипуляции с данными, и я не понимаю, как можно обойтись без этого. Альтернативой является наличие дополнительного индекса типа random_access, а затем перенос расположения вашего представления в этот индекс через rearrange, как описано в boost.org/libs/multi_index/doc/tutorial/indices.html#rearrange . Что касается наличия десяти дополнительных индексов, я не очень понимаю вашу точку зрения, и накладные расходы в любом случае больше, чем 1 миллион указателей подходов на основе представлений. - person Joaquín M López Muñoz; 11.12.2014
comment
Если я правильно вас понимаю, переупорядочивать не вариант, после того как данные загружены и упорядочены, они должны быть константными и доступными только для чтения, поскольку доступ к ним осуществляется из нескольких потоков, переупорядочивание введет в решение механизм синхронизации, который IMO будет иметь неприемлемую производительность. влияние. Что касается дополнительных индексов, см. Редактирование вопроса. Допустим, вы запрашиваете MIC по Key1, а затем вам нужно отсортировать его по m_intMem02, поскольку вы не можете отсортировать результаты, вы просто снова запрашиваете MIC по Key4. - person kreuzerkrieg; 11.12.2014
comment
В любом случае, поскольку вы упомянули, что накладные расходы больше, чем решение на основе представлений, я принимаю ваше предложение в качестве ответа. Спасибо! - person kreuzerkrieg; 11.12.2014
comment
rearrange не вводит никакого механизма синхронизации как такового: как и любые другие изменяющие операции (например, insert), пользователь должен убедиться, что выполнение выполняется потокобезопасным способом. - person Joaquín M López Muñoz; 11.12.2014
comment
да, это то, что я имею в виду - мне придется ввести некоторую блокировку при перестановке. - person kreuzerkrieg; 11.12.2014

Звучит так, как будто вам вообще не нужны ваши данные в контейнере 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;
}
person Richard Hodges    schedule 10.12.2014
comment
Так что, возможно, я недостаточно ясно выразился, я хочу, чтобы данные были неизменными, и что-то специальное было рассмотрено, но оказалось, что оно уступает по производительности MIC. Вернемся к константности, изменяемые алгоритмы на самом деле не мутируют данные с точки зрения внутренней инвариантности экземпляра класса, просто они должны разыменовывать и назначать, поэтому они изменяемы, другими словами, данные остаются «константами». - person kreuzerkrieg; 10.12.2014
comment
Сортировка и создание уникальных представлений логически неотличимы от создания новых индексов — представления сами по себе являются индексами. Вы можете избежать копирования, скопировав ссылки (указатели или итераторы) в контейнер и предоставив собственный компаратор для выполнения разыменования перед сравнением (как указано выше). Я был бы удивлен, если бы мультииндекс был "быстрее", чем поддержка отдельных индексов - конечно, удобнее, но мне кажется, что мультииндекс покрывает только часть ваших требований. Что мне не хватает? - person Richard Hodges; 10.12.2014
comment
что касается «быстрее», «медленнее», проверьте это - stackoverflow.com/questions/26825538/ - person kreuzerkrieg; 10.12.2014
comment
что касается покрытия требований, есть две части: инфра, которая содержит данные и предоставляет «запрошенные» данные пользователю бизнес-логики. тогда разработчик бизнес-логики должен манипулировать данными в соответствии со своими потребностями, к сожалению, эти потребности должны манипулировать данными, например, отфильтровывать ненужные данные, которые требуют изменчивости. копирование указателей рассматривалось как последнее средство - мы говорим об элементах 1M, и я должен сделать все возможное, чтобы обеспечить задержку на уровне микросекунд при манипулировании данными. - person kreuzerkrieg; 10.12.2014
comment
Я не знаю, насколько велики ваши данные. Копирование данных, если оно невелико, зачастую является самым быстрым решением в среде SMP. Другой подход может заключаться в фильтрации данных при создании мультииндексного контейнера. то есть вместо того, чтобы создавать контейнер, а затем фильтровать его, фильтруйте элементы, пока они хранятся в нем. Это устраняет необходимость копировать что-либо (данные могут быть moved в мультииндексе с Boost 1.57 AFAIR) - person Richard Hodges; 10.12.2014
comment
как я упоминал выше, они нацелены на 1 миллион предметов. вот и все, я не могу отфильтровать его заранее, он больше похож на SQL-запрос, я понятия не имею, что они будут запрашивать в следующие 6 месяцев, MIC позволяет просто добавить новый индекс, когда появится новое требование - person kreuzerkrieg; 10.12.2014
comment
:-) Нет, я имею в виду, насколько велик каждый отдельный предмет в коллекции из 1 миллиона предметов? В любом случае, если вы хотите избежать копий или векторов ссылок, подход «фильтрация при построении» — ваше единственное решение. Мне кажется, что вы делаете эквивалент SELECT для SELECT, если вы используете какой-либо другой подход. - person Richard Hodges; 10.12.2014
comment
довольно большой, он включает в себя 3 строковых элемента, один из которых может быть довольно длинным (URL), и дюжину членов int, поэтому копирование этих элементов будет иметь катастрофические последствия для производительности, но у меня все еще есть возможность иметь контейнер указателей. - person kreuzerkrieg; 10.12.2014