C ++プログラマーが「新規」の使用を最小限に抑える必要があるのはなぜですか?


873

私がつまずいスタックオーバーフロー質問のstd ::リストを使用しているときのstd ::文字列を使用してメモリリーク<はstd :: string>に、そしてコメントの一つは、この言います:

使用を中止newそんなに。どこで新しいものを使ったのか、私にはわかりません。C ++では値によってオブジェクトを作成できます。これは、この言語を使用することの大きな利点の1つです。
すべてをヒープに割り当てる必要はありません。Javaプログラマーの
ように考えるのをやめます。

それが何を意味するのかよくわかりません。

C ++でオブジェクトをできるだけ頻繁に値で作成する必要があるのはなぜですか。また、内部的にどのような違いがありますか?
答えを誤解しましたか?

回答:


1037

広く使用されている2つのメモリ割り当て技術があります。自動割り当てと動的割り当てです。通常、それぞれに対応するメモリ領域があります。スタックとヒープです。

スタック

スタックは常に順次方式でメモリを割り当てます。逆の順序でメモリを解放する必要があるため、これを行うことができます(先入れ先出し、FILO)。これは、多くのプログラミング言語におけるローカル変数のメモリ割り当て手法です。最小限の簿記が必要であり、割り当てる次のアドレスが暗黙的であるため、非常に高速です。

C ++では、スコープの最後でストレージが自動的に要求されるため、これは自動ストレージと呼ばれます。現在のコードブロック(を使用して区切られた{})の実行が完了するとすぐに、そのブロック内のすべての変数のメモリが自動的に収集されます。これは、デストラクタが呼び出されてリソースをクリーンアップする瞬間でもあります。

ヒープ

ヒープにより、より柔軟なメモリ割り当てモードが可能になります。簿記はより複雑で、割り当ては遅くなります。暗黙的な解放ポイントはないため、deleteor delete[]freeC)を使用して手動でメモリを解放する必要があります。ただし、暗黙的なリリースポイントがないことは、ヒープの柔軟性の鍵です。

ダイナミックアロケーションを使用する理由

ヒープの使用が遅く、メモリリークやメモリの断片化につながる可能性がある場合でも、動的割り当ての制限は少ないため、完全に適切な使用例があります。

ダイナミックアロケーションを使用する2つの主な理由:

  • コンパイル時に必要なメモリ量がわかりません。たとえば、テキストファイルを文字列に読み取る場合、通常、ファイルのサイズがわからないため、プログラムを実行するまで、割り当てるメモリ量を決定できません。

  • 現在のブロックを終了した後も存続するメモリを割り当てたい。たとえばstring readfile(string path)、ファイルの内容を返す関数を記述したい場合があります。この場合、スタックがファイルの内容全体を保持できたとしても、関数から戻り、割り当てられたメモリブロックを保持することはできません。

ダイナミックアロケーションが不要なことが多い理由

C ++には、デストラクターと呼ばれるきちんとした構成があります。このメカニズムでは、リソースの有効期間を変数の有効期間に合わせることで、リソースを管理できます。この手法はRAIIと呼ばれ、C ++の際立ったポイントです。リソースをオブジェクトに「ラップ」します。 std::string完璧な例です。このスニペット:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

実際には、可変量のメモリを割り当てます。std::stringオブジェクトはヒープとそのデストラクタで解放し、それを使用してメモリを割り当てます。この場合は、リソースを手動で管理する必要はなく、動的メモリ割り当てのメリットが得られます。

特に、このスニペットでは、

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

不要な動的メモリ割り当てがあります。このプログラムでは、さらにタイピング(!)が必要であり、メモリの割り当てを解除し忘れるリスクがあります。これには明らかな利点はありません。

自動ストレージをできるだけ頻繁に使用する理由

基本的に、最後の段落はそれを要約します。自動ストレージをできるだけ頻繁に使用すると、プログラムが次のようになります。

  • 入力が速くなります。
  • 実行すると速くなります。
  • メモリ/リソースリークが発生しにくい。

ボーナスポイント

参照された質問には、追加の懸念があります。特に、次のクラス:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

実際には、次のものよりも使用するのがはるかに危険です。

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

その理由はstd::string、コピーコンストラクタを適切に定義しているからです。次のプログラムを検討してください。

