C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[8]: 連想コンテナでのポインター使用は可能ですか


(過去ログ 125 を表示中)

[トピック内 9 記事 (1 - 9 表示)]  << 0 >>

■74521 / inTopicNo.1)  連想コンテナでのポインター使用は可能ですか
  
□投稿者/ キム (31回)-(2015/01/08(Thu) 13:37:40)

分類:[C/C++] 

std::mapやstd::setにポインター型を使用することは可能ですか

開発環境: Visual C++ 2010 / Windows 7

お世話になります。

std::mapやstd::set等の連想コンテナでポインター型を使用することは可能でしょうか。
std::mapのキーやstd::setに入れる型には 関係演算子 < が必要です。
ところが、JIS X3014:2003 5.9 関係演算子の2項を読むと、2つの異なったオブジェクトを指すポインター間
の比較結果は特定の条件を除いて未定義であるように思えます。
これを見ると、連想コンテナでポインター型を使用するとその動作は未定義ということになってしまいます。

でも、実際にstd::mapのキーやstd::setにポインター型を使用してみると正常に動作しているように見えます。
環境依存でたまたま動作しているだけとも考えられますが、ポインター型が使えないという話は聞いたことが
ないので、なんらかの仕様で動作が保証されているのではないかと予想しています。

でもそのことを示す資料は見つけられませんでした。
やはり環境依存でたまたま動作しているだけなのでしょうか?
何かヒントやキーワードだけでも結構ですので、ご教示いただけると嬉しいです。

引用返信 編集キー/
■74522 / inTopicNo.2)  Re[1]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ 774RR (206回)-(2015/01/08(Thu) 15:12:24)
> 2つの異なったオブジェクトを指すポインター間の比較結果は
> 特定の条件を除いて未定義であるように思えます。
Yes!

C++ コンテナは元の値の複写を格納するので、生ポインタを入れるのは普通間違いなわけだ。
char* p=strdup("hoge");
container.push_back(p); // p の値の複写が格納される
free(p); // コンテナ中のポインタ値が指す先はもう無い
# この場合コンテナに格納すべきは char* でなく std::string のほうが適切

コンテナの寿命より中に入れるポインタの先オブジェクトの有効寿命が長いことを
プログラマの責任で保証するならばポインタ値をコンテナに格納することに問題は無い。
通常これが困難なので、ポインタでなく複写した「値」を格納するのが良いわけだ。
どうしてもポインタでなきゃならないなら、生ポインタ値でなくスマートポインタを使う。

大小比較を伴うコンテナ (map/set など) にポインタ値を格納する場合には
指摘のとおり「ポインタ値同士の比較」が無意味なので
ユーザにとって意味がある比較演算子を提供しなきゃならないよ。
struct my_less_comparator {
bool operator() (const char* lop, const char* rop) { return strcmp(lop, rop)<0; }
};

std::map<char*, char*, my_less_comparator> m;
とか。

引用返信 編集キー/
■74523 / inTopicNo.3)  Re[2]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ キム (32回)-(2015/01/08(Thu) 16:16:08)
No74522 (774RR さん) に返信

774RRさん、お返事ありがとうございます。

std::mapのキーに文字列リテラルを使いたいというのはありそうな要望だと思うのです。
例えば、
std::map<const char*, const char*>とかで、
std::make_pair("Foo", "Fooとはなんたらかんたら...")みたいに。

この場合もやはり「ポインター値同士の比較」が未定義なのであればchar*ではなくstd::stringを使うべき
となってしまいますね。
もちろん、std::stringが文字列の内容で大小を比較するのに対してchar*はポインター値の比較で大小を決定
するので、"ZZZZ"と"AAA"で"AAA"のほうが小さいとは限らないことは理解しています。
が、この場合はコンテナが構成できればよいので「ポインター値同士の比較」がなんらかの仕様で有効なので
あれば問題ないし、簡潔に書けていいなあと思ったのです。

ヒントもいただいたし、すっきり理解したいのでもう少し調べてみます。
引用返信 編集キー/
■74524 / inTopicNo.4)  Re[3]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ キム (33回)-(2015/01/08(Thu) 16:38:33)
調べてみたら有名なスマートポインター(boost::shared_ptr<T>等)は operator < を提供していますね。
これって結局はラップしている生ポインターの大小比較を行っているわけで、同じ問題に突き当たると
思うんですけど、どうやってるのかしら??
この辺りにヒントがありそうです。調べてみます。

引用返信 編集キー/
■74525 / inTopicNo.5)  Re[4]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ キム (34回)-(2015/01/08(Thu) 17:38:06)
いろいろ調べた結果、C++11のstd::shared_ptr<T>ではoperator < が std::less<T*>の比較結果を返すことが
わかりました。
そして、独自のless_comparatorを指定しないstd::map<K, T>もstd::less<K>で大小比較します。

