プログラマー向けのスマートポインターとRAII

従来、管理者はタスクを迅速に完了することを望んでいました。 このため、プログラマーはコードの美しさと純度を維持します。 この投稿は、C ++ 11でめ​​ったに使用されない革新-リソースを解放するファンクターを指定できるスマートポインター-のリマインダーとして登場しました。

たとえば、stdio.hからFILEファイルストリームを取得します。stdio.hは、単純さと速度が大好きなので、例外を除いて、美しさと基本的な保証を追加してみてください。

unique_ptr<FILE, decltype(&fclose)> my_file(fopen("test.txt", "w"), &fclose); if(my_file) fwrite("test", 4, 1, my_file.get());
      
      





その結果、コードはSTLのみに依存し、ファイルアクセスにわずかな変更を加える必要があり、迅速に記述され、モダンに見えます。 これが、RAIIを最も純粋な形で実現した方法です。



どのように機能しますか?



fopen関数は、FILEタイプのオブジェクトへのポインターを返します。このオブジェクトは、fclose関数へのポインターとともにmy_file変数に格納されます。 この方法で、このファイルストリームの所有権はローカル変数に転送されます。



fclose関数はいつ自動的に呼び出されますか?



  1. 変数のスコープを離れるとき(たとえば、関数から)。
  2. my_fileの作成後に例外が発生した場合。
  3. オブジェクトmy_fileへの割り当て関数を呼び出すとき。
  4. my_file.reset()を呼び出すとき。


オーバーヘッドとは何ですか?



  1. プログラマーは、ファイルの作成を複雑にし、fclose呼び出しを削除し、unique_ptr <...> :: get()呼び出しにすべてのファイルアクセスを追加する必要があります。
  2. 最悪の場合、コンパイラはファイル削除機能へのポインタを保持するメモリセルを必要とします。 せいぜい、fclose呼び出しを適切な場所に配置するだけで、my_fileオブジェクトを完全に最適化します。


このアプローチの利点は何ですか?



  1. 他のスマートポインターと同様に、オブジェクトの所有方法を明示的に指定します。 この場合、オブジェクトが共有されていないことが示されます(unique_ptr)。
  2. 次のように型を宣言することで、不要なコピーアンドペーストを取り除くことができます。
     typedef unique_ptr<FILE, decltype(&fclose)> MyFileType;
          
          



  3. たくさんのファイルを使用する場合、小さなラッパーを書くのが理にかなっています

     MyFileType MakeFile(const char* filename, const char* mode) { return unique_ptr<FILE, decltype(&fclose)>(fopen(filename, mode), &fclose); }
          
          



    ...そして次のように使用します:

     auto my_file = MakeFile("test.txt", "w");
          
          



  4. デストラクタで余分なコードを記述する必要がなくなります。 なぜ多すぎるのですか? このリソースをどのように管理したいかをコンパイラーに既に指示しましたが、今では彼の仕事です。
  5. 標準のSTLコンテナでMyFileType型のオブジェクトを使用できます。

     vector<MyFileType> my_files; my_files.push_back(MakeFile("test.txt", "w"));
          
          



    ...そしてオブジェクトの寿命を制御するあなたの時間を無駄にしないでください。 C ++ 11では、ベクトル<MyFileType>を関数から安全に返すことができます。


Cランタイムライブラリのその他のアイデアを次に示します。



Windowsの最適化に困惑している、または熱心な人は、整列されたデータへのアクセスが高速であることを知っています。 したがって、Microsoft Visual C Runtimeライブラリを使用して、16バイトにアラインされたメモリへのポインターを作成できます。

 unique_ptr<char[], decltype(&::_aligned_free)> my_buffer((char*)(_aligned_malloc(512, 16)), &_aligned_free); my_buffer[0] = 'x'; //  
      
      





テンプレートを一度書いた:

 template<typename T> unique_ptr<T[], decltype(&::_aligned_free)> MakeAlignedBuffer(size_t element_count, size_t alignment = alignment_of<T>::value) { return unique_ptr<T[], decltype(&::_aligned_free)> (reinterpret_cast<T*>(_aligned_malloc(element_count*sizeof(T), alignment)), &_aligned_free); }
      
      



異なる機能(あるモジュールではnew []で作成され、別のモジュールではdeleteで削除される)でメモリを割り当てたり削除したりするエラーを忘れることができます。



しかし、複数のオブジェクトが特定のWinAPIリソースを所有している場合はどうでしょうか?



例として、GUIアプリケーションでいくつかの異なるオブジェクトが動的にロードされたDLLにある関数を使用する状況を考えてください。 この場合、ライブラリのタイムリーなアンロードをプログラムするのは簡単ではありません。

ライブラリを読み込んでいます...

 auto my_module = shared_ptr<HMODULE>(new HMODULE(LoadLibrary(_T("my_library.dll"))), [](HMODULE* instance){ FreeLibrary(*instance); //         });
      
      



次に、my_moduleをオブジェクトに配布します...

 module_owner1.set_module(my_module); module_owner2.set_module(my_module); //     vector  
      
      



オブジェクトでは、必要な機能を使用しています...

 if(my_module && *my_module) { auto func1 = GetProcAddress(*my_module, "MyFunc"); }
      
      



関数の使用を停止し、オブジェクトへの参照カウンターがゼロになると、FreeLibrary関数が呼び出され、オブジェクトが削除されます。



unique_ptrでラムダ関数を使用する方法は?



次のような関数テンプレートを使用する必要があります。

 auto my_instance = std::unique_ptr<HMODULE, function<void(HMODULE*)>> (new HMODULE(LoadLibrary(_T("my_library.dll"))), [](HMODULE* instance){ FreeLibrary(*instance); });
      
      





おわりに



読者の皆様、技術は特定の目的のために開発されたものであり、その使用が正当化されない場合には使用しないでください。 必要性について考えたり、結果を分析したりせずに、すべてのポインターをスマートポインターに急いで置き換えないでください。 これらのアプローチには欠点もあり、Habrで繰り返し議論されてきました。 プロになる。

ありがとう



All Articles