int main ()
{
    Line l1;
    Line l2 = l1;
}

元のバージョンを使用deleteすると、同じプログラムで2回使用されるため、このプログラムはおそらくクラッシュします。修飾されたバージョンを使用して、各Lineインスタンスが独自の文字列所有するインスタンスを、それ自体のメモリを有する各両方は、プログラムの終了時に解放されます。

その他の注意事項

上記のすべての理由により、RAIIの広範な使用はC ++でのベストプラクティスと見なされます。ただし、すぐには明らかではない追加の利点があります。基本的に、それはその部分の合計よりも優れています。メカニズム全体が構成しますます。それはスケーリングします。

Lineクラスをビルディングブロックとして使用する場合:

 class Table
 {
      Line borders[4];
 };

その後

 int main ()
 {
     Table table;
 }

4つのstd::stringインスタンス、4つのLineインスタンス、1つのTableインスタンス、およびすべての文字列の内容を割り当て、すべてが自動的に解放されます。


55
最後にRAIIについて言及する場合は+1ですが、例外とスタックの巻き戻しについて何かがあるはずです。
東武

7
@東武:ええ、でも、この投稿はすでにかなり長いので、OPの質問に焦点を当てたままにしたかったのです。私はブログの投稿か何かを書いて、ここからそれにリンクすることになります。
アンドレ・キャノン

15
(少なくともC ++ 1xまで)スタック割り当てのマイナス面について言及するのに最適な補足になります-注意しないと、不必要にコピーする必要があることがよくあります。たとえば、死んだときにMonstera Treasureを吐き出しWorldます。そのDie()方法でそれは世界に宝物を追加します。それはworld->Add(new Treasure(/*...*/))死んだ後の宝物を保存するために他で使用しなければなりません。選択肢はshared_ptr(やり過ぎかもしれません)、auto_ptr(所有権の譲渡のセマンティックが不十分)、値渡し(無駄)、move+ unique_ptr(まだ広く実装されていない)です。
kizzx2

7
スタックに割り当てられたローカル変数についてあなたが言ったことは、少し誤解を招くかもしれません。「スタック」とは、スタックフレームを格納する呼び出しスタックを指します。LIFO形式で保存されるのはこれらのスタックフレームです。特定のフレームのローカル変数は、構造体のメンバーであるかのように割り当てられます。
someguy

7
@someguy:確かに、説明は完璧ではありません。実装は、その割り当てポリシーに自由があります。ただし、変数はLIFO方式で初期化および破棄する必要があるため、類推は成り立ちます。私はそれが答えをさらに複雑にする仕事だとは思わない。
アンドレ・キャノン

171

スタックはより高速でリーク防止であるため

C ++では、特定の関数内のすべてのローカルスコープオブジェクトに、スタック上にスペースを割り当てるために1つの命令しかかかりません。そのメモリをリークすることは不可能です。そのコメントは、「ヒープではなくスタックを使用する」のようなことを意図した(または意図したはずの)コメントです。


18
「スペースを割り当てるには、たった1つの指示が必要です」-ああ、ナンセンス。確かに、スタックポインターに追加するための命令は1つだけですが、クラスに興味深い内部構造がある場合は、スタックポインターに追加するだけではありません。コンパイラーはコンパイル時に参照を管理するので、Javaではスペースを割り当てるための指示は必要ないということも同様に有効です。
チャーリーマーティン

31
@Charlieは正しいです。 自動変数は速く、フールプルーフがより正確になります。
Oliver Charlesworth 2011年

28
@Charlie:クラスの内部はいずれかの方法で設定する必要があります。必要なスペースの割り当てについて比較が行われています。
オリバーチャールズワース2011年

51
int x; return &x;
peterchen

16
速いはい。しかし、間違いなく絶対確実ではありません。絶対に簡単なものはありません。StackOverflowを取得できます:)
rxantos

107

なぜ複雑なのか。

まず、C ++はガベージコレクションされません。したがって、すべての新規に対して、対応する削除が必要です。この削除に失敗すると、メモリリークが発生します。さて、このような単純なケースでは:

std::string *someString = new std::string(...);
//Do stuff
delete someString;

これは簡単です。しかし、「Do stuff」が例外をスローするとどうなりますか?エラー:メモリリーク。「何かをする」という問題がreturn早期に発生するとどうなりますか?エラー:メモリリーク。