そこで、JIS X3014:2003 20.3.3 比較演算 の8項を見てみたら、std::lessテンプレートがポインター型に対
して特殊化を行っていること、組み込み演算子の < 演算子が全順序でなかったとしても全順序となることが
書かれていました。
これってstd::lessを使えば(どうやって実現しているか分からないですけど)2つの異なったオブジェクトを
指すポインター間の大小関係が必ず求められると解釈したんですけど、あっていますか?

もしそうなら、ご教示いただいた内容と調べた結果から、
・ポインターが指すオブジェクトの有効寿命が十分に長ければポインター型を使うことは問題ない。
・大小比較がポインター値(アドレス比較?)で行われることが問題ないなら既定のless_comparatorのままで
 問題なし。
・ポインターが指すオブジェクトの内容で大小比較を行う必要があるなら独自のless_comparatorを提供しな
 ければならない。
ということになりますね。

もしこれが正しいならすっきりしました。
そして、ここまで理解したところで、文字列キーにはchar* でなく std::string のほうが総合的に適切
なんだなあと思えるようになりました。
(文字列以外ではわざわざポインター型を使いたくなるようなケースは思いつかないです)

何か勘違いしている様でしたらご指摘ください。
引用返信 編集キー/
■74539 / inTopicNo.6)  Re[5]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ 774RR (207回)-(2015/01/09(Fri) 10:31:57)
長くなったので結論から先に書く。
> 2つの異なったオブジェクトを指すポインター間の大小関係が必ず求められる
Yes.
・ポインタ生値の大小比較はできる(正しいプログラム動作として)
・その結果に意味があるかどうかは話が別 (特に map/set の comparator として)

以下長文技術解説。興味が無い人は読む必要は無い。
ISO/IEC 14882:1998 しか今オイラの手元に無いのでこれをベースに

先の発言 74522 で Yes (未定義) と書いたがこれは間違いで (すんません)
5.9 には、ポインタの大小比較の結果は (特に定める状況以外では)
「未規定」 unspecified であると書いてある。

言語規格書の用語
「処理系定義」 implementation-defined
正しいプログラムであって、言語仕様書がその詳細を定めないが、
処理系実装者 (Microsoft や GCC Develop Team) が動作について文書化しなければならないもの
例 : int のサイズ (は 32bit である、と文書化しなければならない)

「未規定」 unspecified
正しいプログラムであって、言語仕様書がその詳細を定めないもの
処理系実装者 (Microsoft や GCC Develop Team) が動作を文書化しなくてよいもの
例 : 部分式の評価順 a=b()+c(); があったとき b() が先に呼ばれるか c() が先か

「未定義」 undefined
誤ったプログラムであって、言語仕様書がその詳細を定めないもの
プログラムは、それを書いたプログラマの勝手な期待のとおりに一見正しく動作しても良いし
エラーで停止しても良いし、まったく予期しない異常動作を起こしても良い
例 : fflush(stdin); とか double x=1.0/0.0; とか

5.9 は仕様書が定めるところの正しい false/true が得られる状況の限定をしている。
それ以外の状況でどうなるかは unspecified つまり
・比較してよい (正しいプログラムなので暴走したりしてはならない)
・比較結果は知らない (が bool だから false/true 以外が得られることは無い)

20.3.3 - 8 大小比較
んで、手元の GCC 4.8.2, GCC 4.5.3, VC++2005 で探してみたけどどれも std::less の特殊化を
実装してない。どれも generic に x<y しているだけだ。ということは 5.9 の文言に基づき
最初に述べた結論になる。
一般的 32bit/64bit マシンではポインタをメモリアドレスで実装しているので、
ポインタの大小比較結果はアドレスの大小比較の結果になるだけだ。

> ということになりますね。
まさに御意。

追記:ポインタの比較が未定義なのは C の場合だ。
JIS X 3010:2003, ISO/IEC 9899:1999 6.5.8 関係演算子
> 2つのポインタを比較する場合 (snip) その他の全ての場合、動作は未定義とする。
この辺 C++ では改善されてるんだ・・・知らんかった。勉強になったですよ。
引用返信 編集キー/
■74558 / inTopicNo.7)  Re[6]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ キム (35回)-(2015/01/09(Fri) 14:39:53)
No74539 (774RR さん) に返信

774RRさん、詳細な解説ありがとうございます。
深く理解することが出来てとても嬉しいです。

> 「未規定」 unspecified であると書いてある。
確かに「未規定」と書いてありました。未定義と未規定ってまったく意味が違うんですね。
深く意識せずに未定義と脳内変換してしまってました。

