web-dev-qa-db-ja.com

C ++で新しいキーワードを使用する必要があるのはいつですか?

私はしばらくの間C++を使用していますが、newキーワードについて疑問に思っていました。単純に、私はそれを使うべきなのでしょうか?

1)newキーワードを使用して...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";

2)newキーワードなし...

MyClass myClass;
myClass.MyField = "Hello world!";

実装の観点からは、それらはそれほど違わないように見えます(しかし、違いはあると確信しています)...しかし、私の第一言語はC#であり、もちろん最初の方法は私が慣れているものです。

困難なのは、方法1がstd C++クラスで使用するのが難しいことです。

どの方法を使用すればよいですか?

更新1:

私は最近newキーワードをheapメモリに使用しました(またはfree store)機能)。スタックを使用する前に、要素の半分がスコープ外で破損する原因となっていた場所で、ヒープの使用に切り替えることで、要素が確実に機能するようになりました。わーい!

アップデート2:

私の友人は最近、newキーワードを使用するための簡単なルールがあると言った。 newと入力するたびに、deleteと入力します。

Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

これは、常にどこかに削除を配置する必要があるため(つまり、デストラクタなどにカットアンドペーストするとき)、メモリリークを防ぐのに役立ちます。

254
Nick Bolton

方法1(newを使用)

  • free store でオブジェクトにメモリを割り当てます(これはheapと同じことがよくあります)
  • オブジェクトを後で明示的にdeleteする必要があります。 (削除しないと、メモリリークが発生する可能性があります)
  • メモリは、deleteまで割り当てられたままです。 (つまり、returnを使用して作成したオブジェクトをnewできます)
  • 質問の例は、ポインタがdeletedでない限り leak memory になります。そして、常に削除する必要があります。どの制御パスが使用されるか、または例外がスローされるかどうかに関係なく。

方法2(newを使用しない)

  • stack(すべてのローカル変数が行く場所)のオブジェクトにメモリを割り当てます。通常、スタックに使用できるメモリは少なくなります。割り当てるオブジェクトが多すぎると、スタックオーバーフローのリスクがあります。
  • 後でdeleteする必要はありません。
  • 範囲外になると、メモリは割り当てられなくなります。 (つまり、returnはスタック上のオブジェクトへのポインターであってはなりません)

使用する限り。上記の制約を考慮して、最適な方法を選択します。

簡単なケース:

  • deleteの呼び出しを心配したくない場合(および メモリリーク を引き起こす可能性がある場合)、newを使用しないでください。
  • 関数からオブジェクトへのポインターを返したい場合は、newを使用する必要があります
285

2つの間に重要な違いがあります。

newで割り当てられていないものはすべて、C#の値型とほとんど同じように動作します(そして、これらのオブジェクトはおそらく最も一般的/明白なケースですが、常にそうではないスタックに割り当てられるとよく​​言われます。より正確には、new自動保存期間newで割り当てられたものはすべてヒープに割り当てられ、C#の参照型とまったく同じように、そのポインターが返されます。

スタックに割り当てられるものはすべて、コンパイル時に決定される一定のサイズでなければなりません(コンパイラはスタックポインタを正しく設定するか、オブジェクトが別のクラスのメンバーである場合、その他のクラスのサイズを調整する必要があります) 。これが、C#の配列が参照型である理由です。参照型を使用すると、実行時に要求するメモリ量を決定できるためです。ここでも同じことが言えます。一定サイズ(コンパイル時に決定できるサイズ)の配列のみが、自動ストレージ期間(スタック上)で割り当てられます。 newを呼び出して、動的にサイズ設定された配列をヒープに割り当てる必要があります。

