5

C ++でshared_ptrのカウントを手動でインクリメントおよびデクリメントする方法はありますか?

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

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

4

7 に答える 7

10

たぶんあなたはDLLの境界を越えてboost::shared_ptrを使用していますが、これは正しく機能しません。この場合、boost::intrusive_ptrが役に立ちます。これは、汚いハックを回避しようとする人々の誤用の一般的なケースshared_ptrです...多分私はあなたのケースでは間違っていますが、あなたがやろうとしていることをする正当な理由はないはずです;-)

2010年7月追加:問題は、shared_ptr自体からではなく、DLLのロード/アンロードに起因するようです。boost::intrusive_ptrブーストの理論的根拠でさえ、いつ優先されるべきかについてはあまりわかりませんshared_ptr。.NET開発に切り替えましたが、このトピックに関するTR1の詳細に従わなかったため、この回答は現在有効ではない可能性があることに注意してください...

于 2009-09-27T05:46:39.560 に答える
6

あなたの提案で

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

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

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でコールバックメソッドを呼び出すクライアントであり、1つのshared_ptrを削除します:)

boost::shared_ptrをハッキングする

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

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

  • インクルードガードを変更する
  • 最初に実際のshared_ptrを含めます
  • shared_ptrのインスタンスの名前を自分の名前に変更します(属性にアクセスするには、privateをpublicに変更します)
  • 衝突を避けるために、実際のファイルですでに定義されているものをすべて削除します

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

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

ただし、ハックを使用しないようにアドバイスしてもいいですか?上で示した2つの解決策は、問題に取り組むのに十分なはずです。

于 2009-10-01T18:22:54.447 に答える
2

ここで関心の分離を行う必要があります。クライアントが生のポインタを渡す場合、クライアントはメモリ管理を担当します(つまり、後でクリーンアップします)。ポインタを作成する場合は、メモリ管理を担当します。これは、別の回答で言及されたDLL境界の問題にも役立ちます。

于 2009-09-28T14:27:55.717 に答える
2

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

ユーザーがクラスを使用できるようになります。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の回答を参照してください。Cユーザーは、関連するMyStruct_*関数でのみ使用します。

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

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

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

マネージャーを使用することの良い点は、C ++コードが、ユーザーによって正しく「取得されていない」オブジェクトを追跡できることです(acquire / uncquireメソッドに情報を追加する__FILE____LINE__、バグ検索を絞り込むことができます)。

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

  1. 存在しないオブジェクトを解放しないでください(ちなみに、Cユーザーはどのようにしてオブジェクトを取得できましたか?)
  2. 実行の最後に、どのオブジェクトが不要でなかったかを知る
  3. 未取得のオブジェクトの場合は、とにかくそれらを破壊します(これはRAIIの観点からは良いことです)これはやや悪ですが、これを提供することができます
  4. 上記のコードに示されているように、ポインターが有効なクラスを指していないことを検出するのにも役立つ可能性があります
于 2010-07-11T12:05:05.607 に答える
1

IOCompletionPortsと同時実行性の懸念に関連して、このようなものが必要なユースケースに出くわしました。ハッキーだが標準に準拠した方法は、ここでハーブサッターが説明しているように弁護士を務めることです。

次のコードスニペットは、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();
}

[YourType]を、カウントを変更する必要のあるオブジェクトのタイプに置き換えます。これはかなりハッキーであり、プラットフォーム固有のオブジェクト名を使用することに注意することが重要です。この機能を取得するために実行する必要のある作業の量は、おそらくそれがいかに悪いアイデアであるかを示しています。また、shared_ptrからハイジャックしている関数がauto_ptrを取り込むため、auto_ptrを使用してゲームをプレイしています。

于 2013-05-27T23:32:14.947 に答える
1

もう1つのオプションは、refcountをインクリメントするためにshared_ptrのコピーを動的に割り当て、デクリメントするために割り当てを解除することです。これにより、Capiクライアントで使用中に共有オブジェクトが破棄されないことが保証されます。

次のコードスニペットでは、shared_ptrを制御するためにincrement()とdecrement()を使用しています。この例を簡単にするために、最初の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

于 2016-03-23T07:40:22.510 に答える
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はそれほど高速ではなく、追加の検索、追加のメモリ、スピンロックが必要です。しかし、それはハンドルアプローチで追加の安全性を与えます。

ところで、手動リリース/取得によるハッキングは、(速度とメモリ使用量の点で)はるかに優れたアプローチのようです。C ++ stdは、C ++のかみそりの形を維持するために、クラスにそのような機能を追加する方が適切です。

于 2013-09-18T23:26:10.810 に答える