[解決済み】C++のRAIIとスマートポインタ
質問
C++の実務では RAII とは何ですか? スマートポインタ また、RAIIとスマートポインタの組み合わせの利点は何でしょうか。
どのように解決するのですか?
RAIIの簡単な(そしておそらく使い古された)例として、Fileクラスがあります。RAIIがなければ、コードは次のようになります。
File file("/path/to/file");
// Do stuff with file
file.close();
言い換えれば、ファイルを使い終わったら、必ずファイルを閉じなければならない。第一に、Fileを使うときはどこでもFile::close()を呼ばなければならない。これを忘れると、必要以上に長くファイルを保持することになる。2つ目の問題は、ファイルを閉じる前に例外が発生した場合、どうするかということです。
Javaはfinally節を使うことで2番目の問題を解決している。
try {
File file = new File("/path/to/file");
// Do stuff with file
} finally {
file.close();
}
またはJava 7以降ではtry-with-resourceステートメントです。
try (File file = new File("/path/to/file")) {
// Do stuff with file
}
C++では、RAIIを使って両方の問題を解決しています。つまり、Fileのデストラクタでファイルを閉じるということです。つまり、Fileのデストラクタでファイルを閉じるということです。Fileオブジェクトが適切なタイミングで破壊される限り(いずれにせよそうあるべきですが)、ファイルを閉じることは私たちのために行われます。というわけで、このコードは次のようになります。
File file("/path/to/file");
// Do stuff with file
// No need to close it - destructor will do that for us
Javaではオブジェクトがいつ破壊されるかの保証がないため、ファイルなどのリソースがいつ解放されるかの保証ができないため、このようなことはできません。
スマートポインターについてですが、多くの場合、スタック上にオブジェクトを作成するだけです。例えば、(他の回答から例を盗んで)。
void foo() {
std::string str;
// Do cool things to or using str
}
これはうまくいくのですが、strを返したい場合はどうしたらいいでしょうか?こう書けばいいんです。
std::string foo() {
std::string str;
// Do cool things to or using str
return str;
}
では、何が問題なのか?戻り値の型がstd::stringなので、値で返していることになります。つまり、strをコピーして、実際にそのコピーを返すということです。これはコストがかかるので、コピーにかかるコストは避けたいと思うかもしれない。そこで、参照やポインタで返すことを思いつくかもしれない。
std::string* foo() {
std::string str;
// Do cool things to or using str
return &str;
}
残念ながら、このコードはうまくいかない。strへのポインタを返していますが、strはスタック上に生成されたので、foo()を終了すると削除されてしまいます。つまり、呼び出し側がそのポインタを取得した時点で、そのポインタは役に立たなくなってしまうのです(それを使うと、いろいろとおかしなエラーが発生する可能性があるので、役に立たないよりも悪いと言えるかもしれません)。
では、どうすればいいのか?そうすれば、foo()が完了しても、strは破壊されない。
std::string* foo() {
std::string* str = new std::string();
// Do cool things to or using str
return str;
}
もちろん、この解決策も完璧ではありません。strを作ったはいいが、それを削除することはないからだ。非常に小さなプログラムでは問題ないかもしれませんが、一般的には確実に削除したいものです。呼び出し元がそのオブジェクトを使い終わったら削除しなければならないと言えばいいのです。欠点は、呼び出し側がメモリを管理しなければならないことで、余計に複雑になり、メモリリークにつながる可能性があります。
そこで、スマートポインタの出番です。以下の例ではshared_ptrを使用しています。実際に使用する場合は、さまざまなタイプのスマートポインタを調べてみることをお勧めします。
shared_ptr<std::string> foo() {
shared_ptr<std::string> str = new std::string();
// Do cool things to or using str
return str;
}
さて、shared_ptrはstrへの参照の数を数えます。例えば
shared_ptr<std::string> str = foo();
shared_ptr<std::string> str2 = str;
これで、同じ文字列への参照が2つになりました。strへの参照がなくなると、それは削除されます。そのため、もはや自分で削除する心配はありません。
クイック編集:いくつかのコメントで指摘されているように、この例は(少なくとも!)2つの理由で完璧ではありません。まず、文字列の実装上、文字列のコピーは安価になる傾向があります。第二に、名前付き戻り値の最適化と呼ばれるものによって、コンパイラが高速化するための工夫をすることができるため、値による戻りは高価ではない可能性があります。
では、Fileクラスを使って別の例をしてみましょう。
例えば、あるファイルをログとして使いたいとします。つまり、ファイルを追記モードのみで開きたい。
File file("/path/to/file", File::append);
// The exact semantics of this aren't really important,
// just that we've got a file to be used as a log
さて、このファイルを他のいくつかのオブジェクトのログとして設定してみましょう。
void setLog(const Foo & foo, const Bar & bar) {
File file("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
このメソッドが終了すると同時に、file は閉じられ、foo と bar は無効なログファイルを持っていることになります。ヒープ上に file を構築し、foo と bar の両方に file へのポインタを渡せばよいのです。
void setLog(const Foo & foo, const Bar & bar) {
File* file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
しかし、それでは、誰がファイルを削除する責任を負うのでしょうか?もしどちらもファイルを削除しないのであれば、メモリリークとリソースリークの両方が発生することになります。fooとbarのどちらが先にファイルを使い終わるかは分からないので、どちらかが自分自身でファイルを削除することを期待することはできません。例えば、barがファイルを処理し終わる前にfooがファイルを削除した場合、barは無効なポインタを持つことになります。
そこで、お察しの通り、スマートポインタを使えばいいんです。
void setLog(const Foo & foo, const Bar & bar) {
shared_ptr<File> file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
foo と bar の両方が終了し、file への参照がなくなれば(おそらく foo と bar が破壊されたため)、file は自動的に削除されます。
関連
-
[解決済み】Visual Studio 2015で「非標準の構文。'&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】VC++の致命的なエラーLNK1168:書き込みのためにfilename.exeを開くことができません。
-
[解決済み] static_cast, dynamic_cast, const_cast, reinterpret_cast はいつ使うべきですか?
-
[解決済み] ルール・オブ・スリーとは?
-
[解決済み] コピーアンドスワップ慣用句とは?
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] スマートポインターとは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】構造体のベクター初期化について
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み] error: 'ostream' does not name a type.
-
[解決済み] string does not name a type Errorが発生するのはなぜですか?
-
[解決済み】C++の変数はイニシャライザーを持っているが、不完全な型?
-
[解決済み】C++プログラムでのコンソールの一時停止
-
[解決済み】「std::operator」で「operator<<」にマッチするものがない。
-
[解決済み】リンカーエラーです。"リンカ入力ファイルはリンクが行われていないため未使用"、そのファイル内の関数への未定義参照
-
[解決済み】VC++の致命的なエラーLNK1168:書き込みのためにfilename.exeを開くことができません。
-
[解決済み】演算子のオーバーロード C++; <<操作のパラメータが多すぎる