手动递增和递减boost :: shared_ptr?

时间:2009-09-27 04:39:16

标签: c++ boost shared-ptr

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

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

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

7 个答案:

答案 0 :(得分:10)

也许你正在使用boost :: shared_ptr跨越DLL边界,什么行不通。在这种情况下,boost::intrusive_ptr可能会帮助您。这是一个普遍的情况,误用shared_ptr人试图解决肮脏的黑客...也许我的错在你的情况下,但没有充分的理由做你想做的事情; - )

ADDED 07/2010:问题似乎来自DLL加载/卸载,而不是来自shared_ptr本身。甚至提升理由也没有说明boost::intrusive_ptr应优先于shared_ptr的情况。我转而使用.NET开发并没有按照TR1关于这个主题的细节,所以要注意这个答案可能现在不再有效......

答案 1 :(得分:7)

在你的建议中

  

然后,客户将负责减少计数器。

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

实际修改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:)

黑客提升:: shared_ptr

正如我所说的那样(因为我们使用的是C ++)可以实际入侵shared_ptr。甚至有几种方法可以做到。

最佳方式(最简单)只是将文件复制到另一个名称(my_shared_ptr?)然后:

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

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

我故意省略了'reinterpret_cast'技巧,指针偏移了。有很多方法可以在C / C ++中非法访问某些东西!

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

答案 2 :(得分:3)

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

将使用户能够使用您的课程。我使用了虚拟结构的声明,因为我想通过不强制使用泛型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__的获取/取消方法中添加信息, __LINE__可以帮助缩小错误搜索范围。)

因此经理将能够:

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

以下代码片段适用于由VC11实现的std :: shared_ptr:

Impl文件:

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。

答案 5 :(得分:1)

另一种选择是仅动态分配shared_ptr的副本,以便递增引用计数,并释放它以便减少它。这保证了我的共享对象在C api客户端使用时不会被销毁。

在下面的代码片段中,我使用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

答案 6 :(得分:0)

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

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不是那么快,需要额外的搜索,额外的内存,自旋锁定。 但它通过手柄方式提供额外的安全性。

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