Законно ли иметь независимые shared_ptrs, указывающие на один и тот же объект? - PullRequest
0 голосов
/ 13 февраля 2019

Многие статьи о shared_ptr предостерегают от случайного создания независимых shared_ptr s для одного и того же объекта.Например, эта статья .Там есть комментарий // Bad, each shared_ptr thinks it's the only owner of the object.

Но что, если я хочу именно такого поведения?Например:

auto* object = new Object();
auto ptr1 = std::shared_ptr<Object>(object);
auto ptr2 = std::shared_ptr<Object>(object, [ptr1](Object* obj){ obj->cleanup(); });
ptr2 = nullptr;
ptr1 = nullptr;

Это прекрасно работает в GCC 6.3, но допустимо ли это , то есть стандарт разрешает такое использование?

Ответы [ 3 ]

0 голосов
/ 13 февраля 2019

То, что вы показываете, кажется законным.

Я не хочу, чтобы пользователь одновременно удалял эти объекты из внешних потоков, поэтому я хотел бы использовать пользовательское средство удаления, которое просто запланирует удаление.

Я предлагаю альтернативный подход: просто используйте один уровень общих указателей с пользовательским средством удаления.В устройстве удаления добавьте указатель в потокобезопасную очередь, которая будет уничтожена в правильном потоке.Простой подход - хранить уникальные указатели в очереди, а затем просто очищать очередь, чтобы освободить память.

0 голосов
/ 13 февраля 2019

Наличие двух объектов shared_ptr, владеющих одним и тем же объектом, будет работать некоторое время.Где это не будет работать - это то, где Object получено из std::enable_shared_from_this<Object>.В этом случае магия во время присвоения shared_ptr вызовет неопределенное поведение.

Конструкторы std :: shared_ptr обнаруживают наличие однозначного и доступного (начиная с C ++ 17) enable_shared_from_this baseи назначьте вновь созданный std :: shared_ptr внутренне сохраненной слабой ссылке, если он еще не принадлежит живому std :: shared_ptr (начиная с C ++ 17).Создание std :: shared_ptr для объекта, который уже управляется другим std :: shared_ptr, не будет обращаться к внутренне хранимой слабой ссылке и, следовательно, приведет к неопределенному поведению.

https://en.cppreference.com/w/cpp/memory/enable_shared_from_this

Я не хочу, чтобы пользователь одновременно удалял эти объекты из внешних потоков, поэтому я хотел бы использовать пользовательское средство удаления, которое просто запланирует удаление.

Решение будет зависеть отбудет ли операция очистки нуждаться в общем числе (т. е. дольше, чем один тик).

простой случай:

auto deleter = [&scheduler](Object* p)
{
    auto delete_impl = [p]()
    {
        p->cleanup();
        delete p;
    };
    scheduler.post(delete_impl);
};

auto po = std::shared_ptr<Object>(new Object(), deleter);

менее простой случай:

ВВ случае, когда очистка может занять больше времени, чем один «тик», мне не ясно из документации на cppreference, допустимо ли переназначить p другому shared_ptr<Object> для фазы очистки.Даже если это так, это такой темный угол, что я бы не стал доверять стандартизации поведения во всех реализациях библиотеки.

В целях безопасности давайте определим новый объект, который будет действовать как общий дескриптор во время очистки:

struct DyingObjectHandle : std::enable_shared_from_this<DyingObjectHandle>
{
  DyingObjectHandle(Object* p) : p(p) {}

  void cleanup()
  {
    auto self = shared_from_this();
    ... etc
  }

  void final_destroy()
  {
    delete p;
  }

  Object *p;
};

А затем измените средство удаления:

auto deleter = [&scheduler](Object* p)
{
    auto doh = std::make_shared<DyingObjectHandle>(p);
    scheduler.post([doh = std::move(doh)]()
    {
        doh->cleanup();
    });
};

auto po = std::shared_ptr<Object>(new Object(), deleter);

Наконец:

На самом деле библиотека является оболочкой для boost :: asio

Это часто является источником общей неэффективности.

Обычно asio::io_context следует рассматривать как одноэлементный объект для всего приложения.Он представляет собой «цикл планирования ввода-вывода для всего приложения».Максимальный параллелизм достигается, когда N потоков работают с одним и тем же io_context, каждый объект с поддержкой io имеет свой собственный strand, и все обработчики планируются через нити, например:

timer_.async_wait(asio::bind_executor(my_strand_, 
                  [self = shared_from_this()](error_code ec)
{
   // ...handle the timer.
}); 

Таким образом,не имеет значения, какой обработчик потока завершает.Если несколько параллельных операций происходят с одним и тем же объектом io, они будут сериализованы через цепочку более эффективно , чем если бы все они конкурировали на одном мьютексе или были связаны с io_context.

определенного потока.
0 голосов
/ 13 февраля 2019

Это законно.Единственное, что не разрешено, - это дважды удалить указанный объект.Вы можете предотвратить это, если один shared_ptr использует пользовательский удалитель.

Это хорошая практика?Будет ли проходить проверку кода?Это подняло бы брови?Вы сами решаете.

Я бы постарался не использовать подобные конструкции.

...