ガイドラインには、明示的なメモリ割り当てと割り当て解除に関する 6 つのルールがあります。六!現代の C++ には単純なルールがあるため、驚かれるかもしれません:new と delete を使用しないでください。明らかに、話はそれほど単純ではありません。
ここに 6 つのルールがあります。
- R.10:
malloc()
を避ける とfree()
- R.11:
new
の呼び出しを避ける とdelete
明示的に - R.12:明示的なリソース割り当ての結果をすぐにマネージャー オブジェクトに与える
- R.13:1 つの式ステートメントで最大 1 つの明示的なリソース割り当てを実行します
- R.14:???配列 vs. ポインタ パラメータ
- R.15:一致する割り当て/割り当て解除のペアを常にオーバーロードする
最後の 2 つのルールについては書きません。第一に、ルール R.14 は十分に焼き固められておらず、第二に、ルール R.15 は非常に特殊です。 new と delete のオーバーロードについて詳しく知りたい場合は、メモリの割り当てと解放に関する私の投稿を読んでください。
ルールについて詳しく説明する前に、ルールを理解するために必要な背景を少し説明します。 new を使用して C++ でオブジェクトを作成するには、2 つの手順があります。
<オール>operator new または operator new [] が最初のステップになります。コンストラクターの 2 番目のステップ。
同じ戦略が破壊にも適用されますが、その逆です。最初にデストラクタが呼び出され (存在する場合)、メモリは operator delete または operator delete [] で割り当て解除されます。この 2 段階の作成と破棄が、4 つのルールの理由です。それでは始めましょう。
R.10:malloc()
を避ける および free()
new と malloc、delete と free の違いは何ですか? C 関数 malloc と free は、仕事の半分しか行いません。 malloc はメモリを割り当て、free はメモリの割り当てを解除するだけです。 malloc はコンストラクタを呼び出しませんし、free はデストラクタを呼び出しません。
つまり、作成したばかりのオブジェクトを使用する場合 malloc を使用すると、未定義の動作が発生します。
// mallocVersusNew.cpp #include <iostream> #include <string> struct Record{ Record(std::string na = "Record"): name(na){} // (4) std::string name; }; int main(){ std::cout << std::endl; Record* p1 = static_cast<Record*>(malloc(sizeof(Record))); // (1) std::cout << p1->name << std::endl; // (3) auto p2 = new Record; // (2) std::cout << p2->name << std::endl; std::cout << std::endl; }
(1) Record オブジェクトのメモリのみを割り当てます。その結果、(3) の出力 p1->name は未定義の動作になります。対照的に、呼び出し (2) は、行 (4) でコンストラクターを呼び出します。未定義の動作とは、プログラムの出力について何も仮定できないことを意味します。
使用するプラットフォームと使用する GCC によって、プログラムの結果はまったく異なります。
- GCC 4.8.5 がローカル PC でコア ダンプを生成する
- GCC 4.9 (cppreference.com) は出力を生成しません
- GCC 7.1 (cppreference.com) は 期待される を生成します 出力
R.11:new
の呼び出しを避ける と delete
明示的に
このルールを覚えておく必要があります。このルールで強調されているのは、明示的にという言葉です。 標準テンプレート ライブラリのスマート ポインタまたはコンテナを使用すると、暗黙的にを使用するオブジェクトが得られるためです。 新規および削除。
R.12:マネージャー オブジェクトへの明示的なリソース割り当て
これは std::unique_ptr
void f(const std::string& name) { FILE* f = fopen(name, "r"); // open the file std::vector<char> buf(1024); fclose(f); // close the file }
R.13:最大 1 つの明示的なリソース割り当てを実行します単一の式ステートメント
このルールは少しトリッキーです。
void func(std::shared_ptr<Widget> sp1, std::shared_ptr<Widget> sp2){ ... } func(std::shared_ptr<Widget>(new Widget(1)), std::shared_ptr<Widget>(new Widget(2)));
この関数呼び出しは例外セーフではないため、メモリ リークが発生する可能性があります。なんで?その理由は、共有ポインターを初期化するために 4 つの操作を実行する必要があるためです。
<オール>コンパイラは、最初に Widget(1) と Widget(2) に自由にメモリを割り当ててから、両方を構築できます。
<オール>コンストラクターの 1 つが例外をスローすると、他のオブジェクトのメモリは自動的に解放されず、メモリ リークが発生します。
std::shared_ptr を作成するためのファクトリ関数 std::make_shared を使用することで、この問題を簡単に克服できます。
func(std::make_shared<Widget>(1), std::make_shared<Widget>(2));
std::make_shared は、例外がスローされた場合に関数が影響を与えないことを保証します。 std::unique_ptr を作成するためのペンダント関数 std::make_unique は、同じことを保証します。
次は?
リソース管理の次のルールはルール R.11 に従います。明示的に new と delete を呼び出すことは避けてください。したがって、次の投稿では、スマート ポインター std::unique_ptr、std::shared_ptr、および std::weak_ptr について説明します。