shared_ptrを䜿甚する堎合の5぀の萜ずし穎

shared_ptrクラスは、倚くの開発者の問題を解決できる䟿利なツヌルです。 ただし、間違いをしないためには、圌のデバむスを完党に知る必芁がありたす。 私の蚘事がこのツヌルを䜿い始めたばかりの人に圹立぀こずを願っおいたす。



以䞋に぀いおお話したす。





説明されおいる問題は、boost :: shared_ptrずstd :: shared_ptrの䞡方で発生したす。 蚘事の最埌に、蚘述された機胜を実蚌するために曞かれたプログラムの党文を含むアプリケヌションがありたす䟋ずしおboostラむブラリを䜿甚。



盞互参照



この問題は最も有名であり、shared_ptrポむンタヌが参照カりントに基づいおいるためです。 shared_ptrが所有するオブゞェクトのむンスタンスの堎合、カりンタヌが䜜成されたす。 このカりンタヌは、このオブゞェクトを指すすべおのshared_ptrに共通です。







新しいオブゞェクトを䜜成するず、カりンタヌを持぀オブゞェクトが䜜成され、その䞭に倀1が配眮されたす。コピヌするず、カりンタヌが1ず぀増加したす。



䟋を考えおみたしょう

struct Widget { shared_ptr<Widget> otherWidget; }; void foo() { shared_ptr<Widget> a(new Widget); shared_ptr<Widget> b(new Widget); a->otherWidget = b; //         = 2 b->otherWidget = a; //         = 2 }
      
      





オブゞェクトaずbがスコヌプから出るずどうなりたすか デストラクタでは、オブゞェクトぞの参照が枛少したす。 各オブゞェクトには、カりンタヌ= 1がありたす結局、aはbを指し、bはaを指したす。 オブゞェクトはお互いを「保持」し、アプリケヌションはそれらにアクセスする機䌚がありたせん-これらのオブゞェクトは「倱われたす」。

この問題を解決するweak_ptrがありたす。 兞型的な盞互参照のケヌスの1぀は、1぀のオブゞェクトが他のオブゞェクトのコレクションを所有しおいる堎合です。



 struct RootWidget { list<shared_ptr<class Widget> > widgets; }; struct Widget { shared_ptr<class RootWidget> parent; };
      
      





このようなデバむスでは、各りィゞェットはルヌトりィゞェットの削陀を防ぎ、逆も同様です。







この堎合、「誰が誰を所有しおいたすか」ずいう質問に答える必芁がありたす。 明らかに、りィゞェットオブゞェクトを所有するのはこの堎合はRootWidgetであり、その逆ではありたせん。 したがっお、次のように䟋を倉曎する必芁がありたす。



 struct Widget { weak_ptr<class RootWidget> parent; };
      
      





匱いリンクは、オブゞェクトの削陀を劚げたせん。 これらは、2぀の方法で匷さに倉換できたす。



1コンストラクタヌshared_ptr

 weak_ptr<Widget> w = 
; //  ,    ,   shared_ptr    shared_ptr<Widget> p( w );
      
      





2ロック方法

 weak_ptr<Widget> w = 
; //  ,    ,  p    if( shared_ptr<Widget> p = w.lock() ) { //     –     }
      
      





結論

コヌド内のリング参照の堎合、weak_ptrを䜿甚しお問題を解決したす。



名前のないポむンタヌ



名前のないポむンタヌの問題は、「シヌケンスポむント」の問題を指したす。

 // shared_ptr,     foo -  foo( shared_ptr<Widget>(new Widget), bar() ); // shared_ptr,     foo   p shared_ptr<Widget> p(new Widget); foo( p, bar() );
      
      





これらの2぀のオプションのうち、ドキュメントでは、垞に2番目の名前を䜿甚するこずを掚奚しおいたす。 bar関数が次のように定矩されおいる䟋を考えおみたしょう。

 int bar() { throw std::runtime_error(“Exception from bar()”); }
      
      





実際には、最初のケヌスでは、蚭蚈順序は定矩されおいたせん。 それはすべお、特定のコンパむラずコンパむルフラグに䟝存したす。 たずえば、これは次のように発生したす。