そして、これは最も単純なケースです。あなたがたまたまその文字列を誰かに返した場合、今度は彼らはそれを削除しなければなりません。そして、それを引数として渡した場合、それを受け取った人はそれを削除する必要がありますか?いつ削除すべきですか?

または、これを行うことができます:

std::string someString(...);
//Do stuff

いいえdelete。オブジェクトは「スタック」に作成され、スコープ外になると破棄されます。オブジェクトを返して、その内容を呼び出し元の関数に転送することもできます。オブジェクトを関数に渡すことができます(通常、参照またはconst-referenceとして:)void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)

すべてなしnewdelete。誰がメモリを所有しているか、誰がメモリを削除する責任があるかは問題ではありません。もしあなたがそうするなら:

std::string someString(...);
std::string otherString;
otherString = someString;

otherStringデータのコピーがあることがわかりsomeStringます。これはポインタではありません。別のオブジェクトです。それらはたまたま同じ内容を持っているかもしれませんが、他に影響を与えることなく一方を変更できます:

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

アイデアを参照してください?


1
そのメモについて...オブジェクトがに動的に割り当てられmain()、プログラムの期間中存在し、状況によりスタック上に簡単に作成できない場合、そのオブジェクトへのポインタが、オブジェクトへのアクセスを必要とするすべての関数に渡されます、これはプログラムがクラッシュした場合にリークを引き起こす可能性がありますか、それとも安全ですか?プログラムのすべてのメモリの割り当てを解除するOSも論理的に割り当てを解除する必要があるため、後者を想定しますが、に関しては何も想定しませんnew
ジャスティン時間-モニカ

4
@JustinTimeプログラムの存続期間中存続する動的に割り当てられたオブジェクトのメモリを解放することについて心配する必要はありません。プログラムが実行されると、OSはその物理メモリまたは仮想メモリのアトラスを作成します。仮想メモリ空​​間内のすべてのアドレスは物理メモリのアドレスにマップされ、プログラムが終了すると、その仮想メモリにマップされているすべてのものが解放されます。したがって、プログラムが完全に終了する限り、割り当てられたメモリが削除されないことを心配する必要はありません。
Aiman Al-Eryani 2016

75

によって作成されたオブジェクトは、new最終的deleteにリークしないようにする必要があります。デストラクタは呼び出されず、メモリは解放されません。C ++にはガベージコレクションがないため、問題があります。

値によって(つまり、スタック上に)作成されたオブジェクトは、スコープから外れると自動的に消滅します。デストラクタ呼び出しはコンパイラによって挿入され、メモリは関数が戻るときに自動的に解放されます。

スマートポインタが好きunique_ptrshared_ptrぶら下がっている参照の問題を解決しコーディング規則が必要であり、他の潜在的な問題(コピー可能性、参照ループなど)があります。

また、マルチスレッドの多いシナリオでnewは、スレッド間の競合のポイントになります。使いすぎるとパフォーマンスに影響する可能性がありますnew。各スレッドには独自のスタックがあるため、スタックオブジェクトの作成は、定義上、スレッドローカルです。

値オブジェクトの欠点は、ホスト関数が戻るとオブジェクトが死ぬことです。値によるコピー、戻り、または移動によってのみ、それらへの参照を呼び出し元に戻すことはできません。


9
+1。「「によって作成されたオブジェクトは、new最終的deleteにリークしないようにする必要があります。」-さらに悪いことに、- new[]と一致する必要があり、-edメモリまたは-edメモリのdelete[]場合、未定義の動作が発生します-これについて警告するコンパイラはほとんどありません(Cppcheckなどの一部のツールは、可能な場合に実行します)。delete new[]delete[] new
トニーデルロイ、2011年

3
@TonyDelroyコンパイラがこれを警告できない状況があります。関数がポインタを返す場合、new(単一の要素)またはnew []の場合に作成できます。
fbafelipe 2012年

