カスタムのスマートポインター削除ツールを使用して、適切な動的ライブラリの境界交差を保証する方法

多くのC ++の専門家は、スマートポインターの使用を提唱し、最新のC ++から、 newの明示的な使用は完全になくなるはずだと主張しています(少なくとも、C ++でstd::make_uniqueれている場合)。 すべての動的メモリ割り当ては、標準ライブラリ、またはstd::vectorなどのコンテナ、またはスマートポインターのいずれかにカプセル化する必要があります。

標準ライブラリのスマートポインタは、占有するメモリを解放できるように構成できます。 この機会は、記事のタイトルで提起された質問への回答に基づいています。

オブジェクトが1つのブロックで初期化され、別のブロックで使用される場合、オブジェクトは動的ライブラリの境界を越えています。 これは、たとえば、オブジェクトがdllで初期化され、そのオブジェクトへのポインターが返されるときに発生します。

あるライブラリ(または実行可能モジュール)が別のライブラリにリンクし、ファクトリを使用してオブジェクトを動的に初期化し、そのポインタを取得するとします。 このポインターを使用するブロックは、ポインターを削除して、ポインターが指すメモリー領域を解放できます。 メモリを割り当てるライブラリとポインタで動作するブロックが異なるバージョンのOS動的メモリ割り当て(WindowsのCRT)を使用している場合、エラーが発生します。 この問題の例(Windowsの場合):

原則として(C ++ 11より前)、ライブラリ開発者はこの問題を回避するために、このライブラリ内で割り当てられたオブジェクトのメモリ割り当て解除関数を開発する必要がありました。 これには副作用がありました。そのようなライブラリのインターフェースはより「重く」なり、さらに、ライブラリオブジェクトにメモリを正しく割り当てたり解放したりする方法を知る必要がありました。 理想的には、ユーザーは割り当て/割り当て解除スキーム自体に煩わされるべきではなく、その後のリリースを心配することなく、ライブラリメカニズム(たとえば、ファクトリー)を呼び出してメモリを割り当てる必要があります。

コーディングに渡す


2つのプロジェクトがあります。1つ目は、ライブラリファクトリを使用してそこからオブジェクトを初期化するメインファイルで構成され、2つ目は問題の状況とその解決策を示します。
問題領域はシングルトンファクトリ(ProblematicFactory)であり、オブジェクトを初期化し、そのポインタを返します。 ソリューションは別のシングルトンであり、オブジェクトを初期化した後、 std::unique_ptrへのポインタを返します。このツールには、DLLのメモリを解放する独自の削除ツールがあります。
USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTIONの定義をUSE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTIONしてデバッグモードでプログラムを実行すると、デバッガーがヒープの損傷を検出することがわかります。

メインファイル

 // main.cpp #include <ProblematicFactory.h> #include <SafeFactory.h> //  undef  define,   assert'    #undef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION int main() { #ifdef USE_PROBLEMATIC_FACTORY_AND_CAUSE_HEAP_CORRUPTION { //     DLL auto wMyObject = ProblematicFactory::getInstance().create(); //       delete wMyObject; //  DLL           CLR DLL, //   ,  -     } #endif { auto wMyObject = SafeFactory::getInstance().create(); //      , wMyObject  //  ,    , //   MyClass.h (. ),   //      } { std::shared_ptr< MyClass > wMyObject = SafeFactory::getInstance().create(); } return 0; } 

問題工場

これは、ライブラリが作成できるオブジェクトへのポインタを返す典型的なファクトリ実装です。
 // ProblematicFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" class LIBRARYFACTORY_API ProblematicFactory { public: static ProblematicFactory & getInstance() { static ProblematicFactory wProblematicFactory; return wProblematicFactory; } MyClass * create() const { return new MyClass; } private: ProblematicFactory() {}; }; 

安全な工場

構文的には、このファクトリーの使用は問題のあるもの(メインを参照)とまったく同じですが、ここではポインターはstd::shared_ptrではなくstd::shared_ptr std::unique_ptrカプセル化されています。
 // SaveFactory.h #pragma once #include "DllSwitch.h" #include "MyClass.h" #include <memory> class LIBRARYFACTORY_API SafeFactory { public: static SafeFactory & getInstance(); //  std::unique_ptr     , // ..       DLL.   //   ,   std::unique_ptr . //  ,      // ,    MyClass  std::default_delete inline std::unique_ptr< MyClass > create() const { return std::unique_ptr< MyClass >(doCreate()); } private: SafeFactory(); MyClass * doCreate() const; }; 

国境検問所

 // MyClass.h #pragma once #include "DllSwitch.h" #include <memory> class LIBRARYFACTORY_API MyClass { }; namespace std { template<> class LIBRARYFACTORY_API default_delete< MyClass > { public: void operator()(MyClass *iToDelete) { delete iToDelete; } }; } 

上記のすべてのファイルで、 LIBRARYFACTORY_API 、その内容を定義するDllSwitch.hヘッダーファイルがDllSwitch.hいます。
 // DllSwitch.h #pragma once #ifdef LIBRARYFACTORY_EXPORTS #define LIBRARYFACTORY_API __declspec(dllexport) #else #define LIBRARYFACTORY_API __declspec(dllimport) #endif 


UPDすべての関数実装は、個別のファイルに移動する必要があります

Source: https://habr.com/ru/post/J182970/


All Articles