JAVA開発経験有のC++初心者です。
C++で仮にTestクラスのインスタンスを作成しメソッドを実行する場合下記の方法があるかと思います。
① Test inst;
inst.method();
② Test *inst = new Test();
inst -> method();
この違い、使い分けがよくわかっておりません。
②の方法はdeleteでメモリ解放をしなければならないため、
極力①を使った方がいいのではないかとも考えてしまうのですが・・。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答7件
0
この違い、使い分けがよくわかっておりません。
Chironian さんの説明にある通り、①の方法で定義した変数はブロックから抜けると解放されるため、残しておきたい場合は②の方法でヒープ上にインスタンスを確保します。
それ以外にも、①の方法ではスタック領域にインスタンスが確保されるため、あまり大きなサイズを扱えません。というのも、スタック上に大きな領域を確保しようとすると、場合によっては「スタックオーバーフロー」が発生する危険があるからです。そのため、インスタンスサイズが大きいクラスは、たとえ関数の中でしか使わなくても②の方法でインスタンスを確保することがあります。
ご質問でも指摘されているように②の方法では必ずdeleteする必要がありますが、C++では「スマートポインタ」という仕組みが一般的に使われており、deleteし忘れを防止することができます。
JavaやC#ではGC(ガベージコレクション)の仕組みによりnewしてもdeleteする必要はありません。C++もスマートポインタを使いこなすことで、Javaのようにdeleteせずとも自動でインスタンスの解放を制御できるようになります。
投稿2016/04/02 13:45
編集2016/04/03 22:01総合スコア5938
0
こんにちは。
②の方法はdeleteでメモリ解放をしなければならないため、
極力①を使った方がいいのではないかとも考えてしまうのですが・・。
はい、その通りです。
つまり、②を使う時があるのか?という疑問ですね。
①を使った場合、instの寿命は、instを宣言したブロック({}
で囲まれた範囲)の中だけです。
ブロックが終了するとinstは強制的に開放されてしまいます。
例えば、関数の中で宣言した場合、関数からreturnすると開放されます。
それでは困る場合などに②を使います。
しかし、C++11/14で、ムーブ・セマンティクスやスマート・ポインタが強化されたため、どうしても②を直接使わなければ行けないケースは、実はもうないのではないか?とも感じています。(検証したわけではないので直感的な印象ですけど。)
【追記】
yohhoyさんのコメントを受けて本文を少し修正してます。
元は「②の必要はほぼない」と取れるニュアンスで書いてましたが、「②を直接使う必要がほぼない」へ変更しました。
②の考え方を理解する必要は今もこれからもあります。
投稿2016/04/02 13:07
編集2016/04/05 09:00総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/05 08:45
0
たとえばこんなことやりたいときにポインタ使います。
C++
1Test* inst; 2if ( ファイルに出力 ) { 3 inst = new FileTest(); 4} else { 5 inst = new ConsoleTest(); 6} 7inst->method(); 8delete inst;
投稿2016/04/05 01:57
総合スコア16614
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/05 08:13
0
まずC++11以降を利用する前提で話を勧めます。それ以前のC++を何らかの理由で強制される場合はtetolaさんやepistemeさんの回答を参照してください。
まずそのクラスの大きさはどの程度か、という問題があります。std::string
やstd::vector
がいい例ですが、内部で動的確保をするなどしてクラス側ではポインタを持つような場合、クラスの大きさは一般に小さくなります。
というかクラスが巨大になるのって配列を持った構造体じゃないとまあ見ない気がする。
で、こういう小さなオブジェクトを動的確保すると、(処理系次第ですが)一般に速度が遅くなります。これは速度を追求するC++の精神に合致しません。したがってC++ではJavaやC#と違い、newする(スマートポインタの利用含む)ことはあまりありません。
クラスを関数から返すような場合でもコンパイラのNRVO/RVOという最適化が行われるのでnewする(スマートポインタの利用含む)メリットは皆無で、またその戻り値を一時変数に受ける場合もmoveセマンティックという、rvalue-referenceをある種のフラグとして使う技法により、deep copyが行われることがないコードが書けます。
C言語では動的配列を実現するために動的確保を多用しますが、C++においてはstd::vectorを使えばいいだけなので、この目的でnewないしスマートポインタを使うことはありません。
ではいつnewする(スマートポインタの利用含む)のかというと、例えば非同期処理をする時です。
【boost::asio buffers】 boost::asioでセッション管理にはshared_ptrが便利だ
非同期処理の場合、メモリーが意図せず開放されるのを避けるためにstd::shared_ptrが重宝されるようです。
なお余談ですが、std::vectorも要素数が小さいとやっぱり遅いので、stackメモリー制限が厳しくない環境では、要素数が十分小さく(800byte以下程度)要素数の上限がわかっている場合はstd::arrayを使うという高速化技法が存在します。
https://github.com/YSRKEN/KanColleSimulator_KAI/issues/64
とにかく動的確保はそれ自体重いので、stack overflowにならない範囲で動的確保を避けるのが、高速なプログラムが求められるC++では重要になります。なおstaticにデータを置くのは目に見えないデータ依存を作りやすくマルチスレッドするときに困るのでstaticの濫用は禁物です。
なお書いたプログラムがstack overflowになるかどうかは、コード解析ツールなどを使えば割りと簡単にわかるので、stackにデータを置くことをこわがらないでください。(Boostとか使ってるとたまにむちゃくちゃスタックを消費することがある)
ところでVSよ、なんでお前のstd::vectorはそんなにもメモリー管理が下手くそなんだ。事実上push_backするたびにメモリー再確保とデータコピーが発生するぞ・・・。毎回capacity管理をしろとでも?
投稿2016/04/12 00:02
総合スコア5850
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
C++でオブジェクトを作る場合は、次の4つが基本になるかと思っています。(Testクラスのオブジェクトを引数1を指定して作る場合です)
- (直接)データとして作る
Test test(1);
- ポインタとして作る
Test *test = new Test(1);
- unique_ptrとして作る
std::unique_ptr<Test> test = std::make_unique<Test>(1);
- shared_ptrとして作る
std::shared_ptr<Test> test = std::make_shared<Test>(1);
作り方 | データの場所 | データの寿命 | 寿命延長方法 |
---|---|---|---|
データ | スタック | 変数と同じ | 右辺値参照として返す |
ポインタ | ヒープ | deleteされるまで | deleteされない限り永遠 |
unique_ptr | ヒープ | 変数と同じ | 他のunique_ptrにムーブする |
shared_ptr | ヒープ | 変数と同じ | 他のshared_ptrにコピーする |
通常のローカル変数はブロックがつきると寿命が尽きます。メンバー変数であればオブジェクトのデストラクタが呼ばれるまでが寿命です。グローバル変数やstaticローカル変数は永遠です。
まず、データもunique_ptrもshared_ptrもそのままでは変数が消えるタイミングで消えます。unique_ptrはポインタというよりもデータに近い動作をします。違いは、
- 場所がスタックでは無くヒープになる。(どんなに大きなデータでもスタックオーバフローが発生する恐れが無いが、メモリ確保の処理がスタックより遅い場合があるため、利点でもあり欠点でもある)
- ムーブコンストラクタが無いクラスのオブジェクトでも右辺値参照として返したり、他のunique_ptrにムーブすることができる。(そのかわりコピーはできない)
- オブジェクトに関係なく、(ポインタの値を移動するだけなので)ムーブが極めて速い。
ということです。つまり、unique_ptrはポインタの利点のいくつかを追加してデータのように扱う方法と言った方が良いと思います。
shared_ptrはポインタとして作った時と似たようなことができます。変数と一緒に消されたくなければどこかにコピーしておけば、そのどこかが消えて無くならない限り、生き続けます。かといってそのどこかをきちんと管理しなければ、結局deleteし忘れと同じ結果を招くことでしょう。unique_ptrとの違いは、コピーにより複数の場所で同時に所有できると言うことです。※shared_ptrはコピーだけでなくムーブもできます。その場合は、unique_ptrのように寿命はムーブ先次第となります。
なお、例外等でdeleteし忘れによるメモリリークがあるからポインタはダメであると言うことではありません。例外が発生しうるのであれば、catchやfinallyでdeleteする処理を入れればいいだけです。それに、shared_ptrを使って、他の場所にもコピーを作った後に例外が発生した場合も、結局同じ事になります。
shared_ptrはGCの代わりになりません。weak_ptrでどれだけ工夫を凝らしたとしても循環参照を確実に回収できる保証があるわけではありません。これらはメモリ管理の煩わしさをある程度緩和するだけであって、GC並に何も考えなくても良いというわけでは無い事にもっと注意を払うべきです。
最後に、shared_ptrではなくポインタとして作る利点ですが、削除のタイミングを完全に把握できると言うことです。逆に言えば、きちんと把握していないとメモリリークや解放後にアクセスするなどのバグの元になります。それが良いのか悪いのかはケースバイケースだと思います。
C++は、GCという余計な処理が無い分、高速に動作することができますが、その代わり何でもコレ一つあれば大丈夫というものがありません。コードの内容から、その役割にあったオブジェクト生成方法を選ぶ必要があります。
投稿2016/04/11 12:50
総合スコア21735
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
①の場合、グローバル変数(クラスや関数の一番外にある変数)にしない限り、
関数などの { } を抜けた時点で解放されます。
グローバルの場合は途中で解放する事はできません。
(プログラム起動時に全てインスタンス化され、終わるまで解放されない)
②の場合、適切なタイミングで自前でdeleteするか、
なんらかの参照カウンターを用いて解放手段を用意する必要があります。
大量にメモリーを使う場合は、②を使わないと厳しくなります。
全部オンメモリで問題ない程度なら①をグローバルに持てば何も考える必要がありません。
なお、JavaやC#でのクラスはデフォルトで参照カウンターを用いていてますが、
C++はデフォルトに参照カウンターはなく、基本はコピーです。
例えば下記の場合、
myclass a;
myclass b;
a = b; aに bのクラス(sizeof(myclass)分)が丸ごとコピーされます(参照ではない)
myclass *a = new myclass(); (メモリ0x1000にmyclassが確保されたとする a=0x1000)
myclass *b = new myclass(); (メモリ0x2000にmyclassが確保されたとする b=0x2000)
a=b a=0x2000 になります。 (※アドレスコピーしているだけです。元々のa のクラスは解放されません)
なおC++ をやるなら、嫌でもメモリー管理が必要になりますので、
②になれて正しいメモリー解放手順等を理解された方が良いとは思います。
C++11で参照に関する強化は確かに行われているのですが、
実際の現場では、元々のコピーの概念を使用する機会の方が
まだ圧倒的に多いとは思います。
投稿2016/04/03 13:06
編集2016/04/03 13:50総合スコア23
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/05 07:29
2016/04/05 12:19 編集
2016/04/06 04:58
2016/04/06 10:38 編集
2016/04/11 03:23
2016/04/11 10:49
2016/04/11 23:25 編集
2016/04/12 03:50 編集
2016/04/13 10:28
2016/04/13 16:02 編集
0
②の方法はdeleteでメモリ解放をしなければならないため、
極力①を使った方がいいのではないかとも考えてしまうのですが・・。
私もそう思います。
ただ、どうしても②を使わざるを得ない局面もあります。
単純に思いつくのは巨大な配列や構造体を使う場合です。
①と②では変数を確保するメモリ領域が違います。①はスタック、②はヒープに確保します。確保できるメモリ容量はスタックに比べてヒープの方が多いので(というかスタックはレジスタの退避にも使われるので大量のメモリ確保をすべきではない)、画像の読み込みなどに使用するメモリはヒープから確保するようになっている場合が多いと思います。
投稿2016/04/02 13:55
総合スコア3041
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/03 22:02