独立 shared_ptrs 指向同一个对象是否合法?

Is it legal to have independent shared_ptrs pointing to same object?

许多关于 shared_ptr 的文章警告不要意外地为同一个对象创建独立的 shared_ptr。 例如,this article。它有评论 // 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 上完美运行,但这样做合法,即标准是否允许这种用法?

这是合法的。唯一不合法的是双重删除指向的对象。你可以让一个 shared_ptr 使用自定义删除器来防止这种情况。

这是个好习惯吗?它会通过代码审查吗?它会引起人们的注意吗?你自己决定。

我会尽量不使用这样的结构。

您显示的似乎是合法的。

I don't want user to concurrently delete those objects from outside threads, so I'd like to use custom deleter that will merely schedule deleting.

我建议使用替代方法:只需将一级共享指针与自定义删除器一起使用。在删除器中,将指针添加到线程安全队列中,以便在正确的线程中销毁。一种简单的方法是在队列中存储唯一指针,然后简单地清除队列以释放内存。

让两个 shared_ptr 对象拥有同一个对象有时会奏效。它不起作用的地方是 Objectstd::enable_shared_from_this<Object> 派生的地方。在这种情况下,分配给 shared_ptr 期间的魔法将导致您出现未定义的行为。

The constructors of std::shared_ptr detect the presence of an unambiguous and accessible (since C++17) enable_shared_from_this base and assign the newly created std::shared_ptr to the internally stored weak reference if not already owned by a live std::shared_ptr (since C++17). Constructing a std::shared_ptr for an object that is already managed by another std::shared_ptr will not consult the internally stored weak reference and thus will lead to undefined behavior.

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

I don't want user to concurrently delete those objects from outside threads, so I'd like to use custom deleter that will merely schedule deleting.

解决方案将取决于清理操作是否需要共享计数(即是否需要超过一个滴答)。

简单案例:

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);

不太简单的案例:

如果清理时间可能比一个 'tick' 长,我无法从 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);

最后:

Actually library is a wrapper around boost::asio

这通常是常见的低效率来源。

asio::io_context 通常应该被认为是整个应用程序的单例对象。它代表"the application-wide IO scheduling loop"。当 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 更有效 ].