32
  • C ++は独自のメモリマネージャを採用していません。C#、Javaなどの他の言語には、メモリを処理するガベージコレクターがあります。
  • C ++の実装は通常、オペレーティングシステムルーチンを使用してメモリを割り当てます。新規/削除が多すぎると、使用可能なメモリが断片化する可能性があります
  • どのアプリケーションでも、メモリが頻繁に使用されている場合は、メモリを事前に割り当てて、不要になったときに解放することをお勧めします。
  • メモリ管理が不適切だと、メモリリークが発生する可能性があり、追跡が非常に困難になります。したがって、関数のスコープ内でスタックオブジェクトを使用することは、実証済みの手法です。
  • スタックオブジェクトを使用する場合の欠点は、関数の戻り、関数への受け渡しなどでオブジェクトの複数のコピーが作成されることです。ただし、スマートコンパイラはこれらの状況を十分に認識しており、パフォーマンスのために最適化されています。
  • メモリが2つの異なる場所で割り当てられて解放される場合、C ++では本当に退屈です。リリースの責任は常に問題であり、ほとんどの場合、一般的にアクセス可能なポインター、スタックオブジェクト(可能な限り最大)、auto_ptr(RAIIオブジェクト)などの手法に依存しています
  • 最良のことは、メモリを制御でき、最悪の場合は、アプリケーションに不適切なメモリ管理を採用した場合、メモリを制御できないことです。メモリの破損が原因で発生するクラッシュは最も厄介で、追跡が困難です。

5
実際、メモリを割り当てるすべての言語には、cなどのメモリマネージャがあります。ほとんどは非常に単純です。すなわち、int * x = malloc(4); int * y = malloc(4); ...最初の呼び出しはメモリを割り当て、別名osにメモリを要求します(通常はチャンク1k / 4kで)ので、2番目の呼び出しは実際にはメモリを割り当てませんが、割り当てられた最後のチャンクの一部を提供します。IMO、ガベージコレクターはメモリの自動割り当て解除のみを処理するため、メモリマネージャーではありません。メモリマネージャと呼ばれるには、メモリの割り当て解除だけでなく、メモリの割り当ても処理する必要があります。
Rahly

1
ローカル変数はスタックを使用するので、コンパイラはmalloc()必要なメモリを割り当てるための呼び出しやそのフレンドを発行しません。ただし、スタックはスタック内のアイテムを解放できません。スタックメモリが解放される唯一の方法は、スタックの先頭から巻き戻すことです。
Mikko Rantalainen、2018年

C ++は「オペレーティングシステムルーチンを使用」しません。それは言語の一部ではなく、単なる一般的な実装です。C ++は、オペレーティングシステムがなくても実行できます。
einpoklum

22

新しいものをできるだけ少なくするためのいくつかの重要な理由を見逃しているようです。

オペレーターのnew実行時間は非決定的です

呼び出しnewによって、OSが新しい物理ページをプロセスに割り当てる場合とそうでない場合があります。頻繁に行う場合、これは非常に遅くなる可能性があります。または、適切なメモリロケーションがすでに準備されている可能性があります。プログラムが一貫性のある予測可能な実行時間を必要とする場合(リアルタイムシステムやゲーム/物理シミュレーションなど)new、タイムクリティカルなループを回避する必要があります。

演算子newは暗黙的なスレッド同期です

はい、あなたは私に聞いた、あなたのOSはあなたのページテーブルが一貫していることを確認する必要がある、そしてそれでそのような呼び出しnewはあなたのスレッドが暗黙のミューテックスロックを獲得する原因となる。new多くのスレッドから一貫して呼び出している場合は、実際にスレッドをシリアル化しています(私はこれを32 CPUで実行しており、それぞれnewが数百バイトを取得するためにヒットしています、これはデバッグするためのロイヤルピタでした)。

遅い、断片化、エラーが発生しやすいなどの残りの部分は、すでに他の回答で言及されています。


2
どちらも、配置new / deleteを使用して、事前にメモリを割り当てることで回避できます。または、自分でメモリを割り当て/解放してから、コンストラクタ/デストラクタを呼び出すこともできます。これがstd :: vectorの通常の動作方法です。
rxantos 2015

1
@rxantos OPを読んでください。この質問は、不要なメモリ割り当てを回避することに関するものです。また、配置の削除はありません。
エミリーL.

@エミリーこれはOPの意味だと思います:void * someAddress = ...; delete (T*)someAddress
xryl669

