std::unique_ptr 和 std::shared_ptr 之间破坏行为差异的基本原理是什么?
What is the rationale for the difference in destruction behavior between std::unique_ptr and std::shared_ptr?
来自 http://en.cppreference.com/w/cpp/memory/unique_ptr:
If T
is derived class (sic) of some base B
, then std::unique_ptr<T>
is
implicitly convertible to std::unique_ptr<B>
. The default deleter of
the resulting std::unique_ptr<B>
will use operator delete for B
,
leading to undefined behavior unless the destructor of B
is virtual.
Note that std::shared_ptr
behaves differently: std::shared_ptr<B>
will
use the operator delete for the type T
and the owned object will be
deleted correctly even if the destructor of B
is not virtual.
上述破坏行为差异的基本原理是什么?我最初的猜测是性能?
同样有趣的是 std::shared_ptr<B>
如何能够调用类型 T
的析构函数,以防 B
上的析构函数是非虚拟的并且不能被调用据我所知 std::shared_ptr<B>
?
的上下文
std::shared_ptr<X>
已经比原始 B*
.
有很多开销
一个shared_ptr<X>
基本上维护了4个东西。它维护一个指向B
的指针,它维护两个引用计数(一个"hard"引用计数,一个"soft"引用计数weak_ptr
),它维护一个清理函数.
清理功能是 shared_ptr<X>
行为不同的原因。当您创建 shared_ptr<X>
时,将创建一个调用该特定类型的析构函数的函数,并将其存储在由 shared_ptr<X>
.
管理的清理函数中
当您更改托管类型(B*
变为 C*
)时,清理功能保持不变。
因为shared_ptr<X>
需要管理引用计数,所以清理函数存储的额外开销是微不足道的。
对于 unique_ptr<B>
,class 几乎与原始 B*
一样便宜。它保持 B*
以外的零状态,其行为(在破坏时)归结为 if (b) delete b;
。 (是的,if (b)
是多余的,但优化器可以解决这个问题)。
为了支持 cast-to-base 和 delete-as-derived,必须存储额外的状态以记住 unique_ptr
是 really派生class。这可以采用存储的指向删除器的指针的形式,例如 shared_ptr
.
但是,这会使 unique_ptr<B>
的大小翻倍,或者需要它在堆上的某个地方存储数据。
决定 unique_ptr<B>
应该是零开销的,因此它不支持转换为基,同时仍然调用基的析构函数。
现在,您可能可以教 unique_ptr<B>
通过简单地添加一个删除器类型并存储一个知道它正在销毁的事物类型的销毁函数来做到这一点。上面一直在讲unique_ptr
的默认删除器,无状态,琐碎
struct deleter {
void* state;
void(*f)(void*);
void operator()(void*)const{if (f) f(state);}
deleter(deleter const&)=default;
deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
deleter()=delete;
template<class T>
deleter(T*t):
state(t),
f([](void*p){delete static_cast<T*>(p);})
{}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;
template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique( Args&&... args ) {
T* t = new T(std::forward<Args>(args)...);
return { t, t };
}
live example,在这里我生成一个指向派生的唯一指针,将它存储在指向基的唯一指针中,然后重置基。派生指针被删除。
( 一个简单的 void(*)(void*)
删除器可能 运行 会导致传入 void*
的值在基本情况和派生情况之间有所不同。)
请注意,在不更改删除器的情况下更改存储在此类 unique_ptr
中的指针将导致不明智的行为。
来自 http://en.cppreference.com/w/cpp/memory/unique_ptr:
If
T
is derived class (sic) of some baseB
, thenstd::unique_ptr<T>
is implicitly convertible tostd::unique_ptr<B>
. The default deleter of the resultingstd::unique_ptr<B>
will use operator delete forB
, leading to undefined behavior unless the destructor ofB
is virtual. Note thatstd::shared_ptr
behaves differently:std::shared_ptr<B>
will use the operator delete for the typeT
and the owned object will be deleted correctly even if the destructor ofB
is not virtual.
上述破坏行为差异的基本原理是什么?我最初的猜测是性能?
同样有趣的是 std::shared_ptr<B>
如何能够调用类型 T
的析构函数,以防 B
上的析构函数是非虚拟的并且不能被调用据我所知 std::shared_ptr<B>
?
std::shared_ptr<X>
已经比原始 B*
.
一个shared_ptr<X>
基本上维护了4个东西。它维护一个指向B
的指针,它维护两个引用计数(一个"hard"引用计数,一个"soft"引用计数weak_ptr
),它维护一个清理函数.
清理功能是 shared_ptr<X>
行为不同的原因。当您创建 shared_ptr<X>
时,将创建一个调用该特定类型的析构函数的函数,并将其存储在由 shared_ptr<X>
.
当您更改托管类型(B*
变为 C*
)时,清理功能保持不变。
因为shared_ptr<X>
需要管理引用计数,所以清理函数存储的额外开销是微不足道的。
对于 unique_ptr<B>
,class 几乎与原始 B*
一样便宜。它保持 B*
以外的零状态,其行为(在破坏时)归结为 if (b) delete b;
。 (是的,if (b)
是多余的,但优化器可以解决这个问题)。
为了支持 cast-to-base 和 delete-as-derived,必须存储额外的状态以记住 unique_ptr
是 really派生class。这可以采用存储的指向删除器的指针的形式,例如 shared_ptr
.
但是,这会使 unique_ptr<B>
的大小翻倍,或者需要它在堆上的某个地方存储数据。
决定 unique_ptr<B>
应该是零开销的,因此它不支持转换为基,同时仍然调用基的析构函数。
现在,您可能可以教 unique_ptr<B>
通过简单地添加一个删除器类型并存储一个知道它正在销毁的事物类型的销毁函数来做到这一点。上面一直在讲unique_ptr
的默认删除器,无状态,琐碎
struct deleter {
void* state;
void(*f)(void*);
void operator()(void*)const{if (f) f(state);}
deleter(deleter const&)=default;
deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
deleter()=delete;
template<class T>
deleter(T*t):
state(t),
f([](void*p){delete static_cast<T*>(p);})
{}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;
template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique( Args&&... args ) {
T* t = new T(std::forward<Args>(args)...);
return { t, t };
}
live example,在这里我生成一个指向派生的唯一指针,将它存储在指向基的唯一指针中,然后重置基。派生指针被删除。
( 一个简单的 void(*)(void*)
删除器可能 运行 会导致传入 void*
的值在基本情况和派生情况之间有所不同。)
请注意,在不更改删除器的情况下更改存储在此类 unique_ptr
中的指针将导致不明智的行为。