> んで、手元の GCC 4.8.2, GCC 4.5.3, VC++2005 で探してみたけどどれも std::less の特殊化を
> 実装してない。どれも generic に x<y しているだけだ。ということは 5.9 の文言に基づき
> 最初に述べた結論になる。
ご調査ありがとうございます。
私も VC++ 2010 で探してみたけどstd::less の特殊化は実装されていませんでした。
逆に考えると、20.3.3 - 8で std::less の全順序保証が明記されているのに特殊化されていないということは、
これらの処理系では ポインター型の大小比較が組み込み演算子でも全順序であることの証明になりますね。

> この辺 C++ では改善されてるんだ・・・知らんかった。勉強になったですよ。
こちらこそ、とても勉強になりました。ありがとうございます。
#私は C++ から入ったので C は深くは知らないですが、C++でよかったなと思います。
#たぶん、C だと不便に感じちゃうと思います。

以下、まとめです。

■2つの異なったオブジェクトを指すポインター間の大小比較について
・組み込みの関係演算子での大小比較は未規定(未定義ではないのでプログラムとしては正しいが詳細は実装依存)
・一般的に32bit/64bit マシンではポインタをメモリアドレスで実装しているので、大小比較は指し示すアドレス
 で行われると考えられる。
・std::less での大小比較は全順序となることが保証されている。
・GCC 4.8.2, GCC 4.5.3, VC++ 2005, VC++ 2010 では std::less のポインター型に対する特殊化を実装しておらず、
 x < yとしているだけ。
 よって、これらの処理系では組み込みの関係演算子での大小比較も全順序となると考えられる。
詳細は No74539 を参照。

■std::mapのキーやstd::setでのポインター型使用について
・条件を満たせばポインター型の使用は問題ない。
・必要であれば独自のless_comparatorを提供する。
詳細は No74525 を参照。
解決済み
引用返信 編集キー/
■74575 / inTopicNo.8)  Re[7]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ 774RR (210回)-(2015/01/09(Fri) 19:53:31)
うん、すばらしいまとめだ。みんなこうだったら最高だよね。
# stackoverflow.com みたいに厳密にヤるのはうっとうしいんだけどさ

蛇足などを付け加えてみる。

#74539 ISO/IEC 14882:1998 言語仕様書的には単に implementation-defined 等ではなくて
1.3.5 implementation-defined behavior
1.3.12 undefined behavior
1.3.13 unspecified behavior
となっていて「・・・であるところの振る舞い」とすべきだろう。解説内容/結果は変わらない。

コンテナに生ポインタを格納するのは「コンテナとその中身の寿命が連動しない」のでバグの元だから俺としては非推奨だ。
コンテナよりも生ポインタの指す先オブジェクトが先に消失する とか
コンテナが消失した結果、生ポインタの指す先オブジェクトが宙に浮いた(リークした) とか
そういうバグを発生させないためには単純に「コンテナには値を入れろ」で済むわけだ。
そうすりゃコンテナ自体とコンテナの中身が生死を共にするので、考えなきゃならないことが減る。
# polymorphic なオブジェクトの base* を格納するにはポインタを入れるしかないわけだけど。
# 生ポインタを格納するくらいならスマートポインタ (shared_ptr) を格納するほうがマシ。

で、ポインタ値の大小を「中身の大小」と解釈するのは、ほとんどの場合無意味なわけで
・ポインタを格納し、ポインタの指す先の比較演算子を与えるか
・値を格納し、値の大小を比較する比較演算子を与えるか
のどっちかを必ず満たす、ってことなら、限りなく後者のほうが実装として自然になるだろう。

ってことでオイラも解決済みに1票。

解決済み
引用返信 編集キー/
■74579 / inTopicNo.9)  Re[8]: 連想コンテナでのポインター使用は可能ですか
□投稿者/ キム (37回)-(2015/01/09(Fri) 20:59:10)
No74575 (774RR さん) に返信

補足ありがとうございます。

> で、ポインタ値の大小を「中身の大小」と解釈するのは、ほとんどの場合無意味なわけで

一意のキーを指定して一意の値を得られればいい、要は辞書さえ引ければよくて並び順は一切不問
という場合に限られますよね。
例えば、コマンドワードで辞書を引いて関数ポインターを得て呼び出すみたいな。
この場合でも、オブジェクトの寿命や後始末に少しでも不安があるならスマートポインターを使うと。
#キーが文字列リテラルとかなら安全だと思いますけど。

少しでも順序に依存するなら値を格納するのが自然ですね。
もし、ポインタを格納してポインタの指す先の比較演算子を与えるような必要性が出てしまったら
『本当にその設計で大丈夫か』一回自分を疑いたいと思います。
そして、どうしてもポインターでなければならない場合はこちらもスマートポインターを使うと。

#スマートポインターの設計者さんはきっとものすごく深いところまで考えて operator < を
#実装しているんですね。
#私なんか今回の件まで operator < の存在に気付いてさえいなかったです。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -