#c #c 11 #boost #boost-multi-index
#c #c 11 #повышение #boost-мультииндексный
Вопрос:
У меня есть следующий (упрощенный) код:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
namespace bmi = boost::multi_index;
#include <string>
#include <iostream>
#include <cassert>
using Container = boost::multi_index_container<
std::string,
bmi::indexed_by< bmi::ordered_non_unique< bmi::identity<std::string> > >
>;
/// Get the base of a non-reverse iterator. It's the iterator itself.
inline
Container::iterator constamp;
iter_base(Container::iterator constamp; it)
{
return it;
}
/** Get a non-reverse iterator that points at the same element as the given reverse_iterator.
*
* @param rit reverse_iterator
* @return a (non-reverse) iterator that points to the same element.
* @pre @p rit is dereferenceable (not equal to @c rend() of whatever container @p rit came from)
*/
inline
Container::iterator
iter_base(Container::reverse_iterator constamp; rit)
{
auto bit = rit.base();
// if 'rit' is a reverse iterator: amp;*(rit.base() - 1) == amp;*rit
return --bit;
}
template <typename IT>
void evict(Containeramp; c, IT rb, IT fin)
{
std::vector<std::string> resu<
for (; rb != fin; ) {
if (rb->size() == 3) {
auto victim = rb;
rb;
std::cout << "victim->" << *victim << ", next->" << (rb==fin ? std::string{"THE END"} : *rb) << "n";
auto next = c.erase(iter_base(victim));
std::cout << "size=" << c.size() << "n";
for (auto constamp; s : c) {
std::cout << "remain: " << s << "n"; // bar - baz - foo
}
rb = IT(next);
(void)next;
}
else {
result.push_back(*rb);
}
}
}
int main(int argc, char**)
{
bool forward = (argc == 1);
Container c;
c.insert("foo"); // will be last
c.insert("bar");
c.insert("baz");
if (forward) {
auto b = c.lower_bound("baz");
std::cout << ">> " << *b << "n"; // prints baz
auto rb = (b);
std::cout << "<< " << *rb << "n"; // prints baz
std::cout << "<< " << *iter_base(rb) << "n"; // prints baz
evict(c, rb, c.end());
}
else {
auto b = c.upper_bound("baz");
std::cout << ">> " << *b << "n"; // prints foo
auto rb = Container::reverse_iterator(b);
std::cout << "<< " << *rb << "n"; // prints baz
std::cout << "<< " << *iter_base(rb) << "n"; // prints baz
evict(c, rb, c.rend());
}
}
Реальный код делает больше, чем просто стирает, но этого достаточно, чтобы проиллюстрировать поведение.
ОТРЕДАКТИРОВАНО, чтобы показать, что в цикле не происходит простого удаления. Предполагается, что элементы добавляются result
в прямом или обратном порядке в зависимости от того, какой тип итератора использовался.
При запуске без аргументов, forward==true
и результат соответствует ожидаемому:
>> baz
<< baz
<< baz
victim->baz, next->foo
size=2
remain: bar
remain: foo
victim->foo, next->THE END
size=1
remain: bar
При запуске с аргументом forward==false
и выводе:
>> foo
<< baz
<< baz
victim->baz, next->bar
size=2
remain: bar
remain: foo
segmentation fault (core dumped)
(не так, как ожидалось)
При компиляции с помощью address sanitizer в строке 42 (строка rb) отображается значение heap-use-after-free.
Кажется, что вызов erase(victim)
rb
каким-то образом стал недействительным, хотя стирание не должно приводить к аннулированию любого другого итератора.
Есть идеи, что я делаю не так?
Ответ №1:
Второй ответ, с дополнительным запросом от OP, чтобы обход выполнялся в прямом или обратном порядке в зависимости от характера итератора. С небольшой осторожностью это можно сделать следующим образом:
template <typename IT>
void evict(Containeramp; c, IT rb, IT fin)
{
std::vector<std::string> resu<
if(rb != fin) for(;;) {
IT next = rb;
next;
bool finished = (next == fin);
if (rb->size() == 3) {
c.erase(iter_base(rb));
std::cout << "size=" << c.size() << "n";
for (auto constamp; s : c) {
std::cout << "remain: " << s << "n"; // bar - baz - foo
}
}
else {
result.push_back(*rb);
}
if(finished) break;
rb = next;
}
}
Мой плохой, поврежденный сквозной код все еще запускался в UB. Пожалуйста, попробуйте это:
template <typename IT>
void evict(Containeramp; c, IT rb, IT fin)
{
std::vector<std::string> resu<
if(rb != fin) for(;;) {
bool finished = (std::next(rb) == fin);
if (rb->size() == 3) {
rb = IT{c.erase(iter_base(rb))};
std::cout << "size=" << c.size() << "n";
for (auto constamp; s : c) {
std::cout << "remain: " << s << "n"; // bar - baz - foo
}
}
else {
result.push_back(*rb);
}
if(finished) break;
}
}
Комментарии:
1. К сожалению, это не помогает. Куча-использование-после-освобождения остается (в
next
строке).2. Вы непосредственно скопировали предлагаемый код дословно ? Обратите внимание, например, на эту
if(rb != fin) for(;;)
часть.3. К сожалению, предложенное решение действительно было ошибочным. Отредактировано с помощью, надеюсь, правильной альтернативы.
Ответ №2:
Хорошо, работа с обратными итераторами — это боль в шее. Давайте проанализируем бизнес с указателями во время выполнения этой части кода evict
:
auto victim = rb;
rb;
auto next = c.erase(iter_base(victim));
когда внутри вызова evict(c, Container::reverse_iterator(c.upper_bound("baz")), c.rend())
. Под «указывает на» я имею в виду «внутренний итератор указывает на». Шаг за шагом мы имеем:
- Перед вводом кода:
rb
указывает на"foo"
,victim
еще не существует.auto victim = rb;
rb
указывает на"foo"
,victim
указывает на"foo"
.rb;
rb
указывает на"baz"
,victim
указывает на"foo"
.auto next = c.erase(iter_base(victim));
"baz"
стирается,rb
указывает на удаленное"baz"
,victim
указывает на"foo"
. Любая дальнейшая операция разыменования, сравнения или (de / in) кремирования сrb
неопределенным поведением.
Я понимаю, что вы пытаетесь написать evict
функцию, которая работает как с итераторами, так и с обратными итераторами. Один из возможных способов сделать это заключается в следующем:
template<typename Container>
std::pair<typename Container::iterator,typename Container::iterator>
direct_range(
typename Container::iterator first,
typename Container::iterator last)
{
return {first,last};
}
template<typename Container>
std::pair<typename Container::iterator,typename Container::iterator>
direct_range(
typename Container::reverse_iterator first,
typename Container::reverse_iterator last)
{
return {last.base(),first.base()};
}
template <typename IT>
void evict(Containeramp; c, IT rb, IT fin)
{
auto p=direct_range<Container>(rb,fin);
c.erase(p.first,p.second);
for(auto constamp; s:c){
std::cout<<"remain: "<<s<<"n"; // bar - baz - foo
}
}
Комментарии:
1. Спасибо. К сожалению, фактический код недостаточно прост в использовании
erase(iterator, iterator)
(представьте, если хотите, что только некоторые элементы должны быть удалены).2. Вы написали «4. база удалена». Это потому
iter_base(victim)
, что — итератор — указывает на тот же элемент ,rb
что и — обратный итератор — ?3. На ваш первый ответ: как только у вас будет результат
direct_range
, вы можетеerase(iterator,iterator)
заменить его более сложным кодом, работающим с необратимыми итераторами.4. На ваш второй ответ: да.
5. Я отредактировал цикл,
evict
чтобы показать, что в цикле происходит не просто удаление. Мне кажется, что я не могу использоватьdirect_range
в этом случае (повторениеdirect_range
всегда будет обрабатывать элементы в прямом порядке — это заполнило бы вектор в неправильном порядке при вызове с помощью reverse_iterators ).