(そして、それがC#との類似性が止まるところです)

現在、スタックに割り当てられたものはすべて「自動」ストレージ期間を持っています(実際には変数をautoとして宣言できますが、これは他のストレージタイプが指定されていない場合のデフォルトなので、実際にはキーワードは実際には使用されませんが、から来ます)

自動保存期間とは、その名の通り、変数の期間が自動的に処理されることを意味します。対照的に、ヒープに割り当てられたものはすべて手動で削除する必要があります。以下に例を示します。

void foo() {
  bar b;
  bar* b2 = new bar();
}

この関数は、考慮する価値のある3つの値を作成します。

1行目では、スタック上のb型の変数barを宣言しています(自動継続時間)。

2行目では、スタック上でbarポインターb2を宣言し(自動継続時間)、andがnewを呼び出し、ヒープにbarオブジェクトを割り当てます。 (動的持続時間)

関数が戻ると、次のことが起こります。最初に、b2が範囲外になります(破棄の順序は常に構築の順序とは逆です)。しかし、b2は単なるポインタであるため、何も起こらず、占有しているメモリは単に解放されます。そして重要なのは、メモリポイント(ヒープ上のbarインスタンス)が変更されないことです。ポインタのみが自動継続時間を持っているため、ポインタのみが解放されます。 2番目に、bは範囲外になります。したがって、barは自動継続時間を持つため、デストラクタが呼び出され、メモリが解放されます。

そして、ヒープ上のnewinstance?おそらくまだそこにあります。誰もそれを削除する気にならないので、メモリをリークしました。

この例から、自動持続時間を持つものはすべてguaranteedであり、スコープから外れるとデストラクタが呼び出されることがわかります。それは便利です。ただし、ヒープに割り当てられたものは必要な限り持続し、配列の場合のように動的にサイズ変更できます。それも便利です。これを使用して、メモリ割り当てを管理できます。 Fooクラスがそのコンストラクタでヒープにメモリを割り当て、デストラクタでそのメモリを削除した場合はどうなるでしょう。そうすれば、両方の長所を最大限に活用でき、安全なメモリ割り当てが再び解放されることが保証されますが、すべてをスタックに強制するという制限はありません。

そして、それがほとんどのC++コードの動作とまったく同じです。たとえば、標準ライブラリのstd::vectorを見てください。通常はスタックに割り当てられますが、動的にサイズ変更およびサイズ変更できます。そして、必要に応じてヒープ上のメモリを内部的に割り当てることでこれを行います。クラスのユーザーはこれを見ることはないので、メモリをリークしたり、割り当てたものをクリーンアップすることを忘れたりすることはありません。

この原則はRAII(リソースの取得は初期化)と呼ばれ、取得および解放する必要があるリソースに拡張できます。 (ネットワークソケット、ファイル、データベース接続、同期ロック)。それらはすべてコンストラクターで取得し、デストラクタで解放できるため、取得したすべてのリソースが再び解放されることが保証されます。

原則として、高レベルコードから直接new/deleteを使用しないでください。常にメモリを管理できるクラスでラップし、メモリが再び解放されるようにします。 (はい、この規則には例外がある場合があります。特に、スマートポインターではdeleteを直接呼び出す必要があり、コンストラクターにポインターを渡す必要があります。親指の)

111
jalf

どの方法を使用すればよいですか?

これは、入力の好みではなく、コンテキストによって決定されることはほとんどありません。オブジェクトをいくつかのスタックに保持する必要がある場合、またはスタックに対して重すぎる場合は、フリーストアに割り当てます。また、オブジェクトを割り当てるため、メモリを解放する責任もあります。 delete演算子を検索します。

フリーストア管理を使用する負担を軽減するために、人々はauto_ptrunique_ptrのようなものを発明しました。これらをご覧になることを強くお勧めします。タイピングの問題にも役立つかもしれません;-)

13
dirkgently

C++で記述している場合は、おそらくパフォーマンスのために記述しています。新規およびフリーストアの使用は、スタックの使用(特にスレッドの使用)よりもはるかに遅いため、必要な場合にのみ使用してください。

他の人が言ったように、オブジェクトが関数またはオブジェクトスコープ外に存在する必要がある場合、オブジェクトが本当に大きい場合、またはコンパイル時に配列のサイズがわからない場合、newが必要です。

また、deleteの使用を避けてください。代わりに、新しいものをスマートポインターにラップします。スマートポインター呼び出しで削除してください。

スマートポインターがスマートではない場合があります。 std :: auto_ptr <>をSTLコンテナ内に保存しないでください。コンテナー内のコピー操作のため、ポインターがすぐに削除されます。もう1つのケースは、オブジェクトへのポインターの非常に大きなSTLコンテナーがある場合です。 boost :: shared_ptr <>は、参照カウントを上下させるため、大量の速度オーバーヘッドがあります。その場合のより良い方法は、STLコンテナーを別のオブジェクトに配置し、そのオブジェクトにコンテナー内のすべてのポインターでdeleteを呼び出すデストラクターを与えることです。

