[英]Why do std::shared_ptr<void> work
I found some code using std::shared_ptr to perform arbitrary cleanup at shutdown.我发现一些代码使用 std::shared_ptr 在关机时执行任意清理。 At first I thought this code could not possibly work, but then I tried the following:
起初我认为这段代码不可能工作,但后来我尝试了以下方法:
#include <memory>
#include <iostream>
#include <vector>
class test {
public:
test() {
std::cout << "Test created" << std::endl;
}
~test() {
std::cout << "Test destroyed" << std::endl;
}
};
int main() {
std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>"
<< std::endl;
std::vector<std::shared_ptr<void>> v;
{
std::cout << "Creating test" << std::endl;
v.push_back( std::shared_ptr<test>( new test() ) );
std::cout << "Leaving scope" << std::endl;
}
std::cout << "Leaving main" << std::endl;
return 0;
}
This program gives the output:该程序给出输出:
At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
I have some ideas on why this might work, that have to do with the internals of std::shared_ptrs as implemented for G++.我对为什么这可能会起作用有一些想法,这与为 G++ 实现的 std::shared_ptrs 的内部结构有关。 Since these objects wrap the internal pointer together with the counter the cast from
std::shared_ptr<test>
to std::shared_ptr<void>
is probably not hindering the call of the destructor.由于这些对象将内部指针与计数器包装在一起,因此从
std::shared_ptr<test>
到std::shared_ptr<void>
的强制转换可能不会妨碍析构函数的调用。 Is this assumption correct?这个假设正确吗?
And of course the much more important question: Is this guaranteed to work by the standard, or might further changes to the internals of std::shared_ptr, other implementations actually break this code?当然还有更重要的问题:这是否保证符合标准,或者可能进一步更改 std::shared_ptr 的内部,其他实现实际上破坏了这段代码?
The trick is that std::shared_ptr
performs type erasure.诀窍是
std::shared_ptr
执行类型擦除。 Basically, when a new shared_ptr
is created it will store internally a deleter
function (which can be given as argument to the constructor but if not present defaults to calling delete
).基本上,当创建一个新的
shared_ptr
时,它将在内部存储一个deleter
函数(可以作为构造函数的参数给出,但如果不存在则默认调用delete
)。 When the shared_ptr
is destroyed, it calls that stored function and that will call the deleter
.当
shared_ptr
被销毁时,它会调用存储的函数,然后调用deleter
。
A simple sketch of the type erasure that is going on simplified with std::function, and avoiding all reference counting and other issues can be seen here:可以在此处看到使用 std::function 进行简化的类型擦除的简单草图,并避免所有引用计数和其他问题:
template <typename T>
void delete_deleter( void * p ) {
delete static_cast<T*>(p);
}
template <typename T>
class my_unique_ptr {
std::function< void (void*) > deleter;
T * p;
template <typename U>
my_unique_ptr( U * p, std::function< void(void*) > deleter = &delete_deleter<U> )
: p(p), deleter(deleter)
{}
~my_unique_ptr() {
deleter( p );
}
};
int main() {
my_unique_ptr<void> p( new double ); // deleter == &delete_deleter<double>
}
// ~my_unique_ptr calls delete_deleter<double>(p)
When a shared_ptr
is copied (or default constructed) from another the deleter is passed around, so that when you construct a shared_ptr<T>
from a shared_ptr<U>
the information on what destructor to call is also passed around in the deleter
.当
shared_ptr
从另一个复制(或默认构造)时,删除器会被传递,因此当您从shared_ptr<U>
构造shared_ptr<T>
时,有关要调用的析构函数的信息也会在deleter
中传递。
shared_ptr<T>
logically[*] has (at least) two relevant data members: shared_ptr<T>
logically[*] 有(至少)两个相关的数据成员:
The deleter function of your shared_ptr<Test>
, given the way you constructed it, is the normal one for Test
, which converts the pointer to Test*
and delete
s it.根据您的构造方式,
shared_ptr<Test>
的删除器函数是Test
的正常函数,它将指针转换为Test*
并delete
它。
When you push your shared_ptr<Test>
into the vector of shared_ptr<void>
, both of those are copied, although the first one is converted to void*
.当您将
shared_ptr<Test>
推送到shared_ptr<void>
的向量中时,这两个都会被复制,尽管第一个被转换为void*
。
So, when the vector element is destroyed taking the last reference with it, it passes the pointer to a deleter that destroys it correctly.因此,当向量元素被销毁时,它使用最后一个引用,它会将指针传递给正确销毁它的删除器。
It's actually a little more complicated than this, because shared_ptr
can take a deleter functor rather than just a function, so there might even be per-object data to be stored rather than just a function pointer.它实际上比这复杂一点,因为
shared_ptr
可以接受一个删除器函子而不仅仅是一个函数,因此甚至可能存储每个对象的数据而不仅仅是一个函数指针。 But for this case there is no such extra data, it would be sufficient just to store a pointer to an instantiation of a template function, with a template parameter that captures the type through which the pointer must be deleted.但是对于这种情况,没有这样的额外数据,只需存储指向模板函数实例化的指针就足够了,模板参数捕获必须删除指针的类型。
[*] logically in the sense that it has access to them - they may not be members of the shared_ptr itself but instead of some management node that it points to. [*] 从逻辑上讲,它可以访问它们——它们可能不是 shared_ptr 本身的成员,而是它指向的某个管理节点。
It works because it uses type erasure.它之所以有效,是因为它使用类型擦除。
Basically, when you build a shared_ptr
, it passes one extra argument (that you can actually provide if you wish), which is the deleter functor.基本上,当您构建
shared_ptr
时,它会传递一个额外的参数(如果您愿意,您可以实际提供),即删除器函子。
This default functor accepts as argument a pointer to type you use in the shared_ptr
, thus void
here, casts it appropriately to the static type you used test
here, and calls the destructor on this object.这个默认函子接受一个指向您在
shared_ptr
中使用的类型的指针作为参数,因此在这里为void
,将其适当地转换为您在此处使用的静态类型test
,并在此对象上调用析构函数。
Any sufficiently advanced science feels like magic, isn't it ?任何足够先进的科学都感觉像魔术,不是吗?
The constructor shared_ptr<T>(Y *p)
indeed seems to be calling shared_ptr<T>(Y *p, D d)
where d
is an automatically generated deleter for the object.构造函数
shared_ptr<T>(Y *p)
确实似乎正在调用shared_ptr<T>(Y *p, D d)
,其中d
是自动生成的对象删除器。
When this happens the type of the object Y
is known, so the deleter for this shared_ptr
object knows which destructor to call and this information is not lost when the pointer is the stored in a vector of shared_ptr<void>
.发生这种情况时,对象
Y
的类型是已知的,因此该shared_ptr
对象的删除器知道要调用哪个析构函数,并且当指针存储在shared_ptr<void>
的向量中时,此信息不会丢失。
Indeed the specs require that for a receving shared_ptr<T>
object to accept a shared_ptr<U>
object it must be true that and U*
must be implicitly convertible to a T*
and this is certainly the case with T=void
because any pointer can be converted to a void*
implicitly.实际上,规范要求接收
shared_ptr<T>
对象接受shared_ptr<U>
对象必须是真的,并且U*
必须可以隐式转换为T*
并且T=void
肯定是这种情况,因为任何指针可以隐式转换为void*
。 Nothing is said about the deleter that will be invalid so indeed the specs are mandating that this will work correctly.没有任何关于删除器无效的说明,因此规范确实要求这将正常工作。
Technically IIRC a shared_ptr<T>
holds a pointer to an hidden object that contains the reference counter and a pointer to the actual object;从技术上讲 IIRC 一个
shared_ptr<T>
持有一个指向隐藏对象的指针,该隐藏对象包含引用计数器和指向实际对象的指针; by storing the deleter in this hidden structure it's possible to make this apparently magic feature working while still keeping shared_ptr<T>
as big as a regular pointer (however dereferencing the pointer requires a double indirection通过将删除器存储在这个隐藏结构中,可以使这个看似神奇的功能发挥作用,同时仍然保持
shared_ptr<T>
与常规指针一样大(但是取消引用指针需要双重间接
shared_ptr -> hidden_refcounted_object -> real_object
I am going to answer this question (2 years later) using a very simplistic implementation of shared_ptr that the user will understand.我将使用用户将理解的 shared_ptr 的非常简单的实现来回答这个问题(2 年后)。
Firstly I am going to a few side classes, shared_ptr_base, sp_counted_base sp_counted_impl, and checked_deleter the last of which is a template.首先,我将学习一些辅助类,shared_ptr_base、sp_counted_base sp_counted_impl 和 checked_deleter,其中最后一个是模板。
class sp_counted_base
{
public:
sp_counted_base() : refCount( 1 )
{
}
virtual ~sp_deleter_base() {};
virtual void destruct() = 0;
void incref(); // increases reference count
void decref(); // decreases refCount atomically and calls destruct if it hits zero
private:
long refCount; // in a real implementation use an atomic int
};
template< typename T > class sp_counted_impl : public sp_counted_base
{
public:
typedef function< void( T* ) > func_type;
void destruct()
{
func(ptr); // or is it (*func)(ptr); ?
delete this; // self-destructs after destroying its pointer
}
template< typename F >
sp_counted_impl( T* t, F f ) :
ptr( t ), func( f )
private:
T* ptr;
func_type func;
};
template< typename T > struct checked_deleter
{
public:
template< typename T > operator()( T* t )
{
size_t z = sizeof( T );
delete t;
}
};
class shared_ptr_base
{
private:
sp_counted_base * counter;
protected:
shared_ptr_base() : counter( 0 ) {}
explicit shared_ptr_base( sp_counter_base * c ) : counter( c ) {}
~shared_ptr_base()
{
if( counter )
counter->decref();
}
shared_ptr_base( shared_ptr_base const& other )
: counter( other.counter )
{
if( counter )
counter->addref();
}
shared_ptr_base& operator=( shared_ptr_base& const other )
{
shared_ptr_base temp( other );
std::swap( counter, temp.counter );
}
// other methods such as reset
};
Now I am going to create two "free" function called make_sp_counted_impl which will return a pointer to a newly created one.现在我将创建两个名为 make_sp_counted_impl 的“免费”函数,它将返回一个指向新创建的指针。
template< typename T, typename F >
sp_counted_impl<T> * make_sp_counted_impl( T* ptr, F func )
{
try
{
return new sp_counted_impl( ptr, func );
}
catch( ... ) // in case the new above fails
{
func( ptr ); // we have to clean up the pointer now and rethrow
throw;
}
}
template< typename T >
sp_counted_impl<T> * make_sp_counted_impl( T* ptr )
{
return make_sp_counted_impl( ptr, checked_deleter<T>() );
}
Ok, these two functions are essential as to what will happen next when you create a shared_ptr through a templated function.好的,这两个函数对于通过模板函数创建 shared_ptr 时接下来会发生什么至关重要。
template< typename T >
class shared_ptr : public shared_ptr_base
{
public:
template < typename U >
explicit shared_ptr( U * ptr ) :
shared_ptr_base( make_sp_counted_impl( ptr ) )
{
}
// implement the rest of shared_ptr, e.g. operator*, operator->
};
Note what happens above if T is void and U is your "test" class.请注意,如果 T 为 void 并且 U 是您的“测试”类,则会发生上述情况。 It will call make_sp_counted_impl() with a pointer to U, not a pointer to T. The management of the destruction is all done through here.
它将使用指向 U 的指针而不是指向 T 的指针调用 make_sp_counted_impl()。销毁的管理都通过这里完成。 The shared_ptr_base class manages the reference counting with regards to copying and assignment etc. The shared_ptr class itself manages the typesafe use of operator overloads (->, * etc).
shared_ptr_base 类管理有关复制和赋值等的引用计数。 shared_ptr 类本身管理运算符重载(->、* 等)的类型安全使用。
Thus although you have a shared_ptr to void, underneath you are managing a pointer of the type you passed into new.因此,尽管您有一个要 void 的 shared_ptr,但在下面您正在管理您传递给 new 的类型的指针。 Note that if you convert your pointer to a void* before putting it into the shared_ptr, it will fail to compile on the checked_delete so you are actually safe there too.
请注意,如果您在将指针放入 shared_ptr 之前将其转换为 void*,它将无法在 checked_delete 上编译,因此您实际上也很安全。
Test*
is implicitly convertible to void*
, therefore shared_ptr<Test>
is implicitly convertible to shared_ptr<void>
, from memory. Test*
可以隐式转换为void*
,因此shared_ptr<Test>
可以从内存中隐式转换为shared_ptr<void>
。 This works because shared_ptr
is designed to control destruction at run-time, not compile-time, they will internally use inheritance to call the appropriate destructor as it was at allocation time.这是有效的,因为
shared_ptr
旨在控制运行时的破坏,而不是编译时,它们将在内部使用继承来调用适当的析构函数,就像在分配时一样。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.