読者です 読者をやめる 読者になる 読者になる

Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

C++でstd::stringをどう返すべきか Part1

スポンサードリンク

C++では関数からstd::stringを皆はどうやって返してんだろうっていう疑問がわきました。
フツーに考えてstd::stringをそのまま値で返す一択なのですが、
そこで問題になるのがコピーコスト……
っていうかそもそもコピーしてるの?とかいろいろ疑問が沸くわけです。
C++は個人的に素晴らしい言語だと思いますが、
こんな基礎的なことでもある程度知識がなければ使えない地雷だらけのク○言語
という側面も持ち合わせています。
Gistでいくつか例を書きました。
test1,2は論外、test3,4は普通のやり方、test5,6は参照を渡しています。
なお、動作確認環境はg++ 4.8.3です。

ソースコード


実行結果

テスト3
mystring constructor mystring() called!
function test3 called!!
mystring constructor mystring(const char *str) called!
mystring assignment operator mystring &operator=(const mystring& rhs) called!
[test]
テスト4
mystring constructor mystring() called!
function test4 called!!
mystring constructor mystring(const char *str) called!
mystring assignment operator mystring &operator=(const mystring& rhs) called!
[test]
テスト3a
function test3 called!!
mystring constructor mystring(const char *str) called!
[test]
テスト4a
function test4 called!!
mystring constructor mystring(const char *str) called!
[test]
テスト5
mystring constructor mystring() called!
mystring assignment operator mystring &operator=(const char *str) called!
mystring assignment operator mystring &operator=(const mystring& rhs) called!
[test]
テスト6
mystring constructor mystring() called!
mystring assignment operator mystring &operator=(const char *str) called!
[test]

test1()について

test1は初学者がやりがちなミスです。
ローカルで確保した変数を参照で返すというものです。
今だいたいメジャーな言語(CやC++除く)では基本的にヒープ領域に確保して、そうやって取った領域はスコープを抜けても生きていて、その生存期間はGCが判断していつの間にやら回収してくれるまるで魔法のような仕組みになっている……のでやりがちです。
というか……うん、他人ので結構見ました(;´・ω・)
C++でJavaのようなnewで産みっぱなしのコードを書いたら悲劇ですね。メモリリークしまくり…‥。まぁ、今のC++は極力new/deleteしないようにするのが正解っぽいですが。
スマートポインタとかもあるし。

test2()について

Javaじゃねんだぞゴルァということで割愛。
いや、いいんだけどさ、ダメだって……だれが消すの?

test3()について

まぁ普通ですよね。これがスタンダードなやり方だと思います。
ただ、コピーコストがあるのか?とか気になります。
ちなみに3aは宣言時に代入したら無駄が省けるんじゃない?
と思って書いたコードです。

test4()について

test3となにか差が出ると期待しましたが、特に差はなく。
test3と違うのは後述するRVOというのが絡んでくるのかなと妄想していましたが……。
ちなみに4aも先ほどと同様に、宣言時に代入したら無駄が省けるんじゃない?
と思って書いたコードです。

test5()について

参照渡ししているパターンです。もはやreturnと関係がなくなってきた…。

test6()について

なんかstringとかの中身を変えたいならこれでいい気が指摘ました。
訂正します。test3a, test4aがいいようです。

補足と参考サイト

std::stringの宣言時の初期化の書き方について

今回とは直接関係ないですが、にまとまっていました。
std::string s = "test";が普通でナウい書き方だと
std::string s {"test"};です。

RVOについて

1.呼び出し側では f を格納する場所を確保 (確保するだけ、コンストラクタは呼ばない)
2.そのアドレスを関数 foo に渡す
3.関数のほうでは、戻値用のオブジェクトのアドレスが渡ってきた場合 (かつ、それが NULL でない場合) は、 新たに一時変数を生成したりはせずに、 そのアドレスから始まる領域に対してコンストラクタを起動する

http://www.fides.dti.ne.jp/~oka-t/cpplab-retval-ctor.html

その他雑記・感想

main関数の最後の2つの例間違えたtest3とtest4の例はRVO(Retrun Value Optimization)っていうのが効いてるのかな???
という結果になりました。検証の仕方が間違っている???
returnしたときにコピーコンストラクタが動くものだと思っていましたが…。
とちょっともやもやした結果になりました。string以外で試してみれば結果がよくわかるかな?
今回の結果だけでいうととにかく戻り値は宣言と同時に受け取るのがいいようです。
……どうでもよいですが、演算子のオーバーロードの書き方を毎度忘れてしまうのは僕だけですか(;´・ω・)修業が足りないのでしょうか……
あんまりよくわかっていないですが、
C++11で出てきた右辺値参照とかムーブセマンティクスとか
このあたりに絡んでくるのかなという気がしています。
いや、このケースではあまり有効活用できそうにないかな……。
何か気づいたら追記したいと思います。
To be Continued ... (Part2に続く予定)

id:yohhoyさんにありがたい情報をいただいてPart2を書きます。
Part2でどうすればいいか結論が出ました。