さまざまな言語を学習しているときに、オブジェクトがその場で割り当てられるのをよく見かけます。ほとんどの場合、次のようにJavaとC#で割り当てられます。
functionCall(new className(initializers));
これはメモリ管理言語では完全に合法であることを理解していますが、この手法をC ++で使用しても、メモリリークは発生しませんか?
さまざまな言語を学習しているときに、オブジェクトがその場で割り当てられるのをよく見かけます。ほとんどの場合、次のようにJavaとC#で割り当てられます。
functionCall(new className(initializers));
これはメモリ管理言語では完全に合法であることを理解していますが、この手法をC ++で使用しても、メモリリークは発生しませんか?
あなたのコードは有効ですが (functionCall() がポインターが削除されることを実際に保証すると仮定すると)、コードは壊れやすく、ほとんどの C++ プログラマーの頭の中で警鐘を鳴らします。
コードには複数の問題があります。
functionCall(new className(initializers), new className(initializers));
最初のものは正常に割り当てられましたが、2 番目のものは例外をスローしたとします (メモリ不足であるか、クラス コンストラクターが例外をスローした可能性があります)。functionCall は呼び出されず、メモリを解放できません。単純な (しかし面倒な) 解決策は、最初にメモリを割り当て、ポインタを格納してから、宣言されたのと同じスコープで解放することです (したがって、呼び出し元の関数がメモリを所有します)。
className* p = new className(initializers);
functionCall(p);
delete p;
しかし、これはまだ混乱しています。functionCall が例外をスローした場合はどうなりますか? その後、 p は削除されません。全体に try/catch を追加しない限り、まあまあ、面倒です。関数がもう少し複雑になり、functionCall の後、delete の前に戻る可能性がある場合はどうなるでしょうか? おっと、メモリリーク。維持することは不可能です。悪いコード。
したがって、優れた解決策の 1 つは、スマート ポインターを使用することです。
boost::shared_ptr<className> p = boost::shared_ptr<className>(new className(initializers));
functionCall(p);
これで、メモリの所有権が処理されます。はshared_ptr
メモリを所有し、解放されることを保証します。std::auto_ptr
もちろん、代わりに を 使用することもできますshared_ptr
が、通常期待されるセマンティクスを実装しています。
関数呼び出しを行うときに同じ行に複数の割り当てを行うという問題がまだ存在するため、別の行にメモリを割り当てたことに注意してください。それらの 1 つがまだスローする可能性があり、メモリ リークが発生しています。
一般に、スマート ポインターは、メモリ管理を処理するために必要な絶対最小値です。しかし、多くの場合、独自の RAII クラスを作成することで適切な解決策が得られます。
className
スタックに割り当てる必要があり、そのコンストラクターで、必要な割り当てを行いnew
ます。そして、そのデストラクタで、そのメモリを解放する必要があります。このようにして、メモリリークが発生しないことが保証され、関数呼び出しを次のように単純にすることができます。
functionCall(className(initializers));
C++ 標準ライブラリは次のように機能します。std::vector
は一例です。でベクトルを割り当てることは決してありませんnew
。スタックに割り当て、内部でメモリ割り当てを処理させます。
はい、関数内のメモリの割り当てを解除する限り。しかし、これはC++のベストプラクティスではありません。
場合によります。
これにより、メモリの「所有権」がfunctionCAll()に渡されます。オブジェクトを解放するか、後で解放できるようにポインタを保存する必要があります。このように生のポインタの所有権を渡すことは、メモリの問題をコードに組み込む最も簡単な方法の1つであり、リークまたは二重削除のいずれかです。
C++ では、そのようにメモリを動的に作成しません。
代わりに、一時的なスタック オブジェクトを作成します。
オブジェクトの寿命を関数の呼び出しよりも長くしたい場合は、new を介してヒープ オブジェクトを作成するだけで済みます。この場合、 new をスマートポインターと組み合わせて使用できます(例については、他の回答を参照してください)。
// No need for new or memory management just do this
functionCall(className(initializers));
// This assumes you can change the functionCall to somthing like this.
functionCall(className const& param)
{
<< Do Stuff >>
}
非 const 参照を渡したい場合は、次のようにします。
calssName tmp(initializers);
functionCall(tmp);
functionCall(className& param)
{
<< Do Stuff >>
}
呼び出している関数に所有権の受け入れセマンティクスがある場合は安全です。これが必要だった時期を思い出せないので、珍しいと思います。
関数がこのように動作する場合、その引数をスマート ポインター オブジェクトとして取得して、意図が明確になるようにする必要があります。すなわち
void functionCall(std::auto_ptr<className> ptr);
それよりも
void functionCall(className* ptr);
これにより、所有権の譲渡が明示的に行われ、関数の実行がスコープ外になると、呼び出し元の関数は ptr が指すメモリを破棄します。
これは、スタック上に作成されたオブジェクトに対しては機能しますが、C++の通常のポインターに対しては機能しません。
自動ポインタはそれを処理できるかもしれませんが、私はそれらを十分に理解していません。
一般的に、メモリをリークしたい場合を除いて、いいえ。実際、ほとんどの場合、これは機能しません。
new T();
C++ではT*であり、Tではありません(C#では、新しいT()はTを返します)。