为什么 share_ptr < void > 是合法的,而 only_ptr < void > 是格式不正确的?

这个问题很符合标题: 我很想知道造成这种差异的技术原因是什么,还有理由是什么?

std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
21606 次浏览

这是因为 std::shared_ptr实现了类型擦除,而 std::unique_ptr没有。


由于 std::shared_ptr实现了类型擦除,所以它也支持 另一个感兴趣的属性,即 没有需要类模板中删除器 as template type argument的类型。看看他们的宣言:

template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;

其类型参数为 Deleter,而

template<class T>
class shared_ptr;

没有。

So, why does shared_ptr implement type-erasure?

它确实这样做了,因为它必须支持引用计数,为了支持引用计数,它必须从堆中分配内存,而且由于 必须的无论如何都会分配内存,因此它更进一步,实现了类型擦除ーー这也需要从堆中分配内存。So basically it is just being opportunist!

因为类型擦除,std::shared_ptr能够支持两件事情:

  • 它可以将任何类型的对象存储为 void*然而,它仍然能够正确地删除销毁对象 < strong > 通过正确地 调用它们的销毁函数
  • Deleter 的类型不会作为类型参数传递给类模板,这意味着有一点自由度 而不影响类型安全

好了,这就是 std::shared_ptr的工作原理。

Now the question is, can std::unique_ptr store objects 作为 void*? Well, the answer is, 是的 — provided you pass a suitable deleter as argument. Here is one such demonstration:

int main()
{
auto deleter = [](void const * data ) {
int const * p = static_cast<int const*>(data);
std::cout << *p << " located at " << p <<  " is being deleted";
delete p;
};
    

std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);
    

} //p will be deleted here, both p ;-)

输出(在线演示) :

959 located at 0x18aec20 is being deleted

You asked a very interesting question in the comment:

在我的例子中,我需要一个类型擦除删除器,但它似乎也是可能的(代价是一些堆分配)。基本上,这是否意味着第三类智能指针实际上有一个小众点: 一个具有类型擦除功能的独占所有权智能指针。

@Steve Jessop对此提出了以下解决方案,

我实际上从来没有尝试过这个,但是也许你可以通过使用一个合适的 std::function作为 unique_ptr的删除类型来实现它?假设它真的有效,那么就完成了,独占所有权和一个类型擦除删除器。

根据这个建议,我实现了这个(尽管它没有使用 std::function,因为它看起来没有必要) :

using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;


template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const * data) {
T const * p = static_cast<T const*>(data);
std::cout << "{" << *p << "} located at [" << p <<  "] is being deleted.\n";
delete p;
});
}


int main()
{
auto p1 = unique_void(new int(959));
auto p2 = unique_void(new double(595.5));
auto p3 = unique_void(new std::string("Hello World"));
}

输出(在线演示) :

{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.

希望能帮上忙。

One of the rationales is in one of the many use-cases of a shared_ptr - namely as a lifetime indicator or sentinel.

最初的提升文档中提到了这一点:

auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
auto closure_target = { closure, std::weak_ptr<void>(pv) };
...
// store the target somewhere, and later....
}


void call_closure(closure_target target)
{
// test whether target of the closure still exists
auto lock = target.sentinel.lock();
if (lock) {
// if so, call the closure
target.closure();
}
}

closure_target是这样的:

struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};

调用者会注册一个类似下面这样的回调:

struct active_object : std::enable_shared_from_this<active_object>
{
void start() {
event_emitter_.register_callback([this] { this->on_callback(); },
shared_from_this());
}


void on_callback()
{
// this is only ever called if we still exist
}
};

因为 shared_ptr<X>总是可以转换为 shared_ptr<void>,所以 event _ emitter 现在可以完全不知道它正在调用回的对象的类型。

这种安排释放了事件发送者的订阅者,使其能够处理交叉情况(如果队列中的回调在 active _ object 消失时等待被操作,该怎么办?),也意味着不需要同步取消订阅。weak_ptr<void>::lock是一个同步操作。