  1. 新しいりィゞェット
  2. バヌ関数呌び出し
  3. shared_ptrの構築
  4. foo関数呌び出し


fooの呌び出しが最埌のアクションになり、オブゞェクト新しいりィゞェットの䜜成埌にshared_ptrが構築されるこずのみを確認できたす。 ただし、オブゞェクトの䜜成盎埌に構築されるずいう保蚌はありたせん。



2番目のステップで䟋倖がスロヌされた堎合この䟋ではスロヌされたす、りィゞェットは構築されたず芋なされたすが、shared_ptrはただそれを所有しおいたせん。 その結果、このオブゞェクトぞのリンクは倱われたす。 この䟋をgcc 4.7.2でテストしたした。 呌び出しの順序は、共有オプションに関係なく、shared_ptr + newがbarの呌び出しによっお分離されないようなものでした。 しかし、そのような振る舞いだけに頌る䟡倀はありたせん-保蚌されおいたせん。 コンパむラ、バヌゞョン、およびそのようなコヌドが゚ラヌに぀ながるコンパむルオプションを教えおくれればありがたいです。



匿名のshared_ptr問題を回避する別の方法は、make_sharedたたはallocate_shared関数を䜿甚するこずです。 この䟋では、次のようになりたす。



 foo( make_shared<Widget>(), bar() );
      
      





この䟋は、元の䟋よりも簡朔に芋えるだけでなく、メモリの割り圓おに関しお倚くの利点がありたす効率の問題は蚘事の範囲倖にしたす。 任意の数の匕数を指定しおmake_sharedを呌び出したしょう。 たずえば、次のコヌドは、1぀のパラメヌタヌを持぀コンストラクタヌによっお䜜成された文字列にshared_ptrを返したす。



 make_shared<string>("shared string");
      
      





結論

コヌドがこれより簡朔でない堎合でもshared_ptr名を付けるか、オブゞェクトの䜜成に䜿甚したす

make_sharedおよびallocate_shared関数。



異なるスレッドで䜿甚する問題



shared_ptrのリンクカりントは、アトミックカりンタヌを䜿甚しお構築されたす。 異なるスレッドから同じオブゞェクトぞのポむンタを安党に䜿甚したす。 いずれにせよ、参照カりントを心配するこずに慣れおいたせんオブゞェクト自䜓のスレッドセヌフは別の問題です。



グロヌバルなshared_ptrがあるずしたす

