std::set стирания shared_ptr приводит к SIGABRT

У меня есть std::set общих указателей на объекты определенного типа (здесь int приведены только для примера). Что мне нужно, так это вставить общие ptr, построенные из необработанных указателей. Но когда я пытаюсь стереть некоторые из элементов набора (опять же, у меня есть только необработанный указатель), я должен построить shared_ptr и передать его методу стирания (мне кажется, что это правда, потому что операторы сравнения shared_ptr сравнивают свои необработанные указатели внутри) .

Фрагмент кода, ведущий к SIGABRT:

std::set<std::shared_ptr<int>> sett;
int *rp = new int(5);
sett.emplace( rp );
sett.erase( std::shared_ptr<int>( rp ) );

person Uroboros    schedule 08.02.2018    source источник
comment
Вы не можете удалить &a.   -  person tkausl    schedule 08.02.2018
comment
Вы не можете использовать shared_ptr для управления указателем, указывающим на объект стека.   -  person llllllllll    schedule 08.02.2018
comment
Распределение стека @liliscent здесь, например. На самом деле память, выделенная с помощью new, приводит к такому же поведению   -  person Uroboros    schedule 08.02.2018
comment
@Uroboros Вы не можете использовать 2 shared_ptr для управления 1 необработанным указателем.   -  person llllllllll    schedule 08.02.2018


Ответы (2)


Это не нормально:

sett.erase( shared_ptr<int>( rp ) );

Здесь rp является указателем, поэтому вы создаете анонимный временный shared_ptr, затем удаляете значение и память, на которые он указывает, затем ваш анонимный временный объект снова удаляет его.

Вы не должны создавать два разных shared_ptr, указывающих на один и тот же объект. Если вам нужно что-то в этом роде, вы можете вместо этого рассмотреть enable_shared_from_this. Или, что еще лучше, сотрите из контейнера, вообще не создавая shared_ptr, реализуя функцию сравнения для std::set, которая позволяет сравнивать с необработанным указателем. Подробнее об этом см.: Что такое прозрачные компараторы?

person John Zwinck    schedule 08.02.2018
comment
Спасибо, но я не понимаю You must not construct two different shared_ptrs pointing to the same object. Он общий, а не уникальный... - person Uroboros; 08.02.2018
comment
@Uroboros: он используется совместно, если два указателя знают друг о друге. То есть, если p1 указывает на некоторый объект, p2 может быть построен из p1, но не должен быть построен из адреса объекта. У них нет возможности координировать свои действия просто по адресу объекта (где они будут хранить передаваемые данные?), поэтому вы должны соединить их, создав все общие указатели из первого, если он уже существует. Другой способ подумать об этом: обычно вы не должны создавать общие указатели из необработанных указателей - только из new выражений или через make_shared. - person John Zwinck; 08.02.2018
comment
Представьте, умирает старик. В понедельник он просит сына организовать его похороны. Во вторник он просит дочь организовать его похороны. Если сын и дочь не будут общаться друг с другом, то похорон будет двое, а одна не состоится за исключением пустого гроба. - person John Zwinck; 08.02.2018

Цитата из cppreference

Построение нового shared_ptr с использованием необработанного базового указателя, принадлежащего другому shared_ptr, приводит к неопределенному поведению.

Когда вы сделали это:

sett.emplace( rp );

Из-за неявного преобразования типов был создан shared_ptr, которому было передано право собственности на ячейку памяти, указанную rp. Назовем его sp1(1), где (1) обозначает количество ссылок

Когда вы вызываете это: sett.erase( std::shared_ptr<int>( rp ) );

Происходит следующая последовательность событий:

  • Новый shared_ptr владеет памятью, на которую указывает rp. shared_ptr считает, что счетчик ссылок памяти равен 1. sp2(1)
  • erase называется. Это вызывает компаратор по умолчанию. Это может привести к удалению первого shared_ptr sp1.

  • Деструктор вызывается для sp1, и счетчик ссылок становится равным 0. Поскольку счетчик ссылок равен 0, память освобождается.

  • Когда стирание возвращает sp2(1), вызывается деструктор (поскольку он был временным, созданным для вызова функции). Счетчик ссылок становится равным 0 для памяти, управляемой sp2.

  • Деструктор, не зная, что судьба базовой памяти уже решена удалением sp1, снова вызывает удаление памяти. В этот момент происходит двойное удаление.

Вот почему либо сохраняйте все как shared_ptr сразу после создания памяти, либо напишите собственный компаратор для ваших типов указателей.

person bashrc    schedule 08.02.2018