9
Zan Lynx

簡単な答えは次のとおりです。C++の初心者の場合、nevernewまたはdeleteを自分で使用する必要があります。

代わりに、std::unique_ptrstd::make_unique(またはそれほど頻繁ではないが、std::shared_ptrstd::make_shared)などのスマートポインターを使用する必要があります。そうすれば、メモリリークについてほとんど心配する必要はありません。そして、より高度な場合でも、ベストプラクティスは通常、newおよびdeleteを使用しているカスタム方法を、オブジェクトライフサイクルの問題専用の小さなクラス(カスタムスマートポインターなど)にカプセル化することです。

もちろん、舞台裏では、これらのスマートポインターはまだ動的な割り当てと割り当て解除を実行しているため、それらを使用するコードには関連するランタイムオーバーヘッドがあります。ここでの他の答えは、これらの問題、およびスマートポインターを使用するタイミングと、スタック上にオブジェクトを作成するか、オブジェクトの直接のメンバーとして組み込むかを設計決定する方法について説明しました。しかし、私のエグゼクティブサマリーは次のようになります。何かが強制されるまで、スマートポインターや動的割り当てを使用しないでください。

5
Daniel Schepler

newキーワードがなければ、それを call stack に保存しています。スタックに非常に大きな変数を格納すると、 スタックオーバーフロー になります。

2
vartec

関数からmyClassを渡すのですか、それともその関数の外に存在することを期待していますか?他の人が言ったように、ヒープに割り当てていないときのスコープがすべてです。関数を終了すると、(最終的に)消えます。初心者が犯す典型的な間違いの1つは、あるクラスのローカルオブジェクトを関数で作成し、ヒープに割り当てずにそれを返すという試みです。この種のことを以前のC++でデバッグしたことを思い出すことができます。

1
itsmatt

変数が単一の関数のコンテキスト内でのみ使用される場合、スタック変数、つまりオプション2を使用することをお勧めします。他の人が言ったように、スタック変数のライフタイムを管理する必要はありません。自動的に破壊されました。また、ヒープ上の変数の割り当て/割り当て解除は、比較すると遅いです。関数が頻繁に呼び出される場合、スタック変数とヒープ変数を使用すると、パフォーマンスが大幅に向上します。

そうは言っても、スタック変数が不十分な明らかな例がいくつかあります。

スタック変数のメモリフットプリントが大きい場合、スタックがオーバーフローする危険があります。デフォルトでは、Windowsでは 各スレッドのスタックサイズは1 MB です。サイズが1 MBのスタック変数を作成することはほとんどありませんが、スタック使用率は累積的であることに注意する必要があります。関数が別の関数を呼び出す別の関数を呼び出す関数を呼び出す場合、これらの関数のすべてのスタック変数は同じスタック上のスペースを占有します。再帰関数は、再帰の深さに応じて、この問題にすぐに遭遇する可能性があります。これが問題になる場合は、スタックのサイズを増やすか(推奨されません)、new演算子を使用して変数をヒープに割り当てることができます(推奨)。

他の、より可能性の高い条件は、変数が関数のスコープを超えて「生きる」必要があるということです。この場合、変数をヒープに割り当てて、特定の関数のスコープ外に到達できるようにします。

1
Matt Davis

単純な答えはイエスです-new()はヒープ上にオブジェクトを作成します(明示的にdeleteを呼び出すことでそのライフタイムを管理する必要があるという不幸な副作用があります)が、2番目の形式は現在のスタックにオブジェクトを作成しますスコープとそのオブジェクトは、スコープ外になると破棄されます。

1
Timo Geusch

簡単な答えは「はい」です。「新しい」キーワードを使用すると、オブジェクトデータはスタックではなくヒープに格納されるため、非常に重要です。これは最も重要です。

0
RAGNO

2番目のメソッドは、intで宣言されたものや関数に渡されるパラメーターのリストなどと共に、スタック上にインスタンスを作成します。

最初のメソッドは、スタック上のpointerのスペースを作ります。これは、新しいMyClassがヒープまたは空きストアに割り当てられているメモリ内の場所に設定します。

また、最初のメソッドではdeleteで作成したものをnewにする必要がありますが、2番目のメソッドでは、クラスがスコープから外れると自動的に破棄され、解放されます(通常は次の閉じ括弧)。

0
greyfade