【问题标题】:Manually incrementing and decrementing a boost::shared_ptr?手动增加和减少 boost::shared_ptr?
【发布时间】:2010-12-01 18:05:18
【问题描述】:

有没有办法在 C++ 中手动增加和减少 shared_ptr 的计数?

我要解决的问题如下。我正在用 C++ 编写一个库,但接口必须是纯 C。在内部,我想使用 shared_ptr 来简化内存管理,同时保留通过 C 接口传递原始指针的能力。

当我通过接口传递一个原始指针时,我想增加引用计数。然后,客户端将负责调用一个函数,该函数将在不再需要传递的对象时减少引用计数。

【问题讨论】:

  • 我想不出任何好的理由这样做,除了隐藏另一个错误的存在......
  • @Dzhelil,你能解释一下你想在这里解决什么问题吗?

标签: c++ boost shared-ptr


【解决方案1】:

也许您正在跨 DLL 边界使用 boost::shared_ptr,这将无法正常工作。在这种情况下,boost::intrusive_ptr 可能会帮助您。这是滥用shared_ptr 的常见情况,人们试图解决肮脏的黑客行为......也许我在你的情况下错了,但应该没有充分的理由去做你想做的事情;-)

添加 07/2010:问题似乎更多来自 DLL 加载/卸载,而不是 shared_ptr 本身。即使是提升理由也没有说明boost::intrusive_ptr 应该优先于shared_ptr 的情况。我切换到 .NET 开发,并没有关注 TR1 关于这个主题的详细信息,所以请注意这个答案现在可能不再有效......

