문제

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__ 버그 검색을 좁히는 데 도움이 될 수 있습니다).

따라서 관리자는 다음을 수행 할 수 있습니다.

  1. 존재하지 않는 물체를 자유롭게하지 않음 (C 사용자는 어떻게 하나를 얻었습니까?)
  2. 실행이 끝날 때 어떤 개체가 적용되지 않았는지 알 수 있습니다.
  3. 접근하지 않은 오브제의 경우 어쨌든 파괴하십시오 (Raii의 관점에서 좋은) 이것은 다소 악이지만, 당신은 이것을 제공 할 수 있습니다.
  4. 위의 코드에서 볼 수 있듯이 포인터를 감지하는 데 도움이 될 수도 있습니다.

여기에서 우려 사항을 분리해야합니다. 클라이언트가 원시 포인터로 통과하면 클라이언트는 메모리 관리에 대한 책임이 있습니다 (즉, 나중에 정리). 포인터를 만들면 메모리 관리에 대한 책임이 있습니다. 이것은 또한 다른 답변에서 언급 된 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 ++ 면도기 모양을 유지하기 위해 클래스에 이러한 기능을 추가합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top