为什么 std::shared_ptr<void> 有效
Why do std::shared_ptr<void> work
我找到了一些使用 std::shared_ptr 的代码在关机时执行任意清理。起初我认为这段代码不可能工作,但后来我尝试了以下内容:
#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;
}
该程序给出输出:
At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
我对为什么这可能起作用有一些想法,这与为 G++ 实现的 std::shared_ptrs 的内部结构有关。由于这些对象将内部指针与计数器包装在一起,因此从 std::shared_ptr<test>
转换为 std::shared_ptr<void>
可能不会妨碍析构函数的调用。这个假设正确吗?
当然,更重要的问题:这是否保证按照标准工作,或者可能会进一步更改 std::shared_ptr 的内部结构,其他实现实际上会破坏此代码?
诀窍是std::shared_ptr
执行类型擦除。基本上,当创建一个新shared_ptr
时,它将在内部存储一个deleter
函数(可以作为参数提供给构造函数,但如果没有,则默认调用delete
)。当shared_ptr
被销毁时,它会调用该存储函数,这将调用deleter
。
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)
当从另一个shared_ptr
复制(或默认构造)时,删除器会四处传递,因此当您从shared_ptr<U>
构造shared_ptr<T>
时,有关要调用的析构函数的信息也会在deleter
中传递。
shared_ptr<T>
逻辑上[*](至少)有两个相关的数据成员:
- 指向所管理对象的指针
- 指向将用于销毁它的删除程序函数的指针。
shared_ptr<Test>
的删除器函数,考虑到您构造它的方式,是Test
的正常功能,它将指针转换为Test*
并delete
它。
当您将shared_ptr<Test>
推入 shared_ptr<void>
的向量时,这两个都被复制,尽管第一个被转换为 void*
。
因此,当 vector 元素被销毁并带有最后一个引用时,它会将指针传递给正确销毁它的删除器。
它实际上比这稍微复杂一些,因为shared_ptr
可以采用删除器函子而不仅仅是函数,因此甚至可能存储每个对象的数据,而不仅仅是函数指针。但是对于这种情况,没有这样的额外数据,只需存储指向模板函数实例化的指针就足够了,模板参数捕获必须通过其删除指针的类型。
[*] 从逻辑上讲,它有权访问它们 - 它们可能不是shared_ptr本身的成员,而是它指向的某个管理节点。
它之所以有效,是因为它使用类型擦除。
基本上,当你构建一个shared_ptr
时,它会传递一个额外的参数(如果你愿意,你可以实际提供),那就是删除器函子。
此默认函子接受指向您在shared_ptr
中使用的类型的指针作为参数,因此void
此处,将其适当地强制转换为您在此处test
使用的静态类型,并在此对象上调用析构函数。
任何足够先进的科学都感觉像魔术,不是吗?
函数shared_ptr<T>(Y *p)
似乎确实在调用shared_ptr<T>(Y *p, D d)
其中d
是对象的自动生成的删除器。
发生这种情况时,Y
对象的类型是已知的,因此此shared_ptr
对象的删除器知道要调用哪个析构函数,并且当指针存储在 shared_ptr<void>
的向量中时,此信息不会丢失。
事实上,规范要求接收shared_ptr<T>
对象接受shared_ptr<U>
对象必须是真的,并且U*
必须隐式转换为T*
,T=void
当然也是如此,因为任何指针都可以隐式转换为void*
。没有关于无效的删除器的任何说明,因此规范确实要求这将正常工作。
从技术上讲,IIRC shared_ptr<T>
保存指向隐藏对象的指针,该指针包含引用计数器和指向实际对象的指针;通过将删除器存储在这个隐藏结构中,可以使这个明显的神奇功能工作,同时仍然保持shared_ptr<T>
与常规指针一样大(但是取消引用指针需要双间接寻址。
shared_ptr -> hidden_refcounted_object -> real_object
我将使用用户将理解的非常简单的shared_ptr实现来回答这个问题(2 年后)。
首先,我将参加一些副类,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
};
现在我将创建两个名为 make_sp_counted_impl 的"free"函数,它将返回指向新创建的函数的指针。
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>() );
}
好的,这两个函数对于通过模板化函数创建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->
};
请注意,如果 T 为 void,U 是您的"测试"类,则上面会发生什么。它将使用指向 U 的指针调用 make_sp_counted_impl(),而不是指向 T 的指针。销毁的管理都是通过这里完成的。shared_ptr_base类管理有关复制和分配等的引用计数。shared_ptr类本身管理运算符重载(->、* 等)的类型安全使用。
因此,尽管您有一个要取消的shared_ptr,但在下面,您正在管理您传递给 new 的类型的指针。请注意,如果在将指针放入shared_ptr之前将指针转换为 void*,它将无法在checked_delete上编译,因此您实际上在那里也是安全的。
Test*
可以隐式转换为void*
,因此shared_ptr<Test>
可以从内存隐式转换为shared_ptr<void>
。这是有效的shared_ptr
因为它旨在控制运行时的销毁,而不是编译时,它们将在内部使用继承来调用适当的析构函数,就像在分配时一样。
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 在c++类上调用void函数
- 为什么野牛仍在使用"int yylex(void)",却找不到"int yylex(YYS
- 在派生函数中指定void*参数
- C++为什么尽管我调用了void函数,它却不起作用
- 如何从void函数输出字符串
- 我应该使用什么来代替void作为变体中的替代类型之一
- 奇怪的结构&GCC&clang(void*返回类型)
- Arduino:for/while/if在void setup()或void loop()之前?——错误:之前需要不合格
- 为什么这个函数将"const char*"转换为"void* const"而不是"const void*"
- 引用一个已擦除类型(void*)的指针
- 将尾部调用void(i32,..)位转换为llvm::函数以获取FnAttribute
- 库函数需要一个 std::function<void(void)>,如何传入类函数?
- 如何将指针从一个void函数传递到另一个C++
- 为什么我在使用void函数时得到错误代码C2276
- EASTL矢量<向量<int>>连续的
- void*到驱动程序中的UnicodeString
- 为什么在逗号分隔符上下文中将预增量的结果强制转换为void
- 指针没有更新它在void函数内部指向的值
- 不能将 "void *" 类型的值分配给类型 "TCHAR" 的实体