文字列比較でハマったので紹介

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

文字列比較でハマったので紹介

投稿記事 by MoNoQLoREATOR » 12年前

え~っと、知ってる人が沢山いそうな気はするのですが、一応書いておきます。
この記事によってできるだけ沢山の初心者プログラマーが救われますように。

私は今日、大雑把に言うとこんなコードを書きました。

CODE:

const char * str1 = "test";
std::string str2 = "test";
if(str1 == str2.c_str() ) printf("Coincide!!");
パッと見、Coincide!!が表示されるコードに見えますよね。
ええ、私もそれを期待して書きました。
しかし、このコードでは期待通りの結果になりません。
ここで、if文内の右辺と左辺をひっくり返してみましょう。

CODE:

if(str2 == str1) printf("Coincide!!");
するとなんと、今度は期待通りの結果になってくれるではありませんか!

…まあ、よく考えれば当たり前ですよね。(よく考えなくても当たり前ですが)
前者のコードはconst char型のポインタ同士を比較しているのに対して、後者のコードはstd::string::operator==の結果を受け取っているわけですね。
std::stringは文字列をコピーするクラスですから、当然その先頭アドレスは違ってくるはずです。
でも、文字列を比較するイメージだけど実は先頭アドレスを比較するだけというようなコードを今まで書いてきて、特に問題ありませんでしたよね?(私だけw?)
それはですね、

CODE:

const char * str1 = "test";
const char * str2 = "test";
と書いたとき、testという文字列がメモリ上のどこかに記録されていて、その先頭アドレスをstr1とstr2が持つというネイティブコードにコンパイルされるから問題なく比較できるのだと考えられます。(あくまで私の推論です)

この論法で行くと、char[]型を使ったときやstrcpyを使った場合は文字列比較のときに==を使っても期待通りの結果が得られない可能性が高いですね。
ある文字列の途中の要素のインデックスを先頭アドレスとして持ちましたという場合ももちろんアウトですね。

私の場合は相当早い段階からstd::stringを使い始めたので今まで気づきませんでしたが、他の方々はどうなのでしょうか…?
ちなみに、今回わざわざconst char *型を使ったのは、正に ある文字列の途中の要素のインデックスを先頭アドレスとして持ちたかったからです。
抜き出した文字列を使っている間に元になった文字列が消滅することが無い状況の場合はこちらの方がパフォーマンスが良いですからね。
なにせstd::stringは文字列をコピーしてしまいますから。



それでは、良いプログラミングライフを。

アバター
tk-xleader
記事: 158
登録日時: 14年前

RE: 文字列比較でハマったので紹介

投稿記事 by tk-xleader » 12年前

MoNoQLoREATOR さんが書きました:testという文字列がメモリ上のどこかに記録されていて、その先頭アドレスをstr1とstr2が持つというネイティブコードにコンパイルされるから問題なく比較できるのだと考えられます。(あくまで私の推論です)
というのはC/C++のはまりやすい罠で。そのような処理系もありますが常にそのように実装されるとは限らないんですね…

アバター
GRAM
記事: 164
登録日時: 14年前

RE: 文字列比較でハマったので紹介

投稿記事 by GRAM » 12年前

自分は初めロベールのC++入門とかいう本で勉強し始めた記憶がありますが、確かに文字リテラルについては初めよくわかんなかった記憶があります。
どっちにしてもstd::vectorとstd::stringの有用性はすぐに明らかになったので、考えるのはすぐにやめました。
実際のところダブルクオーテーションで囲んだ文字がどういう扱いになっているかは仕様書を読もうとしか言えませんが、
C言語における配列を使うことは(C++で書く分には)まずないですし、細かいことが気になり始めるまで1年くらいは気にすることはなかったと思います。
ちなみに、今回わざわざconst char *型を使ったのは、正に ある文字列の途中の要素のインデックスを先頭アドレスとして持ちたかったからです。
抜き出した文字列を使っている間に元になった文字列が消滅することが無い状況の場合はこちらの方がパフォーマンスが良いですからね。
なにせstd::stringは文字列をコピーしてしまいますから。
パフォーマンスに関しては正直どうでもいいというか、この場合でも間違いなくconst std::stringを使うべきだと思いますよ。
まぁまず大前提として、
①std::stringを使えば、今回のような厄介な問題はまったく考える必要がないということ
それから
②仮にコピーが発生しても、気になるようなパフォーマンスの低下はまず起きないだろうということ

