質問

C++でshared_ptrのカウントを手動で増減させる方法はありますか?

私が解決しようとしている問題は次のとおりです。C++ でライブラリを作成していますが、インターフェイスは純粋な C である必要があります。内部的には、shared_ptr を使用して、C インターフェイスを介して生のポインタを渡す機能を維持しながら、メモリ管理を簡素化したいと考えています。

インターフェイスを介して生のポインターを渡すときに、参照カウントをインクリメントしたいと考えています。クライアントは、渡されたオブジェクトが必要なくなったときに参照カウントをデクリメントする関数を呼び出す必要があります。

役に立ちましたか?

解決

あなたの提案では

その後、クライアントはカウンターをデクリメントする必要があります。

これは、問題のクライアントがメモリ管理の責任を負っており、あなたが彼女を信頼していることを意味します。理由はまだわかりません。

実際に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を1つ削除することになります:)

boost::shared_ptr のハッキング

先ほども述べたように、(C++ を使用しているため) 実際にshared_ptr をハッキングすることが可能です。それにはいくつかの方法があります。

最高 方法 (そして最も簡単な方法) は、単にファイルを別の名前 (my_shared_ptr ?) でコピーしてから次のようにすることです。

  • インクルードガードを変更する
  • 実際のshared_ptrを先頭に含めます
  • 任意のshared_ptrインスタンスの名前を自分の名前に変更します(属性にアクセスするにはプライベートをパブリックに変更します)。
  • 衝突を避けるために、実際のファイルですでに定義されているものをすべて削除します。

このようにして、独自のshared_ptrを簡単に取得し、その数にアクセスできます。ただし、C コードがカウンターに直接アクセスするという問題は解決しません。ここでコードを「単純化」して組み込みコードに置き換える必要があるかもしれません (マルチスレッドでない場合は機能しますが、これは実に悲惨です)あなたがいる場合)。

私は意図的に「reinterpret_cast」トリックを省略し、ポインターでオフセットします。C/C++ では、何かに不正にアクセスする方法が非常にたくさんあります。

ただし、このハックは使用しないようにアドバイスしてもいいでしょうか?問題を解決するには、上で紹介した 2 つの解決策で十分です。

他のヒント

たぶん、あなたはブーストを使用している:: DLLの境界accrossのshared_ptrを、どのような正常に動作しません。この場合、後押し:: にはあなたを助けるかもしれないintrusive_ptrでる。これはshared_ptr人の誤用の一般的なケースである汚いハックを回避しよう...たぶん私はあなたのケースでは間違っているが、あなたがやろう何を行うには十分な理由があってはならない; - )

07/2010を追加しました:問題はshared_ptrの自体からよりも、DLLのロード/アンロードからより多くの来ているようです。 boost::intrusive_ptrshared_ptrよりも優先されなければならない場合であってもブースト根拠はケースについて多くを教えてくれありません。私は、.NET開発に切り替えると、このトピックに関するTR1の詳細は次のとおりこの答えは、今はもう有効ではない可能性があります注意していなかった...

1.ハンドル?

最大限のセキュリティが必要な場合は、ユーザーにポインターではなくハンドルを与えます。この方法では、彼がそうしようとする方法はありません free それは半分成功します。

以下では、わかりやすくするために、ユーザーにオブジェクト ポインターを与えると仮定します。

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 へのポインタは明らかに無効であることに注意してください。いかなる理由であっても、元の MyClass 型に reinterpret_cast することなく使用しないでください (Jaif のを参照してください)。 答え 詳細については。C ユーザーは、関連する MyStruct_* 関数でのみこれを使用します。

このコードはクラスが存在することを確認していることにも注意してください。これはやりすぎかもしれませんが、マネージャーを使用する可能性があります (下記を参照)

3.マネージャーについて

Matthieu M. が提案したように、マネージャーは、値として共有ポインター (およびキーとしてポインター自体またはハンドル) を含むマップを保持します。または、ユーザーが何らかの方法で同じオブジェクトを複数回取得できる場合は、マルチマップ。

マネージャーを使用することの良い点は、C++ コードがユーザーによって正しく「未取得」にされなかったオブジェクトをトレースできることです (次のような取得/未取得メソッドに情報を追加します)。 __FILE__ そして __LINE__ バグ検索を絞り込むのに役立ちます)。

したがって、マネージャーは次のことができるようになります。

  1. 存在しないオブジェクトを解放してはなりません (ところで、C ユーザーはどうやってオブジェクトを取得できたのでしょうか?)
  2. 実行の終了時にどのオブジェクトが未要求であったかを知る
  3. 獲得していないオブジェの場合、とにかくそれらを破壊します(これはRaiiの観点からは良いです)これはやや邪悪ですが、これを提供できます
  4. 上記のコードに示すように、ポインターが有効なクラスを指していないことを検出するのにも役立ちます。

現在の関心事の分離を行う必要があります。クライアントは、生のポインタを渡した場合、クライアントは、(すなわち、その後のクリーンアップ)、メモリ管理のための責任を負うことになります。あなたはポインタを作成する場合は、メモリ管理のための責任を負うことになります。これはまた別の答えで言及されたDLLの境界問題のお手伝いをします。

私はIOCompletionPortsと並行性の問題に関連し、このようなものを、必要としなかったユースケースに出くわしました。ハックしかし、標準に準拠する方法は、の弁護士にあるのそれハーブサッターここで説明したようにのます。

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]交換してください。かなりハックであることに注意することが重要であり、プラットフォーム固有のオブジェクト名を使用しています。あなたはこの機能を取得するために通過しなければならない仕事の量は、おそらくどのように悪いことがあるという考えを示すものです。私はshared_ptrのからハイジャックしています関数はauto_ptrを取り込んでいるのでまた、私はauto_ptrをしてゲームをプレイしています。

別のオプションは、単に参照カウントをインクリメントし、それをデクリメントするためにそれを解放するために、動的のshared_ptrのコピーを割り当てることであろう。これは、C APIクライアントで使用されている間、私の共有オブジェクトが破壊されないことを保証します。

は、以下のコードでは、私は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

最速同時ロックレスマネージャー(あなたは何をしているかを知っている場合)。

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 ::マップはそれほど速くない、追加の検索、追加メモリ、スピンロックを必要とします。 しかし、それはハンドルのアプローチで、余分な安全性を付与します。

ところで、手動解除/取得してハック(速度やメモリ使用量の面で)よりよい方法であると思われます。 C ++のstdは、より良いだけでC ++カミソリ状を保つために、授業中にこのような機能を追加します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top