수동으로 증가하고 부스트를 감소시키고 :: shared_ptr?
-
18-09-2019 - |
문제
C ++에서 shared_ptr의 수를 수동으로 증가시키고 줄이는 방법이 있습니까?
내가 해결하려는 문제는 다음과 같습니다. C ++로 라이브러리를 작성하고 있지만 인터페이스는 순수한 C에 있어야합니다. 내부적으로 CONTRACE를 통해 원시 포인터를 전달할 수있는 기능을 유지하면서 Shared_PTR을 사용하여 메모리 관리를 단순화하고 싶습니다.
인터페이스를 통해 원시 포인터를 전달하면 참조 수를 증가시키고 싶습니다. 그런 다음 클라이언트는 더 이상 전달 된 객체가 필요하지 않을 때 참조 수를 줄이는 함수를 호출해야합니다.
해결책
당신의 제안에
그런 다음 고객은 카운터를 줄일 책임이 있습니다.
해당 고객이 메모리 관리에 대한 책임이 있고 귀하의 신뢰를 의미합니다. 나는 아직도 이유를 이해하지 못한다.
shared_ptr 카운터를 실제로 수정하는 것은 불가능합니다 ... (험, 마지막에 어떻게 ....
솔루션 1 : 고객에게 소유권을 완성하십시오
고객에게 포인터를 넘겨줍니다 (shared_ptr :: 릴리스) 그리고 호출 할 때 (또는 실제로 공유되지 않은 경우 객체를 삭제할 때) 소유권을 다시 전달할 것으로 기대합니다.
그것은 실제로 원시 포인터를 다룰 때 전통적인 접근법이며 여기에도 적용됩니다. 단점은 실제로 소유권을 공개한다는 것입니다 이 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을 해킹하는 것이 가능합니다. 그것을하는 방법에는 여러 가지가 있습니다.
그만큼 베스트 Way (그리고 가장 쉬운)는 단순히 파일을 다른 이름 (my_shared_ptr?) 아래로 복사 한 다음 :
- 포함 경비원을 변경하십시오
- 처음에 실제 shared_ptr을 포함하십시오
- 자신의 이름으로 shared_ptr의 인스턴스를 바꾸고 (속성에 액세스하기 위해 개인을 대중으로 변경)
- 충돌을 피하기 위해 실제 파일에 이미 정의 된 모든 내용을 제거하십시오.
이렇게하면 카운트에 액세스 할 수있는 자신의 shared_ptr을 쉽게 얻을 수 있습니다. C 코드가 카운터에 직접 액세스하는 데 문제가 해결되지는 않지만 내장으로 대체하려면 여기에서 코드를 '단순화'해야 할 수도 있습니다 (다중 스레드가 아니며 완전히 비참한 일이 발생합니다. 당신이있는 경우).
나는 의도적으로 'reinterpret_cast'트릭을 제외하고 포인터는 하나를 상쇄합니다. C/C ++에서 무언가에 불법적 인 액세스를 얻는 방법에는 여러 가지가 있습니다!
그래도 해킹을 사용하지 말라고 조언 해 주시겠습니까? 위에서 제시 한 두 가지 솔루션은 문제를 해결하기에 충분해야합니다.
다른 팁
어쩌면 당신은 boost :: shared_ptr accross dll 경계를 사용하고있을 것입니다. 이 경우 부스트 :: intrusive_ptr 도움이 될 수 있습니다. 이것은 오용의 일반적인 경우입니다 shared_ptr
사람들은 더러운 해킹으로 일하려고 노력합니다 ... 어쩌면 나는 당신의 경우에 틀렸을 수도 있지만 당신이하려는 일을할만한 충분한 이유가 없어야합니다 ;-)
2010 년 7 월 7 일 : Shared_PTR 자체보다 DLL로드/언 로딩에서 문제가 더 많이 발생하는 것 같습니다. 부스트 이론적 근거조차도 사건에 대해 많이 말하지 않습니다. boost::intrusive_ptr
선호해야합니다 shared_ptr
. .NET 개발로 전환 했고이 주제와 관련하여 TR1의 세부 사항을 따르지 않았 으므로이 답변이 더 이상 유효하지 않을 수 있다고 조심하십시오 ...
1. 손잡이?
최대 보안을 원한다면 사용자에게 포인터가 아닌 핸들을 제공합니다. 이렇게하면 그가 시도 할 방법이 없습니다. free
그것과 반 강력.
아래에서 Simplicity를 위해 사용자에게 객체 포인터를 줄 것입니다.
2. 획득하고 접속하지 않습니까?
Matthieu M에 설명 된대로 관리자 클래스를 만들어야합니다. 대답, 사용자가 획득/인수 한 것을 외우기 위해.
지옥 페이스가 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
사용자가 수업을 사용할 수 있습니다. C 사용자에게 일반적인 사용을 부과하지 않도록 도와주고 싶기 때문에 더미 구조물의 선언을 사용했습니다. void *
바늘. 그러나 사용 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에 대한 포인터는 무효입니다. Reneterpret_cast-inding을 원래 MyClass 유형으로 사용하지 않고 어떤 이유로 든 사용해서는 안됩니다 (Jaif의 참조. 대답 그것에 대한 자세한 내용은. C 사용자는 연관된 myStruct_* 함수와 함께 사용합니다.
이 코드도 클래스가 존재하는지 확인하십시오. 이것은 과잉 일 수 있지만 관리자가 가능할 수 있습니다 (아래 참조).
3. 관리자 정보
관리자는 Matthieu M.이 제안한 바와 같이 공유 포인터를 값으로 포함하는 맵 (및 포인터 자체 또는 핸들을 키로)을 보유합니다. 또는 사용자가 동일한 객체를 여러 번 획득 할 수있는 경우 멀티 맵.
관리자의 사용에 대한 좋은 점은 C ++ 코드가 사용자가 "접근하지 않은"객체를 추적 할 수 있다는 것입니다. __FILE__
그리고 __LINE__
버그 검색을 좁히는 데 도움이 될 수 있습니다).
따라서 관리자는 다음을 수행 할 수 있습니다.
- 존재하지 않는 물체를 자유롭게하지 않음 (C 사용자는 어떻게 하나를 얻었습니까?)
- 실행이 끝날 때 어떤 개체가 적용되지 않았는지 알 수 있습니다.
- 접근하지 않은 오브제의 경우 어쨌든 파괴하십시오 (Raii의 관점에서 좋은) 이것은 다소 악이지만, 당신은 이것을 제공 할 수 있습니다.
- 위의 코드에서 볼 수 있듯이 포인터를 감지하는 데 도움이 될 수도 있습니다.
여기에서 우려 사항을 분리해야합니다. 클라이언트가 원시 포인터로 통과하면 클라이언트는 메모리 관리에 대한 책임이 있습니다 (즉, 나중에 정리). 포인터를 만들면 메모리 관리에 대한 책임이 있습니다. 이것은 또한 다른 답변에서 언급 된 DLL 경계 문제에 도움이됩니다.
나는 IOCEMPLETIONPORTS 및 동시성 문제와 관련하여 이와 같은 것을 필요로하는 유스 케이스를 발견했습니다. 해킹하지만 표준 준수 방법은 다음과 같습니다 변호사 Herb Sutter가 설명한대로 여기.
다음 코드 스 니펫은 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();
}
YourTytype]를 카운트를 수정하는 데 필요한 객체 유형으로 교체하십시오. 이것은 매우 해킹되며 플랫폼 별 객체 이름을 사용합니다. 이 기능을 얻기 위해 겪어야 할 작업의 양은 아마도 아이디어가 얼마나 나쁜지를 나타냅니다. 또한 shared_ptr에서 납치 된 함수가 auto_ptr에서 가져 오기 때문에 auto_ptr로 게임을하고 있습니다.
또 다른 옵션은 refcount를 증가시키기 위해 shared_ptr의 사본을 동적으로 할당하고 그것을 줄이기 위해 그것을 거래하는 것입니다. 이를 통해 C API 클라이언트가 사용하는 동안 내 공유 객체가 파괴되지 않도록 보장합니다.
다음 코드 스 니펫에서는 shared_ptr을 제어하기 위해 excrement () 및 retend ()를 사용합니다. 이 예제의 단순성을 위해 초기 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
가능한 가장 빠른 동시 Lockless Manager (당신이 무엇을하고 있는지 알고 있다면).
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 ::지도는 빠르지 않으며 추가 검색, 추가 메모리, 스핀 잠금이 필요합니다. 그러나 손잡이 접근 방식으로 추가 안전을 부여합니다.
BTW, 수동 릴리스/획득이 포함 된 해킹은 속도 및 메모리 사용 측면에서 훨씬 더 나은 접근 방식 인 것 같습니다. C ++ STD는 C ++ 면도기 모양을 유지하기 위해 클래스에 이러한 기능을 추가합니다.