1
スタックを使用しても、実行時間は確定的ではありません。あなたが電話しmlock()たり、似たようなものでない限り。これは、システムのメモリが不足している可能性があり、スタックで使用できる準備が整った物理メモリページがないため、OSが実行を続行する前に、キャッシュをスワップまたはディスクに書き込む(ダーティメモリをクリアする)必要がある場合があるためです。
ミッコランタライネン2018年

1
@mikkorantalainenは技術的には真実ですが、メモリが少ない状況では、ディスクにプッシュしているため、パフォーマンスにかかわらずすべてのベットがオフになっているため、何もできません。とにかくそれが妥当であるときに、新しい呼び出しを避けるためのアドバイスを無効にすることはありません。
エミリーL.

21

C ++ 17より前:

結果をスマートポインターでラップしても、微妙なリークが発生しやすいためです。

オブジェクトをスマートポインタでラップすることを覚えている「注意深い」ユーザーを考えてみましょう。

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

が存在しないため、このコードは危険であるという保証のいずれかということshared_ptrに構築されたのいずれかT1またはT2。したがって、一方が失敗した場合、new T1()またはnew T2()もう一方が成功した後に失敗した場合、最初のオブジェクトはリークさshared_ptrれ、破棄して割り当てを解除するオブジェクトが存在しないためです。

解決策:を使用しますmake_shared

C ++ 17以降:

これはもはや問題ではありません。C++ 17はこれらの操作の順序に制約を課します。この場合、への各呼び出しのnew()直後に、対応するスマートポインタの構築が続き、その間に他の操作はないようにします。これnew()は、2番目のオブジェクトが呼び出されるまでに、最初のオブジェクトがすでにスマートポインターでラップされていることが保証されているため、例外がスローされた場合のリークを防止します。

C ++ 17によって導入された新しい評価順序の詳細な説明は、別の回答でバリーによって提供されました

これはC ++ 17 でもまだ問題であることを指摘してくれた@Remy Lebeauに感謝します(それほどではありません)。コンストラクターは制御ブロックの割り当てとスローに失敗する可能性があり、その場合、渡されたポインターは削除されません。shared_ptr

解決策:を使用しますmake_shared


5
その他の解決策:1行に複数のオブジェクトを動的に割り当てないでください。
アンチモン2013

3
@Antimony:ええ、まだ何も割り当てていない場合に比べて、既に1つを割り当てている場合は、複数のオブジェクトを割り当てる方がはるかに魅力的です。
user541686 2013

1
より良い答えは、例外が呼び出されても何もキャッチされない場合、smart_ptrがリークすることだと思います。
ナタリーアダムス

2
C ++ 17以降のケースでも、new成功した場合はリークが発生し、その後のshared_ptr構築が失敗する可能性があります。std::make_shared()それも解決します
Remy Lebeau

1
@Mehrdad問題のshared_ptrコンストラクターは、共有ポインターと削除機能を格納する制御ブロックにメモリを割り当てます。そのため、理論的にはメモリエラーがスローされる可能性があります。コピー、移動、エイリアスのコンストラクタのみがスローされません。make_shared割り当て制御ブロック自体の内部の共有オブジェクトは、これだけ1割当の代わりに2がある
レミールボーは、

17

多くの場合、それは自分の弱点を一般的なルールにまで引き上げる誰かです。演算子を使用してオブジェクトを作成すること自体、何も問題はありませんnew。いくつかの議論があるのは、ある種の規律でそうしなければならないということです。オブジェクトを作成する場合、オブジェクトが確実に破棄されるようにする必要があります。

これを行う最も簡単な方法は、自動ストレージにオブジェクトを作成することです。そのため、C ++は、オブジェクトがスコープ外になったときにそれを破棄することを認識しています。

 {
    File foo = File("foo.dat");

    // do things

 }

ここで、エンドブレースの後にそのブロックから落ちるとfoo、スコープ外であることを確認してください。C ++は自動的にそのdtorを呼び出します。Javaとは異なり、GCがそれを見つけるのを待つ必要はありません。

あなたは書きましたか

 {
     File * foo = new File("foo.dat");

あなたはそれを明示的に一致させたいでしょう

     delete foo;
  }

またはさらに良い、あなたの割り当て File *「スマートポインター」として。気を付けないと漏れの原因になります。

