为什么std :: shared_ptr <void>有效

LiK*_*Kao 125 c++ shared-ptr c++11

我发现一些代码使用std :: shared_ptr在shutdown时执行任意清理.起初我认为这段代码不可行,但后来我尝试了以下内容:

#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;
}
Run Code Online (Sandbox Code Playgroud)

该程序给出了输出:

At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
Run Code Online (Sandbox Code Playgroud)

我有一些关于为什么这可能有用的想法,这与为G ++实现的std :: shared_ptrs的内部有关.由于这些对象将内部指针与计数器一起包装,因此来自std::shared_ptr<test>to的std::shared_ptr<void>强制转换可能不会阻碍析构函数的调用.这个假设是否正确?

当然还有一个更重要的问题:这可以保证按标准工作,还是可以进一步改变std :: shared_ptr的内部,其他实现实际上会破坏这段代码?

Dav*_*eas 95

诀窍是std::shared_ptr执行类型擦除.基本上,当shared_ptr创建一个new时,它将在内部存储一个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)
Run Code Online (Sandbox Code Playgroud)

shared_ptr从另一这样,当你构建的缺失者传递周围,复制(或默认的构造)shared_ptr<T>shared_ptr<U>上调用也是在传来传什么的析构函数的信息deleter.

  • @ user102008你不需要'std :: function',但它更灵活一点(可能在这里根本不重要),但如果你将'delete_deleter <T>'存储为',那么这不会改变类型擦除的工作方式您正在执行类型擦除的函数指针'void(void*)':T从存储的指针类型中消失. (2认同)

Ste*_*sop 34

shared_ptr<T> 逻辑上[*]有(至少)两个相关数据成员:

  • 指向要管理的对象的指针
  • 指向将用于销毁它的删除函数的指针.

根据shared_ptr<Test>你构造它的方式,你的删除函数是正常的Test,它将指针转换为Test*delete它.

当你把你shared_ptr<Test>进入的载体shared_ptr<void>,那些被复制,但是第一个被转化成void*.

因此,当使用最后一个引用来销毁vector元素时,它会将指针传递给正确销毁它的删除器.

它实际上比这更复杂,因为shared_ptr可以使用删除函数而不仅仅是函数,因此甚至可能存储每个对象数据而不仅仅是函数指针.但是对于这种情况,没有这样的额外数据,仅存储指向模板函数实例的指针就足够了,模板参数捕获必须删除指针的类型.

[*]逻辑上它可以访问它们 - 它们可能不是shared_ptr本身的成员,而是它指向的某个管理节点.

  • +1 提到删除器函数/函子被复制到其他 shared_ptr 实例中 - 在其他答案中遗漏了一条信息。 (4认同)
  • @ronag:如果使用适当的类型直接创建`shared_ptr`或者使用`make_shared`,则不需要虚拟析构函数.但是,仍然是一个好主意,因为指针的类型可以从构造改变,直到它存储在`shared_ptr`:`base*p = new derived; shared_ptr <base> sp(p);`,就`shared_ptr`而言,对象是`base`而不是`derived`,所以你需要一个虚析构函数.例如,这种模式可以与工厂模式相同. (2认同)

Mat*_* M. 10

它的工作原理是因为它使用了类型擦除.

基本上,当你构建一个时shared_ptr,它会传递一个额外的参数(如果你愿意,你可以实际提供),这是一个删除函数.

这个默认仿函数接受一个指向你在其中使用的类型的指针shared_ptr,因此void这里将它适当地转换为你test在这里使用的静态类型,并在这个对象上调用析构函数.

任何足够先进的科学感觉就像魔术一样,不是吗?


650*_*502 5

构造函数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*必须可以隐式转换为a T*,这当然是这种情况,T=void因为任何指针都可以被void*隐式转换.没有任何关于删除器的说法是无效的,所以规范确实要求这将正常工作.

从技术上讲,IIRC a shared_ptr<T>保存一个指向隐藏对象的指针,该对象包含引用计数器和指向实际对象的指针; 通过将删除器存储在这个隐藏的结构中,可以使这个显然神奇的功能工作,同时仍然保持shared_ptr<T>与常规指针一样大(但是取消引用指针需要双重间接

shared_ptr -> hidden_refcounted_object -> real_object
Run Code Online (Sandbox Code Playgroud)


Cas*_*Cow 5

我将使用用户能够理解的、非常简单的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
};
Run Code Online (Sandbox Code Playgroud)

现在我将创建两个名为 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>() );
}
Run Code Online (Sandbox Code Playgroud)

好的,这两个函数对于通过模板化函数创建共享指针时接下来会发生的情况至关重要。

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->
};
Run Code Online (Sandbox Code Playgroud)

请注意如果 T 为 void 并且 U 是您的“测试”类,上面会发生什么。它将使用指向 U 的指针而不是指向 T 的指针来调用 make_sp_counted_impl()。销毁的管理都是通过这里完成的。Shared_ptr_base 类管理与复制和赋值等相关的引用计数。shared_ptr 类本身管理运算符重载(->、* 等)的类型安全使用。

因此,尽管您有一个要 void 的shared_ptr,但在下面您正在管理传递给 new 的类型的指针。请注意,如果您在将指针放入shared_ptr之前将其转换为void*,则它将无法在checked_delete上编译,因此您实际上在那里也是安全的。