②に関しては実装によるとしか言えませんが、仮に最悪の設計だったとしても(つまり仕様を最低限満たすものだったとしても)
よほどの回数(それこそ1秒間に数万回とか)コピーが発生しない限り問題ないんじゃないかと思います。
最低限でない普通の実装ならC++03におけるstd::stringの実装であれば、参照カウント、バッファもしくは両方がありますし、
C++11においても多少変わるにしても最適化はかなりされているはずです。自分でプログラムを組むよりもよほど速いなんてことはざらだと思います。

結論:stdいいよstd
最後に編集したユーザー GRAM on 2013年1月23日(水) 02:20 [ 編集 1 回目 ]

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前

RE: 文字列比較でハマったので紹介

投稿記事 by h2so5 » 12年前

MoNoQLoREATOR さんが書きました: ここで、if文内の右辺と左辺をひっくり返してみましょう。

CODE:

if(str2 == str1) printf("Coincide!!");
するとなんと、今度は期待通りの結果になってくれるではありませんか!
str1 == str2 でも判定されますよ。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

Re: 文字列比較でハマったので紹介

投稿記事 by MoNoQLoREATOR » 12年前

返信ありがとうございます。

>>tkさん
そうなんですよね。
所詮、たまたまうまくいったのは何故かということを説明するための仮説ですからね。

>>GRAMさん
私もvectorとstringには一瞬で虜にされました。
パフォーマンスはそんなに気にしなくても良いのでしょうかね?
文書ファイル丸々1つ(NULLで区切られた文字列の羅列)から文字列を1つずつ全て取り出してそれぞれ文字列の種類に応じて別々のstd::string型変数に格納するということを1フレームで行わせようとしているのですが…。
先頭アドレスを取る方法でも、ファイルを丸々1つコピーしているのと同じですし、そうでない方法では2回コピーしているようなものなのですよね。
……かなり多く見積もってみても、ファイルの大きさは数百キロバイト以内には収まりそうです。
ということは気にしなくてもよさそうですね。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前

Re: 文字列比較でハマったので紹介

投稿記事 by softya(ソフト屋) » 12年前

.c_str()で比較したのがまずかったですね。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

Re: 文字列比較でハマったので紹介

投稿記事 by MoNoQLoREATOR » 12年前

>>h2so5さん
あれ?operator==ってそっち向きの引数も取れましたっけ?
…取れるようですね。期待通りの結果も返してくれます。

アバター
GRAM
記事: 164
登録日時: 14年前

Re: 文字列比較でハマったので紹介

投稿記事 by GRAM » 12年前

MoNoQLoREATOR さんが書きました:>>h2so5さん
あれ?operator==ってそっち向きの引数も取れましたっけ?
…取れるようですね。期待通りの結果も返してくれます。
それをどうやって実現するかは、ヘッダを見れば明らかですよ。たとえばVC++なら

CODE:

template inline
	bool operator==(
		const _Elem * _Left,
		const basic_string& _Right)
	{	// test for NTCS vs. string equality
	return (_Right.compare(_Left) == 0);
	}

template inline
	bool operator==(
		const basic_string& _Left,
		const _Elem *_Right)
	{	// test for string vs. NTCS equality
	return (_Left.compare(_Right) == 0);
	}
単純にインライン展開してstd::basic_string::conpareを呼び出してるだけです。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 14年前
住所: 東京

Re: 文字列比較でハマったので紹介

投稿記事 by MoNoQLoREATOR » 12年前

なるほど。グローバルな演算子オーバーロードが定義されているのですね。