答え自体は、使用しない場合newはヒープに割り当てないという誤った仮定をしています。実際、C ++ではそれがわかりません。ほとんどの場合、メモリの小さな量、たとえば1つのポインタが確実にスタックに割り当てられます。ただし、Fileの実装が次のようなものであるかどうかを検討してください。

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

その後、FileImpl、まだスタックに割り当てられます。

そして、はい、あなたは持っている方がいいでしょう

     ~File(){ delete fd ; }

クラスでも。これがないと、ヒープ割り当てをまったく行わなかったとしても、ヒープからメモリがリークします。


4
参照されている質問のコードを見てください。そのコードには間違いなく多くの問題があります。
アンドレ・キャノン

7
new それ自体を使用することに何の問題もないことに同意しますが、コメントが参照された元のコードを見ると、new乱用されています。コードはJavaまたはC#のように記述newされ、スタック上にあることがより理にかなっている場合、事実上すべての変数に使用されます。
luke

5
フェアポイント。ただし、一般的な規則は通常、一般的な落とし穴を回避するために適用されます。これが個人の弱点であるかどうかにかかわらず、メモリ管理はこのような一般的なルールを正当化するのに十分複雑です!:)
Robben_Ford_Fan_boy

9
@Charlie:コメントには、絶対に使用してはならないということはありませんnew。ダイナミックアロケーションと自動ストレージのどちらかを選択できる場合、自動ストレージを使用すると書かれています。
アンドレ・キャノン

8
@Charlie:を使用しても何も問題はありませんnewが、を使用した場合delete、それは間違っています。
Matthieu M.11年

16

new()使用すべきではない少し可能な限り。できるだけ慎重に使用する必要があります。そして、それは実用主義によって要求されるのと同じくらい必要な頻度で使われるべきです。

暗黙的な破棄に依存するスタック上のオブジェクトの割り当ては、単純なモデルです。オブジェクトの必要なスコープがそのモデルに適合する場合、NULLポインターのnew()関連付けられたdelete()チェックを使用して、を使用する必要はありません。スタックに短期間のオブジェクトがたくさんある場合は、ヒープの断片化の問題を減らす必要があります。

ただし、オブジェクトの有効期間が現在のスコープを超えて拡張する必要new()がある場合は、正しい答えです。いつ、どのように呼び出すか、およびdelete()NULLポインターの可能性に注意を払い、削除されたオブジェクトと、ポインターの使用に伴うその他すべての問題を使用していることを確認してください。


9
「オブジェクトの存続期間が現在のスコープを超えて拡張する必要がある場合、new()が正しい答えです」...値で優先的に返すか、非const参照またはポインタで呼び出し側スコープの変数を受け入れないのはなぜですか?
Tony Delroy、2011年

2
@トニー:はい、はい!誰かが言及を主張するのを聞いてうれしいです。これらはこの問題を防ぐために作成されました。
Nathan Osman

@TonyD ...またはそれらを組み合わせる:スマートポインタを値で返します。このようにして、呼び出し元、および多くの場合(つまり、make_shared/_unique使用可能な場所)で、呼び出し先は、newまたはを行う必要がありませんdelete。この答えは本当のポイントを逃しています:(A)C ++はRVO、移動セマンティクス、出力パラメーターのようなものを提供します-これはしばしば動的に割り当てられたメモリを返すことによるオブジェクト作成と寿命延長の処理が不必要で不注意になることを意味します。(B)動的割り当てが必要な状況でも、stdlibはユーザーの醜い内部の詳細を軽減するRAIIラッパーを提供します。
underscore_d

14

newを使用すると、オブジェクトがヒープに割り当てられます。通常、拡張が予想される場合に使用されます。次のようなオブジェクトを宣言すると、

Class var;

スタックに配置されます。

ヒープに配置したオブジェクトに対して、常にnewでdestroyを呼び出す必要があります。これにより、メモリリークが発生する可能性があります。スタックに配置されたオブジェクトは、メモリリークの傾向がありません!


2
+1「[ヒープ]は通常、拡張が予想されるときに使用されます」- std::stringまたはへの追加などstd::map、鋭い洞察力。私の最初の反応は「オブジェクトのライフタイムを作成中のコードのスコープから切り離すことも非常に一般的」でしたが、実際には値で返すか、非const参照またはポインターによって呼び出し元スコープの値を受け入れる方が、「拡張」が関係する場合を除き、その方が適していますあまりにも。ファクトリーメソッドのような他のサウンドの使い方もあります...
トニー・デルロイ

