Stack Overflowの質問に偶然出会ったstd::list<std::string> を使用すると std::string でメモリ リークが発生する、 そしてコメントの一つこう言っています。
多用するのはやめてください
new
。どこにも new を使用した理由が見当たりません。C ++では値によってオブジェクトを作成できます。これは、この言語を使用する大きな利点の 1 つです。すべてをヒープ上に割り当てる必要はありません。Java プログラマーのように考えるのはやめてください。
彼が何を意味しているのかよく分かりません。
C++では、オブジェクトをできるだけ頻繁に値で作成する必要があるのはなぜですか。また、内部的にはどのような違いがあるのでしょうか。回答を誤解しましたか。
ベストアンサー1
広く使用されているメモリ割り当て手法には、自動割り当てと動的割り当ての 2 つがあります。通常、それぞれに対応するメモリ領域 (スタックとヒープ) があります。
スタック
スタックは常にメモリを順番に割り当てます。これは、メモリを逆の順序 (先入れ後出し: FILO) で解放する必要があるためです。これは、多くのプログラミング言語でローカル変数に使用されているメモリ割り当て手法です。最小限の記録しか必要とせず、次に割り当てるアドレスが暗黙的であるため、非常に高速です。
C++ では、スコープの終了時にストレージが自動的に要求されるため、これを自動ストレージと呼びます。現在のコード ブロック ( で区切られる) の実行が完了するとすぐに、そのブロック内のすべての変数のメモリが自動的に収集されます。これは、リソースをクリーンアップするためにデストラクタ{}
が呼び出される瞬間でもあります。
ヒープ
ヒープにより、より柔軟なメモリ割り当てモードが可能になります。記録はより複雑になり、割り当ては遅くなります。暗黙的な解放ポイントがないため、delete
またはdelete[]
( free
C の場合) を使用して、メモリを手動で解放する必要があります。ただし、暗黙的な解放ポイントがないことが、ヒープが柔軟である理由です。
ダイナミックアロケーションを使用する理由
ヒープを使用すると速度が遅くなり、メモリ リークやメモリの断片化につながる可能性がありますが、制限が少ないため、動的割り当てを使用するのに十分適したユースケースがあります。
動的割り当てを使用する主な理由は 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;
}
std::string
4 つのインスタンス、4 つのLine
インスタンス、1 つのTable
インスタンスとすべての文字列の内容を割り当て、すべてが自動的に解放されます。