【讨论】:

    【解决方案2】:

    在你的建议中

    然后客户端将负责递减计数器。

    表示有问题的客户负责内存管理,并且您信任她。我还是不明白为什么。

    实际上无法修改 shared_ptr 计数器...(嗯,我将在最后解释如何...)但还有其他解决方案。

    解决方案 1:完全归客户所有

    将指针交给客户端(shared_ptr::release),并期望它在回调时将所有权传回给您(或者如果对象不是真正共享的,则简单地删除它)。

    这实际上是处理原始指针时的传统方法,它也适用于这里。缺点是您实际上释放了仅为此 shared_ptr 的所有权。如果对象实际上是共享的,那可能会带来不便……请耐心等待。

    解决方案 2:使用回调

    此解决方案意味着您始终保持所有权,并负责在客户需要时保持该对象的活动(和启动)。当客户端处理完对象后,您希望她告诉您,并在您的代码中调用一个回调来执行必要的清理。

    struct Object;
    
    class Pool // may be a singleton, may be synchronized for multi-thread usage
    {
    public:
      int accept(boost::shared_ptr<Object>); // adds ptr to the map, returns NEW id
      void release(int id) { m_objects.erase(id); }
    
    private:
      std::map< int, boost::shared_ptr<Object> > m_objects;
    }; // class Pool
    

    这样,您的客户端“递减”计数器实际上是您的客户端使用您使用的 id 调用回调方法,并且您删除了一个 shared_ptr :)

    黑客 boost::shared_ptr

    正如我所说的(因为我们在 C++ 中)实际上侵入 shared_ptr 是可能的。甚至有几种方法可以做到这一点。

    最好的方法(也是最简单的)就是将文件复制到另一个名称下(my_shared_ptr ?),然后:

    • 更改包含防护
    • 在开头包含真正的 shared_ptr
    • 用您自己的名称重命名 shared_ptr 的任何实例(并将私有更改为公共以访问属性)
    • 删除所有已在真实文件中定义的内容以避免冲突

    通过这种方式,您可以轻松获得自己的 shared_ptr,您可以访问它的计数。但是它并没有解决让 C 代码直接访问计数器的问题,您可能必须在这里“简化”代码以将其替换为内置的(如果您不是多线程的,则该代码可以工作,并且是彻头彻尾的灾难性如果你是)。

    我故意省略了“reinterpret_cast”技巧和指针偏移。有很多方法可以非法访问 C/C++ 中的某些内容!

    我可以建议你不要使用这些黑客吗?我上面介绍的两种解决方案应该足以解决您的问题。

    【讨论】:

    • +1。请注意,如果您将类指针提供给 C 客户端,他将无法delete 它。因此,您仍然需要提供 new_MyClassdelete_MyClass 之类的包装函数,使用 void * 或前向声明的虚拟结构类型。
    【解决方案3】:

    您应该在这里进行关注点分离:如果客户端传入原始指针,则客户端将负责内存管理(即事后清理)。如果您创建指针,您将负责内存管理。这也将帮助您解决另一个答案中提到的 DLL 边界问题。

    【讨论】:

      【解决方案4】:

      1。把手?

      如果你想要最大的安全性,给用户一个句柄,而不是指针。这样一来,他就不会尝试free它并成功一半。

      为了简单起见,我将在下面假设您将向用户提供对象指针。

      2。获取和取消获取?

      您应该创建一个管理器类,如 Matthieu M. 在他的 answer 中所述,以记住用户获得/未获得的内容。

      由于接口是 C,你不能指望他使用delete 或其他什么。所以,像这样的标题:

      #ifndef MY_STRUCT_H
      #define MY_STRUCT_H
      
      #ifdef __cplusplus
      extern "C"
      {
      #endif // __cplusplus
      
      typedef struct MyStructDef{} MyStruct ; // dummy declaration, to help
                                              // the compiler not mix types
      
      MyStruct * MyStruct_new() ;
      size_t     MyStruct_getSomeValue(MyStruct * p) ;
      void       MyStruct_delete(MyStruct * p) ;
      
      #ifdef __cplusplus
      }
      #endif // __cplusplus
      
      #endif // MY_STRUCT_H
      

      将使用户能够使用您的课程。我使用了一个 dummy struct 的声明,因为我想通过不强制他使用通用 void * 指针来帮助 C 用户。但是使用void * 仍然是一件好事。

      实现该功能的 C++ 源代码是:

      #include "MyClass.hpp"
      #include "MyStruct.h"
      
      MyManager g_oManager ; // object managing the shared instances
                             // of your class
      
      extern "C"
      {
      
      MyStruct * MyStruct_new()
      {
         MyClass * pMyClass = g_oManager.createMyClass() ;
         MyStruct * pMyStruct = reinterpret_cast<MyStruct *>(pMyClass) ;
         return pMyStruct ;
      }
      
      size_t MyStruct_getSomeValue(MyStruct * p)
      {
         MyClass * pMyClass = reinterpret_cast<MyClass *>(p) ;
      
         if(g_oManager.isMyClassExisting(pMyClass))
         {
            return pMyClass->getSomeValue() ;
         }
         else
         {
            // Oops... the user made a mistake
            // Handle it the way you want...
         }
      
         return 0 ;
      }
      
      void MyStruct_delete(MyStruct * p)
      {
         MyClass * pMyClass = reinterpret_cast<MyClass *>(p) ;
         g_oManager.destroyMyClass(pMyClass) ;
      }
      
      }
      

      请注意,指向 MyStruct 的指针是完全无效的。如果不将其重新解释为原始 MyClass 类型,则不应出于任何原因使用它(有关详细信息,请参阅 Jaif 的 answer。C 用户将仅将其与关联的 MyStruct_* 函数一起使用。

      还要注意,此代码验证该类确实存在。这可能是矫枉过正,但它是一个可能的使用经理(见下文)

      3。关于经理

      按照 Matthieu M. 的建议,管理器将持有一个映射,其中包含作为值的共享指针(以及作为键的指针本身或句柄)。或者一个多图,如果用户有可能以某种方式多次获取同一个对象。

      使用管理器的好处是您的 C++ 代码将能够跟踪用户没有正确“未获取”哪些对象(在获取/取消获取方法中添加信息,例如 __FILE__ 和 @987654330 @ 可以帮助缩小错误搜索范围)。

      因此经理将能够:

      1. 不释放一个不存在的对象(顺便说一句,C 用户是如何获得一个对象的?)
      2. 在执行结束时知道哪些对象未被获取
      3. 如果是未获得的对象,无论如何都要销毁它们(从 RAII 的角度来看这很好) 这有点邪恶,但你可以提供这个
      4. 如上面的代码所示,它甚至可以帮助检测未指向有效类的指针

      【讨论】:

        【解决方案5】:

        我遇到了一个用例,我确实需要这样的东西,与 IOCompletionPorts 和并发问题有关。按照 Herb Sutter here 的描述,律师是一种老套但符合标准的方法。

        以下代码 sn-p 用于 VC11 实现的 std::shared_ptr:

        实现文件:

        namespace {
            struct HackClass {
                std::_Ref_count_base *_extracted;
            };
        }
        
        template<>
        template<>
        void std::_Ptr_base<[YourType]>::_Reset<HackClass>(std::auto_ptr<HackClass> &&h) {
             h->_extracted = _Rep; // Reference counter pointer
        }
        
        std::_Ref_count_base *get_ref_counter(const std::shared_ptr<[YourType]> &p) {
             HackClass hck;
             std::auto_ptr<HackClass> aHck(&hck);
        
             const_cast<std::shared_ptr<[YourType]>&>(p)._Reset(std::move(aHck));
        
             auto ret = hck._extracted; // The ref counter for the shared pointer
                                        // passed in to the function
        
             aHck.release(); // We don't want the auto_ptr to call delete because
                             // the pointer that it is owning was initialized on the stack
        
             return ret;
        }
        
        void increment_shared_count(std::shared_ptr<[YourType]> &sp) {
             get_ref_counter(sp)->_Incref();
        }
        
        void decrement_shared_count(std::shared_ptr<[YourType]> &sp) {
             get_ref_counter(sp)->_Decref();
        }
        

        将 [YourType] 替换为您需要修改计数的对象类型。需要注意的是,这非常 hacky,并且使用特定于平台的对象名称。获得此功能所需的工作量可能表明它的想法有多糟糕。另外,我正在使用 auto_ptr 玩游戏,因为我从 shared_ptr 劫持的函数需要一个 auto_ptr。

        【讨论】:

          【解决方案6】:

          另一种选择是动态分配 shared_ptr 的副本,以增加 refcount,并释放它以减少它。这保证了我的共享对象在 C api 客户端使用时不会被破坏。

          在下面的代码 sn-p 中,我使用 increment() 和 decrement() 来控制一个 shared_ptr。为简单起见,我将初始 shared_ptr 存储在全局变量中。

          #include <iostream>
          #include <boost/shared_ptr.hpp>
          #include <boost/make_shared.hpp>
          #include <boost/scoped_ptr.hpp>
          using namespace std;
          
          typedef boost::shared_ptr<int> MySharedPtr;
          MySharedPtr ptr = boost::make_shared<int>(123);
          
          void* increment()
          {
              // copy constructor called
              return new MySharedPtr(ptr);
          }
          
          void decrement( void* x)
          {
              boost::scoped_ptr< MySharedPtr > myPtr( reinterpret_cast< MySharedPtr* >(x) );
          }
          
          int main()
          {
              cout << ptr.use_count() << endl;
              void* x = increment();
              cout << ptr.use_count() << endl;
              decrement(x);
              cout << ptr.use_count() << endl;
          
              return 0;
          }
          

          输出:

          1
          2
          1

          【讨论】:

          • 减量,delete reinterpret_cast&lt; MySharedPtr* &gt;(x); 不是更清楚吗?
          • @dzhelil 你是对的,它更清楚,但删除关键字在我的 C++ 词汇表中不存在 :)
          【解决方案7】:

          最快的并发无锁管理器(如果您知道自己在做什么)。

          template< class T >
          class shared_pool
          {
          public:
          
              typedef T value_type;
              typedef shared_ptr< value_type > value_ptr;
              typedef value_ptr* lock_handle;
          
          shared_pool( size_t maxSize ):
              _poolStore( maxSize )
          {}
          
          // returns nullptr if there is no place in vector, which cannot be resized without locking due to concurrency
          lock_handle try_acquire( const value_ptr& lockPtr ) {
              static value_ptr nullPtr( nullptr );
              for( auto& poolItem: _poolStore ) {
                  if( std::atomic_compare_exchange_strong( &poolItem, &nullPtr, lockPtr ) ) {             
                      return &poolItem;
                  }
              }
              return nullptr;
          }
          
          
          lock_handle acquire( const value_ptr& lockPtr ) {
              lock_handle outID;
              while( ( outID = try_acquire( lockPtr ) ) == nullptr ) {
                  mt::sheduler::yield_passive(); // ::SleepEx( 1, false );
              }
              return outID;
          }
          
          value_ptr release( const lock_handle& lockID ) {
              value_ptr lockPtr( nullptr );
              std::swap( *lockID, lockPtr);
              return lockPtr;
          }
          
          protected:
          
              vector< value_ptr > _poolStore;
          
          };
          

          std::map 不是那么快,需要额外的搜索、额外的内存、自旋锁定。 但它通过手柄方法提供了额外的安全性。

          顺便说一句,手动释放/获取似乎是一种更好的方法(在速度和内存使用方面)。 C++ std 最好在他们的类中添加这样的功能,只是为了保持 C++ 剃刀形状。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-10
            相关资源
            最近更新 更多