12

ヒープの過剰使用を回避する1つの注目すべき理由は、パフォーマンスです。特に、C ++で使用されるデフォルトのメモリ管理メカニズムのパフォーマンスに関係しています。割り当ては非常に簡単な場合もnewありdeleteますが、厳密な順序なしでサイズが不均一なオブジェクトを多数実行すると、メモリの断片化が発生するだけでなく、割り当てアルゴリズムが複雑になり、特定の場合にパフォーマンスが完全に失われる可能性があります。

これは、解決のために作成されたメモリプールの問題であり、必要に応じてヒープを使用しながら、従来のヒープ実装の固有の欠点を軽減することができます。

ただし、問題を完全に回避するためには、なお良いでしょう。スタックに配置できる場合は、それを実行してください。


常にかなり大量のメモリを割り当て、速度が問題になる場合は、配置new / deleteを使用できます。
rxantos 2015

メモリプールは、断片化を回避し、割り当て解除(数千のオブジェクトに対して1つの割り当て解除)を高速化し、割り当て解除をより安全にするためのものです。
Lothar

10

ポスターはYou do not have to allocate everything on theheapでなく、言うことを意図したものだと思いますstack

基本的にオブジェクトはスタックに割り当てられます(もちろん、オブジェクトサイズが許す場合)。ヒープ割り当てベースの割り当てではなく、スタック割り当てのコストが安いため、アロケータによるかなりの作業が伴い、冗長性が追加されます。ヒープに割り当てられたデータを管理します。


10

私は新しい「多すぎる」を使用するという考えに反対する傾向があります。元の投稿者がシステムクラスでnewを使用するのは少しばかげています。(int *i; i = new int[9999];?本当に?int i[9999];は、はるかに明確です。)私は、それがコメンターのヤギを獲得したものだと思います。

システムオブジェクトを使用している場合、まったく同じオブジェクトへの複数の参照が必要になることは非常にまれです。値が同じである限り、それだけが重要です。また、システムオブジェクトは通常、メモリ内で多くの領域を占有しません。(文字列内の文字ごとに1バイト)。そして、もしそうなら、ライブラリはそのメモリ管理を考慮に入れるように設計されるべきです(それらが上手く書かれていれば)。これらの場合(コード内の1つまたは2つのニュースを除くすべて)、newは実質的に無意味であり、バグの混乱と潜在的な可能性をもたらすだけです。

ただし、独自のクラス/オブジェクト(たとえば、元の投稿者のLineクラス)で作業している場合は、メモリフットプリント、データの永続性などの問題について自分で考え始める必要があります。この時点で、同じ値への複数の参照を許可することは非常に重要です。リンクされたリスト、ディクショナリ、グラフなどの構造が可能になります。複数の変数が同じ値を持つだけでなく、メモリ内のまったく同じオブジェクトを参照する必要があります。ただし、Lineクラスにはこれらの要件はありません。そのため、元の投稿者のコードには、実際にはは必要ありませんnew


通常、配列のサイズを事前に知らない場合は、新規/削除が使用されます。もちろんstd :: vectorはあなたのために新しい/削除を隠します。引き続き使用しますが、std :: vectorを使用します。したがって、今日では、配列のサイズがわからず、何らかの理由でstd :: vectorのオーバーヘッドを避けたい場合に使用されます(これは小さいですが、まだ存在します)。
rxantos 2015

When you're working with your own classes/objects...あなたはしばしばそうする理由がありません!Qのごく一部は、熟練したプログラマーによるコンテナー設計の詳細にあります。全く対照的に、憂鬱な割合、stdlibが存在することを知らない初心者の混乱に関するものです。ホイールとは何か、そしてそれが機能する理由を学びました。より抽象的な割り当てを促進することにより、C ++はCの無限の「リンクされたリストを使用したsegfault」から私たちを救うことができます。それをしましょ
underscore_d 2016

