有没有办法在C ++中手动递增和递减shared_ptr的计数?
我想解决的问题如下。我正在用C ++编写一个库,但接口必须是纯C.在内部,我想使用shared_ptr来简化内存管理,同时保留通过C接口传递原始指针的能力。
当我通过接口传递原始指针时,我想增加引用计数。然后,客户端将负责调用一个函数,该函数将在不再需要传递的对象时减少引用计数。
答案 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,您可以访问该计数。它没有解决让C代码直接访问计数器的问题,你可能必须在这里“简化”代码以通过内置替换它(如果你不是多线程的话,这是有效的,并且是彻头彻尾的灾难性的如果你是。)
我故意省略了'reinterpret_cast'技巧,指针偏移了。有很多方法可以在C / C ++中非法访问某些东西!
我可以建议你不要使用黑客吗?我上面介绍的两个解决方案应该足以解决您的问题。
答案 2 :(得分:3)
如果您想要最大安全性,请为用户提供句柄,而不是指针。通过这种方式,他无法尝试free
和半成功。
我将在下面假设,为简单起见,您将为用户提供对象指针。
你应该创建一个经理类,如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_ *函数一起使用。
另请注意,此代码验证该类是否存在。这可能是矫枉过正,但可能会使用经理(见下文)
根据Matthieu M.的建议,经理将持有一个包含共享指针作为值(以及指针本身或句柄作为键)的映射。或者是多图,如果用户可能以某种方式多次获取同一个对象。
使用管理器的好处是,您的C ++代码将能够跟踪用户未正确“未获取”的对象(在诸如__FILE__
的获取/取消方法中添加信息, __LINE__
可以帮助缩小错误搜索范围。)
因此经理将能够:
答案 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 ++剃刀形状。