独立 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 对象拥有同一个对象有时会奏效。它不起作用的地方是 Object
从 std::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
更有效 ].
许多关于 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 对象拥有同一个对象有时会奏效。它不起作用的地方是 Object
从 std::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
更有效 ].