システムクラスで新しいのオリジナルポスターの使用は少しばかげている。( int *i; i = new int[9999];?本当に?int i[9999];より明確である。)」はい、それは明確ですが、プレイ悪魔の提唱者に、タイプは必ずしも悪い引数ではありません。9999要素を使用すると、9999要素に十分なスタックがないタイトな組み込みシステムを想像できます。9999x4バイトは〜40 kB、x8〜80 kBです。したがって、このようなシステムは、代替メモリを使用して実装する場合、動的割り当てを使用する必要がある場合があります。それでも、それはおそらくダイナミックアロケーションを正当化するだけでnew、a vectorはその場合の実際の修正です
underscore_d

@underscore_dに同意します。これはそれほど良い例ではありません。そのようにスタックに40,000または80,000バイトを追加することはありません。私はおそらく実際にそれらをヒープに割り当てます(std::make_unique<int[]>()もちろん)。
einpoklum

3

2つの理由:

  1. この場合は不要です。あなたはあなたのコードを不必要により複雑にしています。
  2. ヒープに領域を割り当てます。これは、delete後で記憶する必要があることを意味します。そうしないと、メモリリークが発生します。

2

new新しいgotoです。

なぜgotoこれほどひどいのかを思い出してください。フロー制御のための強力で低レベルのツールですが、不必要に複雑な方法で使用され、コードの追跡が困難になることがよくありました。さらに、パターンを読み取るのに最も便利で最も簡単なものは、構造化プログラミングステートメント(forまたはwhile)にエンコードされています。最終的な効果はgoto、適切な方法がどこにあるかというコードがかなりまれであるということです。もしあなたがを書きたくなった場合goto、あなたはおそらく悪いことをしています(本当に自分が何をしているかを理解していない限り)。

new似ています—不必要に複雑で読みにくいものにするためによく使用され、エンコードできる最も有用な使用パターンはさまざまなクラスにエンコードされています。さらに、標準クラスがまだない新しい使用パターンを使用する必要がある場合は、それらをエンコードする独自のクラスを作成できます。

私もそれが主張newされるより悪いよりもgotoペアにする必要が原因、newdelete声明。

のようにgoto、を使用する必要newがあると思われる場合は、おそらく悪いことをしています。特に、必要な動的割り当てをカプセル化することが目的であるクラスの実装の外部でそうしている場合はなおさらです。


そして私は付け加えます:「あなたは基本的にそれを必要としません」。
einpoklum

1

主な理由は、ヒープ上のオブジェクトは、単純な値よりも常に使用および管理が難しいためです。読みやすく保守しやすいコードを書くことは、真面目なプログラマーにとって常に最優先事項です。

別のシナリオは、使用しているライブラリが値のセマンティクスを提供し、動的割り当てを不要にすることです。Std::string良い例です。

ただし、オブジェクト指向コードの場合、ポインタを使用newすること(つまり、事前に作成するために使用すること)は必須です。リソース管理の複雑さを単純化するために、スマートポインタなど、それをできるだけ単純にするための数十のツールを用意しています。オブジェクトベースのパラダイムまたはジェネリックパラダイムは、値のセマンティクスを想定してnewおり、他のポスターで述べられているように、必要な数は少ないか、まったくありません。

従来のデザインパターン、特にGoF本で言及さnewれているものは、典型的なOOコードであるため、多く使用されます。


4
これはひどい答えです。For object oriented code, using a pointer [...] is a mustナンセンス。あなたは小さなサブセットのみを参照することにより、「OO」を切り下げている場合は、 ポリモーフィズム - ナンセンス:参照は、あまりにも動作します。[pointer] means use new to create it beforehand特にナンセンス:参照またはポインタを自動的に割り当てられたオブジェクトに取って多態的に使用できます。私を見てください[typical OO code] use new a lot:おそらく古い本にあるかもしれませんが、誰が気にしますか?漠然とした最近のC ++ new可能な場合はいつでも/ rawポインタを避けます- そうすることで、決してOOでありません
underscore_d

1

上記のすべての正解に対するもう1つのポイントは、どのようなプログラミングを行っているかによって異なります。たとえば、カーネルがWindowsで開発されている->スタックは厳しく制限されており、ユーザーモードのようにページフォールトを実行できない場合があります。

このような環境では、新しい、またはCのようなAPI呼び出しが優先され、必要になることさえあります。

もちろん、これはルールの例外にすぎません。


-6

newオブジェクトをヒープに割り当てます。それ以外の場合、オブジェクトはスタックに割り当てられます。2つの違いを調べてください。

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