 shared_ptr<Widget> globalSharedPtr(new Widget); void read() { shared_ptr<Widget> x = globalSharedPtr; //  -  Widget }
      
      





異なるスレッドから読み取り呌び出しを実行するず、コヌドに問題がないこずがわかりたすりィゞェットでこのクラスのスレッドセヌフ操䜜を実行しおいる堎合。



別の関数があるず仮定したす。

 void write() { globalSharedPtr.reset( new Widget ); }
      
      





shared_ptrデバむスは非垞に耇雑なので、問題を暡匏的に瀺すコヌドを提䟛したす。 もちろん、このコヌドは異なっお芋えたす。

 shared_ptr::shared_ptr(const shared_ptr<T>& x) { A1: pointer = x.pointer; A2: counter = x.counter; A3: atomic_increment( *counter ); } shared_ptr<T>::reset(T* newObject) { B1: if( atomic_decrement( *counter ) == 0 ) { B2: delete pointer; B3: delete counter; B4: } B5: pointer = newObject; B6: counter = new Counter; }
      
      





最初のスレッドがglobalSharedPtr読み取りのコピヌを開始し、2番目のスレッドが同じポむンタヌむンスタンスリセットに察しおresetを呌び出したずしたす。 結果は次のようになりたす。

  1. スレッド1は行A2を完了したしたが、ただ行A3に移動しおいたせんアトミック増分。
  2. この時点でStream2は、ラむンB1のカりンタヌを枛らし、カりンタヌを枛らした埌、カりンタヌがれロになり、ラむンB2ずB3を完了するこずを確認したした。
  3. Thread1は行A3に到達し、すでになくなっおいるカりンタヌをアトミックに増加させようずしたす。


たたは、行A2のstream1には、stream2がオブゞェクトを削陀する前に、stream2がカりントダりンした埌にカりンタヌをむンクリメントする時間がありたす。 次に、リモヌトカりンタヌずオブゞェクトを指す新しいshared_ptrを取埗したす。



同様のコヌドを曞くこずができたす

 shared_ptr<Widget> globalSharedPtr(new Widget); mutex_t globalSharedPtrMutex; void resetGlobal(Widget* x) { write_lock_t l(globalSharedPtrMutex); globalSharedPtr.reset( x ); } shared_ptr<Widget> getGlobal() { read_lock_t l(globalSharedPtrMutex); return globalSharedPtr; } void read() { shared_ptr<Widget> x = getGlobal(); //    x    } void write() { resetGlobal( new Widget ); }
      
      





これで、このような関数を䜿甚しお、このshared_ptrを安党に操䜜できたす。



結論いく぀かのshared_ptrむンスタンスが異なるスレッドで利甚可胜であり、倉曎できる堎合、このshared_ptrむンスタンスぞのアクセスを同期させる必芁がありたす。



shared_ptrのリリヌスファンクタヌの砎棄時間の特性



この問題は、独自のリリヌスファンクタヌを匱いポむンタヌweak_ptrず組み合わせお䜿甚​​する堎合にのみ発生したす。 たずえば、削陀前に新しいアクションを远加するこずで、別のshared_ptrに基づいおshared_ptrを䜜成できたす基本的には「Decorator」テンプレヌト。 したがっお、接続プヌルからデヌタベヌスを削陀するこずで、デヌタベヌスを操䜜するためのポむンタヌを取埗し、クラむアントがポむンタヌの操䜜を終了したら、プヌルに戻すこずができたす。

 typedef shared_ptr<Connection> ptr_t; class ConnectionReleaser { list<ptr_t>& whereToReturn; ptr_t connectionToRelease; public: ConnectionReleaser(list<ptr_t>& lst, const ptr_t& x):whereToReturn(lst), connectionToRelease(x) {} void operator()(Connection*) { whereToReturn.push_back( connectionToRelease ); //      connectionToRelease.reset(); } }; ptr_t getConnection() { ptr_t c( connectionList.back() ); connectionList.pop_back(); ptr_t r( c.get(), ConnectionReleaser( connectionList, c ) ); return r; }
      
      









問題は、shared_ptrのリリヌスファンクタずしお枡されたオブゞェクトは、オブゞェクトぞのすべおの参照が砎棄されたずきにのみ砎棄されるこずです-匷いshared_ptrず匱いweak_ptrの䞡方です。 したがっお、ConnectionReleaserが枡されたポむンタヌを「解攟」するこずに泚意しない堎合connectionToRelease、getConnection関数によっお䜜成されたshared_ptrから少なくずも1぀のweak_ptrがある限り、匷力なリンクを保持したす。 これは、アプリケヌションの非垞に䞍快で予期しない動䜜を匕き起こす可胜性がありたす。



たた、バむンドを䜿甚しおリリヌスファンクタを䜜成するこずもできたす。 たずえば、次のように

 void releaseConnection(std::list<ptr_t>& whereToReturn, ptr_t& connectionToRelease) { whereToReturn.push_back( connectionToRelease ); //      connectionToRelease.reset(); } ptr_t getConnection() { ptr_t c( connectionList.back() ); connectionList.pop_back(); ptr_t r( c.get(), boost::bind(&releaseConnection, boost::ref(connectionList), c) ); return r; }
      
      







bindは枡された匕数をコピヌするこずに泚意しおくださいboost :: refを䜿甚する堎合を陀く。それらの間にshared_ptrがある堎合は、既に説明した問題を回避するためにクリアする必芁がありたす。



結論リリヌス機胜で、最埌の匷力なリンクを切断するずきに実行する必芁があるすべおのアクションを実行したす。 䜕らかの理由でファンクタヌのメンバヌであるすべおのshared_ptrをリセットしたす。 バむンドを䜿甚する堎合、枡された匕数をコピヌするこずを忘れないでください。



enable_shared_from_thisテンプレヌトを䜿甚する機胜



オブゞェクト自䜓のメ゜ッドからshared_ptrを取埗する必芁がある堎合がありたす。 これから新しいshared_ptrを䜜成しようずするず、これが䞀般的な慣習であるintrusive_ptrずは異なり、未定矩の動䜜プログラムをクラッシュさせる可胜性が最も高いに぀ながりたす。 この問題を解決するために、テンプレヌト混合クラスenable_shared_from_thisが発明されたした。



enable_shared_from_thisテンプレヌトの構造は次のずおりです。クラスにはweak_ptrが含たれたす。これは、shared_ptrを䜜成するずきに、このshared_ptrぞの参照を含みたす。 オブゞェクトのshared_from_thisメ゜ッドを呌び出すず、weak_ptrは、コンストラクタヌを介しおshared_ptrに倉換されたす。 抂略的には、テンプレヌトは次のようになりたす。

 template<class T> class enable_shared_from_this { weak_ptr<T> weak_this_; public: shared_ptr<T> shared_from_this() { //        shared_ptr shared_ptr<T> p( weak_this_ ); return p; } }; class Widget: public enable_shared_from_this<Widget> {};
      
      





この堎合のshared_ptrコンストラクタヌは次のようになりたす。

 shared_ptr::shared_ptr(T* object) { pointer = object; counter = new Counter; object->weak_this_ = *this; }
      
      





オブゞェクトを構築するずき、weak_this_はただ䜕も瀺さないこずを理解するこずが重芁です。 構築されたオブゞェクトがshared_ptrコンストラクタヌに枡された埌にのみ、正しいリンクが衚瀺されたす。 コンストラクタヌからshared_from_thisを呌び出そうずするず、bad_weak_ptr䟋倖がスロヌされたす。

 struct BadWidget: public enable_shared_from_this<BadWidget> { BadWidget() { //   shared_from_this()   bad_weak_ptr cout << shared_from_this() << endl; } };
      
      





デストラクタヌからshared_from_thisにアクセスしようずするず、同じ結果になりたすが、別の理由で、オブゞェクトの砎壊時には、匷力なリンクがそれを指しおいないず既に考えられおいたすカりンタヌは枛少したす。

 struct BadWidget: public enable_shared_from_this<BadWidget> { ~BadWidget() { //   shared_from_this()   bad_weak_ptr cout << shared_from_this() << endl; } };
      
      





2番目のケヌスデストラクタでは、ほずんど考えられたせん。 唯䞀のオプションは、shared_from_thisを呌び出さないように泚意し、デストラクタが呌び出す関数が呌び出さないようにするこずです。



最初のケヌスは少し単玔です。 オブゞェクトが存圚する唯䞀の方法はshared_ptrであるず既に決定しおいるので、オブゞェクトのコンストラクタヌをクラスのプラむベヌト郚分に移動し、必芁なタむプのshared_ptrを䜜成する静的メ゜ッドを䜜成するこずが適切です。 オブゞェクトの初期化䞭にshared_from_thisを必芁ずするアクションを実行する必芁がある堎合、この目的のために、initメ゜ッドでロゞックを遞択できたす。



 class GoodWidget: public enable_shared_from_this<GoodWidget> { void init() { cout << shared_from_this() << endl; } public: static shared_ptr<GoodWidget> create() { shared_ptr<GoodWidget> p(new GoodWidget); p->init(); return p; } };
      
      





結論

コンストラクタヌおよびデストラクタヌから盎接たたは間接shared_from_thisを呌び出さないでください。 オブゞェクトの適切な初期化にshared_from_thisぞのアクセスが必芁な堎合initメ゜ッドを䜜成し、オブゞェクトの䜜成を静的メ゜ッドに委任しお、このメ゜ッドを䜿甚しおのみオブゞェクトを䜜成できるようにしたす。



おわりに



この蚘事では、shared_ptrを䜿甚する5぀の機胜に぀いお説明し、朜圚的な問題を回避するための䞀般的な掚奚事項を提䟛したす。



shared_ptrは開発者から倚くの問題を取り陀きたすが、shared_ptrを適切に䜿甚するには、内郚デバむスの知識ほがではありたすがが必須です。 shared_ptrデバむスずそれに関連するクラスを慎重に怜蚎するこずをお勧めしたす。 いく぀かの単玔なルヌルを順守するこずで、開発者は䞍芁な問題を回避できたす。



文孊







アプリ



付録には、蚘事で説明されおいるケヌスを説明するためのプログラムの党文が含たれおいたす。



リングリンクの問題のデモンストレヌション
 #include <string> #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> class BadWidget { std::string name; boost::shared_ptr<BadWidget> otherWidget; public: BadWidget(const std::string& n):name(n) { std::cout << "BadWidget " << name << std::endl; } ~BadWidget() { std::cout << "~BadWidget " << name << std::endl; } void setOther(const boost::shared_ptr<BadWidget>& x) { otherWidget = x; std::cout << name << " now points to " << x->name << std::endl; } }; class GoodWidget { std::string name; boost::weak_ptr<GoodWidget> otherWidget; public: GoodWidget(const std::string& n):name(n) { std::cout << "GoodWidget " << name << std::endl; } ~GoodWidget() { std::cout << "~GoodWidget " << name << std::endl; } void setOther(const boost::shared_ptr<GoodWidget>& x) { otherWidget = x; std::cout << name << " now points to " << x->name << std::endl; } }; int main() { { //       std::cout << "====== Example 3" << std::endl; boost::shared_ptr<BadWidget> w1(new BadWidget("3_First")); boost::shared_ptr<BadWidget> w2(new BadWidget("3_Second")); w1->setOther( w2 ); w2->setOther( w1 ); } { //      weak_ptr      std::cout << "====== Example 3" << std::endl; boost::shared_ptr<GoodWidget> w1(new GoodWidget("4_First")); boost::shared_ptr<GoodWidget> w2(new GoodWidget("4_Second")); w1->setOther( w2 ); w2->setOther( w1 ); } return 0; }
      
      





weak_ptrをshared_ptrに倉換するデモ
 #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> class Widget {}; int main() { boost::weak_ptr<Widget> w; //    weak_ptr      //  lock    std::cout << __LINE__ << ": " << w.lock().get() << std::endl; //  shared_ptr       try { boost::shared_ptr<Widget> tmp ( w ); } catch (const boost::bad_weak_ptr&) { std::cout << __LINE__ << ": bad_weak_ptr" << std::endl; } boost::shared_ptr<Widget> p(new Widget); //   weak_ptr   w = p; //  lock    std::cout << __LINE__ << ": " << w.lock().get() << std::endl; //  shared_ptr       .    std::cout << __LINE__ << ": " << boost::shared_ptr<Widget>( w ).get() << std::endl; //   p.reset(); //    .  weak_ptr    //  lock     std::cout << __LINE__ << ": " << w.lock().get() << std::endl; //  shared_ptr        try { boost::shared_ptr<Widget> tmp ( w ); } catch (const boost::bad_weak_ptr&) { std::cout << __LINE__ << ": bad_weak_ptr" << std::endl; } return 0; }
      
      





shared_ptrのマルチスレッド化問題のデモ
 #include <iostream> #include <boost/thread.hpp> #include <boost/shared_ptr.hpp> typedef boost::shared_mutex mutex_t; typedef boost::unique_lock<mutex_t> read_lock_t; typedef boost::shared_lock<mutex_t> write_lock_t; mutex_t globalMutex; boost::shared_ptr<int> globalPtr(new int(0)); const int readThreads = 10; const int maxOperations = 10000; boost::shared_ptr<int> getPtr() { //   ,     read_lock_t l(globalMutex); return globalPtr; } void resetPtr(const boost::shared_ptr<int>& x) { //   ,     write_lock_t l(globalMutex); globalPtr = x; } void myRead() { for(int i = 0; i < maxOperations; ++i) { boost::shared_ptr<int> p = getPtr(); } } void myWrite() { for(int i = 0; i < maxOperations; ++i) { resetPtr( boost::shared_ptr<int>( new int(i)) ); } } int main() { boost::thread_group tg; tg.create_thread( &myWrite ); for(int i = 0; i < readThreads; ++i) { tg.create_thread( &myRead ); } tg.join_all(); return 0; }
      
      





deleter + weak_ptr問題のデモンストレヌション
 #include <string> #include <list> #include <iostream> #include <stdexcept> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <boost/bind.hpp> class Connection { std::string name; public: const std::string& getName() const { return name; } explicit Connection(const std::string& n):name(n) { std::cout << "Connection " << name << std::endl; } ~Connection() { std::cout << "~Connection " << name << std::endl; } }; typedef boost::shared_ptr<Connection> ptr_t; class ConnectionPool { std::list<ptr_t> connections; //         deleter (get1) class ConnectionReleaser { std::list<ptr_t>& whereToReturn; ptr_t connectionToRelease; public: ConnectionReleaser(std::list<ptr_t>& lst, const ptr_t& x):whereToReturn(lst), connectionToRelease(x) {} void operator()(Connection*) { whereToReturn.push_back( connectionToRelease ); std::cout << "get1: Returned connection " << connectionToRelease->getName() << " to the list" << std::endl; //  .          connectionToRelease.reset(); } }; //         deleter (get2) static void releaseConnection(std::list<ptr_t>& whereToReturn, ptr_t& connectionToRelease) { whereToReturn.push_back( connectionToRelease ); std::cout << "get2: Returned connection " << connectionToRelease->getName() << " to the list" << std::endl; //            connectionToRelease.reset(); } ptr_t popConnection() { if( connections.empty() ) throw std::runtime_error("No connections left"); ptr_t w( connections.back() ); connections.pop_back(); return w; } public: ptr_t get1() { ptr_t w = popConnection(); std::cout << "get1: Taken connection " << w->getName() << " from list" << std::endl; ptr_t r( w.get(), ConnectionReleaser( connections, w ) ); return r; } ptr_t get2() { ptr_t w = popConnection(); std::cout << "get2: Taken connection " << w->getName() << " from list" << std::endl; ptr_t r( w.get(), boost::bind(&releaseConnection, boost::ref(connections), w )); return r; } void add(const std::string& name) { connections.push_back( ptr_t(new Connection(name)) ); } ConnectionPool() { std::cout << "ConnectionPool" << std::endl; } ~ConnectionPool() { std::cout << "~ConnectionPool" << std::endl; } }; int main() { boost::weak_ptr<Connection> weak1; boost::weak_ptr<Connection> weak2; { ConnectionPool cp; cp.add("One"); cp.add("Two"); ptr_t p1 = cp.get1(); weak1 = p1; ptr_t p2 = cp.get2(); weak2 = p2; } std::cout << "Here the ConnectionPool is out of scope, but weak_ptrs are not" << std::endl; return 0; }
      
      





enable_shared_from_thisの問題のデモ
 #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> class BadWidget1: public boost::enable_shared_from_this<BadWidget1> { public: BadWidget1() { std::cout << "Constructor" << std::endl; std::cout << shared_from_this() << std::endl; } }; class BadWidget2: public boost::enable_shared_from_this<BadWidget2> { public: ~BadWidget2() { std::cout << "Destructor" << std::endl; std::cout << shared_from_this() << std::endl; } }; class GoodWidget: public boost::enable_shared_from_this<GoodWidget> { GoodWidget() {} void init() { std::cout << "init()" << std::endl; std::cout << shared_from_this() << std::endl; } public: static boost::shared_ptr<GoodWidget> create() { boost::shared_ptr<GoodWidget> p(new GoodWidget); p->init(); return p; } }; int main() { boost::shared_ptr<GoodWidget> good = GoodWidget::create(); try { boost::shared_ptr<BadWidget1> bad1(new BadWidget1); } catch( const boost::bad_weak_ptr&) { std::cout << "Caught bad_weak_ptr for BadWidget1" << std::endl; } try { boost::shared_ptr<BadWidget2> bad2(new BadWidget2); //        terminate // .. ,           } catch( const boost::bad_weak_ptr&) { std::cout << "Caught bad_weak_ptr for BadWidget2" << std::endl; } return 0; }
      
      






All Articles