Наличие двух объектов 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
.
определенного потока.