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


273

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

1)新しいキーワードで...

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

2)新しいキーワードなし...

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

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

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

どの方法を使用する必要がありますか?

更新1:

私は最近、スコープから外れる(つまり、関数から返される)大きな配列のヒープメモリ(またはfree store)に新しいキーワードを使用しました。以前はスタックを使用していたため、要素の半分がスコープ外で破損していましたが、ヒープの使用に切り替えることで、要素が正常に機能するようになりました。わーい!

アップデート2:

私の友人は最近、newキーワードを使用するための簡単なルールがあると私に言った。タイプするたびにnew、タイプしてくださいdelete

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

これは、常に削除をどこかに置く必要があるため(つまり、切り取りしてデストラクタなどに貼り付ける場合)、メモリリークを防ぐのに役立ちます。


6
短い答えは、それがうまくいくときは短いバージョンを使うことです。:)
jalf 2009年

11
常に対応する削除を書くよりも優れた技術-使用のSTLコンテナなどスマートポインタstd::vectorstd::shared_ptr。これらはあなたへの呼び出しnewdeleteあなたのための呼び出しをラップしているので、メモリをリークする可能性はさらに低くなります。たとえば、自問してみてください。delete例外がスローされる可能性のあるすべての場所に、対応するものを常に置くことを覚えていますか 中に入れdelete、手でSは、あなたが思っているより難しいです。
AshleysBrain、2010

@nbolton日時:UPDATE 1 - ++ C程度美しいものの一つは、C#のようなゴミ収集langs一方あなたは、スタック上にユーザー定義型を格納することができますということです力はあなたが上にデータを保存するために、ヒープヒープにデータを格納すると、スタックデータを格納するよりも多くのリソースが消費されるため、UDTがデータを格納するために大量のメモリを必要とする場合を除いて、ヒープよりもスタックを優先する必要があります。(これは、オブジェクトがデフォルトで値によって渡されることも意味します)。あなたの問題に対するより良い解決策は、配列を参照によって関数に渡すことです。
Charles Addis

回答:


304

方法1(を使用new

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

方法2(を使用しないnew

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

どちらを使用するか。上記の制約を前提として、最適な方法を選択します。

いくつかの簡単なケース:

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

4
1つのnitpick-mallocが「ヒープ」から割り当てるのに対して、新しいオペレーターは「フリーストア」からメモリを割り当てると思います。実際には通常は同じですが、これらが同じであることは保証されていません。gotw.ca/gotw/009.htmを参照してください。
Fred Larson、

4
どちらを使用するかについて、あなたの答えがより明確になると思います。(99%の場合、選択は簡単です。コンストラクター/デストラクターでnew / deleteを呼び出すラッパーオブジェクトでメソッド2を使用します)
jalf

4
@jalf:方法2は新しい方法を使用しない方法です:-/いずれにせよ、方法2(新しい方法がない方法)を使用すると、コーディングがはるかに簡単になる(たとえば、エラーケースの処理)ことが多い
ダニエルルケミナン09年

別のひっくり返る...例外に直面しても、Nickの最初の例ではメモリリークが発生するのに対し、2番目の例ではメモリリークが発生しないことをより明確にする必要があります。
アラファンギオン2009年

4
@フレッド、アラファンギオン:あなたの洞察をありがとう。あなたのコメントを回答に組み込みました。
Daniel LeCheminant 2009年

118

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

すべてはが割り当てられていないnew多くのC#で値型のように振る舞う(人々はしばしば、これらのオブジェクトは、おそらく最も一般的な/明白な場合で、スタック、上に割り当てられ、常にではないが、真されていると言う。より正確には、割り当てられたオブジェクト使用せずにnew持っている自動ストレージをdurationで 割り当てられたものnewはすべてヒープに割り当てられ、C#の参照型とまったく同じようにヒープへのポインタが返されます。

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

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

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

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

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

この関数は、検討に値する3つの値を作成します。

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

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

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

そして、barヒープ上のインスタンス?それはおそらくまだそこにあります。誰もそれを削除する気にならなかったので、メモリをリークしました。

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

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

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

原則として、高レベルのコードから直接new / deleteを使用しないでください。常にメモリを管理できるクラスでラップしてください。これにより、メモリが再び解放されます。(はい、このルールには例外がある可能性があります。特に、スマートポインターはnew、直接呼び出す必要があり、ポインターをコンストラクターに渡す必要があります。コンストラクターが引き継ぎ、delete正しく呼び出されることを保証します。しかし、これは依然として非常に重要な経験則です)


2
「newで割り当てられていないものはすべてスタックに置かれます」私が取り組んだシステムにはありません...通常、初期化された(そして初期化されていない)グローバル(静的)データは独自のセグメントに置かれます。たとえば、.data、.bssなどのリンカーセグメント。Pedantic、I know ...
Dan

もちろん、そうです。静的データについてはあまり考えていませんでした。もちろん悪い。:)
jalf 2009年

2
なぜスタックに割り当てられるものは一定のサイズでなければならないのですか?
user541686

常にそうであるとは限らず、それを回避する方法はいくつかありますが、スタック上にあるため、通常は回避します。スタックの一番上にある場合は、サイズを変更できる可能性がありますが、他に何かがプッシュされると、「壁」になり、どちらかの側のオブジェクトに囲まれるため、実際にはサイズ変更できません。はい、それはいつもと言っているの固定サイズを持っていることは単純化のビットですが、それは基本的な考え方(と私はあなたがスタック割り当てとあまりにも独創的であるとするC関数でぐちゃぐちゃお勧めしません)伝える
jalfを

14

どの方法を使用する必要がありますか?

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

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


10

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

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

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

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


10

簡単に言えば、C ++の初心者であれば、自分自身を使用することはできませnewdelete

代わりに、std::unique_ptrand std::make_unique(またはそれほど頻繁ではないが、std::shared_ptrand std::make_shared)などのスマートポインターを使用してください。そうすれば、メモリリークについてほとんど心配する必要がなくなります。そして、あなたは、より高度な方にも、ベストプラクティスは、通常使用しているカスタム方法カプセル化するだろうnewdeleteちょうどオブジェクトのライフサイクルの問題に捧げられて(例えばカスタムスマートポインタのような)小さなクラスにします。

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


時間が経過するにつれて答えがどのように変化するかを見るのは興味深いです;)
ウルフ


2

単純な答えは「はい」です。new()は、ヒープ上にオブジェクトを作成します(残念ながら、存続期間を管理する必要があるという副作用があります(明示的にdeleteを呼び出すことにより))。2番目のフォームは、現在のスタックにオブジェクトを作成します。スコープとそのオブジェクトは、スコープ外になると破棄されます。


1

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

とはいえ、スタック変数が不十分である明らかな例がいくつかあります。

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

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


1

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


0

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

最初の方法では、スタック上のポインタの場所をMyClass確保します。これは、新しいヒープが割り当てられたメモリ内の場所または空きストアに設定します。

最初の方法でもでdelete作成したものが必要ですがnew、2番目の方法では、クラスがスコープから外れると(通常は次の右中括弧)、クラスが自動的に破壊されて解放されます。


-1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.