C +におけるスマートポインタを用いたメモリ管理-パート1
25447 ワード
C +におけるスマートポインタを用いたメモリ管理-パート1
CとC +の両方の主要な長所と短所の1つはポインタです.メモリ管理が正しく実行されない場合、C +はポインタの多くの利点を持っています.ポインタには、参照するリソースの明示的な管理が必要です.たとえば、動的に割り当てられたメモリのアドレスを保持するポインタを使用して取得する
new
プログラマがメモリを解放する必要があるdelete
; そうすることができないので、見つけるのが難しいポインターまたはメモリリークをぶらぶらさせます.スマートポインターは、リソースの明示的な管理を自動化するメカニズムを提供します.rawポインタとは対照的に、スマートポインタは、それが指すリソースのメモリを管理することができます.それはリソースへの生のポインタのためのプロキシです、そして、それは生のポインタのように見えて、感じます.Dereferencingをサポートします
(*)
とポインタ(->)
演算子スマートポインタはスタックにあるスコープから出ると、そのデストラクタはそれが指すダイナミックメモリを解放する.スマートポインタはオブジェクト指向言語の熟語リソース取得を初期化するRAII ). このイディオムは、リソースが初期化時間、すなわちポインタのメモリが作成されたときに取得されると述べます.
なぜスマートポインタを使うべきか
メモリリークを引き起こすこの非常に単純なコードを見てみましょう.
int main()
{
int *ptr = new int[20];
// Oops forgot to delete ptr
return 0;
}
私が使うときValgrind メモリリークのバイナリをチェックする次の結果を得た.==1562402== LEAK SUMMARY:
==1562402== definitely lost: 80 bytes in 1 blocks
==1562402== indirectly lost: 0 bytes in 0 blocks
==1562402== possibly lost: 0 bytes in 0 blocks
==1562402== still reachable: 0 bytes in 0 blocks
==1562402== suppressed: 0 bytes in 0 blocks
==1562402==
==1562402== For lists of detected and suppressed errors, rerun with: -s
==1562402== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
私たちが走っていなかったので、メモリの80バイト(4バイト×20)をリークしたことをはっきりと見ることができますdelete
on ptr
そして、それは手動ですべての割り当てとdelete
右側の時間;特に、オブジェクトがコピーされ、コード内のどこにでも移動して渡されたC +プロジェクトが十分に大きい場合.生のポインタを使ってメモリリークの別のシナリオを見てみましょう
void someFunction()
{
MyClass *obj = new MyClass(10);
if (!Validate(obj))
{
return; // memory leak
}
// ....
obj->someOtherFunction();
delete obj;
}
機能を仮定するsomeFunction()
は無限回と呼ばれるobj
検証は毎回失敗します.これは、プログラマが新しいメモリを割り当てて、ポインタを失うことはないが、プログラムがもう使用しないオブジェクトへのポインタを保持している状態になる.これは正式にメモリリークではありませんが、同じ状況になります使っても
new
and delete
ペアが正しくメモリリークのシナリオに遭遇する可能性があります.以下の例を見てみましょう.void SomeMethod()
{
MyClass *obj = new MyClass;
SomeOtherMethod(); // it can throw an exception
delete obj;
}
例外がスローされた場合はobj
オブジェクトは削除されません.どれだけ努力しても、すべての動的に割り当てられたメモリを解放することは非常に困難です.たとえ我々がそれをすることができるとしても、それはしばしば例外から安全でありません.配列の削除
delete
動的なサイズの一時的な配列を作成することがしばしば必要です.それらがもはや要求されないあと、割り当てられたメモリを解放することは重要です.C +は特別です
delete
演算子[]
ブラケットは、非常に簡単に忘れています.The delete[]
演算子は配列に割り当てられたメモリを削除するとともに、配列からすべてのオブジェクトのデストラクタを呼び出します.を使うのも不正確ですdelete
演算子なしで[]
配列のためのブラケット.できるだけヒープメモリを使うのを避けるべきです
そして、これらの理由のために、現代のC +はスタックの上でオブジェクトを宣言することによってできるだけヒープメモリを使用するのを避けます;リソースを所有するスタックオブジェクトがスコープ外になると、そのデストラクタが自動的に呼び出されます.
単純なオブジェクトを示す例を次に示します
e
. これは関数のスタックで宣言され、関数ブロックの最後に破棄されます.オブジェクトe
リソースを所有していない(ヒープ割り当てされたメモリなど)はないので、Entity
デストラクタ.class Entity
{
private:
int* data;
public:
explicit Entity(const int size) { data = new int[size]; } // acquire
~Entity() { delete[] data; } // release
void do_something() {}
};
void functionUsingEntity() {
Entity e(100); // lifetime automatically tied to enclosing scope
e.do_something();
} // automatic destruction and deallocation for e
C++ 11以降は、標準ライブラリからのスマートポインタを使用して、前の例を書くより良い方法があります.スマートポインタは、所有しているメモリの割り当てと削除を管理します.スマートポインタを使用すると、Entity
クラス.スマートポインタの種類
スマートポインタの3種類があります.
ユニークなポインタ
std::unique_ptr<>
)共有ポインタ
std::shared_ptr<>
)弱ポインタ
std::weak_ptr<>
)shared_ptr
NOTE: auto pointer (
std::auto_ptr
) has been depreciated after the inclusion of move semantics in C++11.
ユニークなポインタ
std::unique_ptr<>
)と
unique_ptr
, 割り当てられたオブジェクト、およびunique_ptr
スコープから出て、ポインターオブジェクトは削除されます.この関数は、関数のままになっているかどうかにかかわらず、どこかにスローされるか、あるいは例外がスローされるかによって起こります.例えば、
void functionWithUniquePointer()
{
std::unique_ptr<MyClass> ptr(new MyClass); // ptr owns the MyClass resource
ptr->do_something(); // tell the object to do something
do_somethingelse(); // might throw an exception
} // ptr gets destroyed; destructor deletes the MyClass object resource
使用
unique_ptr
生のポインタのようにデリフェレンシング以来
(*)
とポインタ(->)
演算子はunique_ptr
クラス利用可能unique_ptr
ASup->do_something(); // used arrow operator similar to raw pointers
(*up).do_something(); // used dereferencing operator similar to raw pointers
また、rawポインタに似たポインタのヌル性をチェックすることもできますif(up)
if(up.get() != nullptr)
if(up == nullptr)
メモリの解放unique_ptr
to nullptr
up == nullptr
up.reset()
オブジェクトの排他的所有権
unique_ptr
資源の唯一の所有者であることは、それぞれunique_ptr
ポインタは、それがもはや必要でない(すなわち、オブジェクトの寿命について責任がある)とき、オブジェクトを削除するのに責任があります.これは、他の誰もその生涯を終えて、同じ理由のためにオブジェクトを使うことができないことを意味しますstd::unique_ptr
実際にオーバーヘッドはありませんし、非常に予測可能な行動をしています.以来
unique_ptr
リソースの唯一の所有権を維持します、1つはそれのコピーを作ることができません、そして、タイプ安全性理由でコピー建設者とコピー代入演算子は削除されます.代わりに、ポインターの所有権を1つのポインターオブジェクトから別のものへ移動移動することもできます.std::unique_ptr<Thing> p1 (new Thing); // p1 owns the Thing
std::unique_ptr<Thing> p2(p1); // error - copy construction is not allowed.
std::unique_ptr<Thing> p3; // an empty unique_ptr;
p3 = p1; // error, copy assignment is not allowed.
std::unique_ptr<Thing> up1;
std::unique_ptr<Thing> up2(new Thing);
up1 = std::move(up2); // ownership transfer using move semantics is allowed
通過
std::unique_ptr
関数引数としてエー
unique_ptr
を取る関数に渡す間に移動する必要がありますunique_ptr
値によって.void foo(std::unique_ptr<std::string> cp) {
//...
}
//call foo
auto up = std::make_unique<std::string>("some string");
foo(up); //!ERROR. No copy allowed.
foo(std::move(up)); //OK. Explicit move
foo(std::make_unique<std::string>("some string")); //OK. Implicit move
使用
std::unique_ptr
標準コンテナで我々は標準的な容器を埋めることができます
unique_ptr
sはそれ自身のオブジェクト、そして所有権はそれからコンテナに有効に存在する.unique_ptr
そのため、所有権はunique_ptr
インザコンテナunique_ptr
. unique_ptr
sは容器にとどまります.カスタム削除
std::unique_ptr
完全なタイプstd::unique_ptr
番目のテンプレートパラメータ、デフォルトの型を持つその削除子を持ちますstd::default_delete<T>
template< class T, class Deleter = std::default_delete<T> >
class unique_ptr;
エーunique_ptr
オブジェクトリソースを破壊するために呼び出されるカスタム提供されたカスタムDeleterで宣言されて、初期化されることができます.template <typename T>
class CustomDeleter
{
public:
void operator()(T* ptr) const {
std::cout << "freeing memory using 'delete'...\n";
delete ptr;
}
};
template <typename T>
class CustomDeleter<T[]>
{
public:
template <typename U>
void operator()(U* ptr) const {
std::cout << "freeing memory using 'delete[]'...\n";
delete[] ptr;
}
};
int main()
{
int* p = new int{0};
int* p2 = new int[3]{5, 7, 8};
std::unique_ptr<int, CustomDeleter<int>> upi(p, CustomDeleter<int>{}); // using unique_ptr with raw pointer is discouraged
std::unique_ptr<int[], CustomDeleter<int[]>> upari(p2, CustomDeleter<int[]>{});
return 0;
}
CustomDeleterはコンストラクタへの引数として渡され、unique_ptr
オブジェクト.デクリプタは、関数オブジェクト、関数ポインタ、またはラムダであり得る.上記の例では、関数オブジェクトを2番目のパラメーターとして使用します.関数ポインタを使用した顧客の削除のための非常に簡単な例です.
void deleter(int* ptr)
{
std::cout << "Function pointer deleter\n" << std::endl;
delete ptr;
}
int main()
{
std::unique_ptr<int, void(*)(int*)> up_fptr(new int, deleter);
return 0;
}
カスタム削除int main()
{
// With a capture-less lambda.
auto l = [](int* p) {
std::cout << "Lambda deleter\n";
delete p;
};
std::unique_ptr<int, decltype(l)> up_lambda(new int, l);
return 0;
}
最も一般的な使用unique_ptr
関数に割り当てられたオブジェクトが削除されたことを確認する、かなり愚かな証明方法です.しかし、オブジェクトの所有権が移される必要がある状況があります、しかし、常に1つの場所に一度に残る;unique_ptr
この概念を直接表現する方法を提供します.次は何ですか.
次の記事では
std::shared_ptr
and std::weak_ptr
詳しくはまた、パフォーマンスに関してこれらのスマートポインタについて話します.Reference
この問題について(C +におけるスマートポインタを用いたメモリ管理-パート1), 我々は、より多くの情報をここで見つけました https://dev.to/pratikparvati/memory-management-using-smart-pointers-in-c-part-1-4j2kテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol