Conversation
| * https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.rxnrgw1lj851 | ||
| * どっちでもいいのではという風潮 | ||
| * https://cpprefjp.github.io/reference/set/set.html | ||
| * `contains` C++20 からなので割と新しい。なんでこれがなかったんだろう。まあ `count` とかすればいいのはそうだけど。 |
There was a problem hiding this comment.
C++ STL は、哲学として「どのようなコンテナもできるだけ同じメソッドを用意する」という考えがあった(と理解しています)。
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0458r2.html
https://langdev.stackexchange.com/questions/3960/why-did-stdset-not-have-a-contains-function-until-c20
https://stackoverflow.com/questions/42532550/why-does-stdset-not-have-a-contains-member-function
There was a problem hiding this comment.
(basic_)string にだけ length があるのは、contains などと同じ背景なのかな、と想像しました(一般的なコンテナは size を持つ)。
https://cpprefjp.github.io/reference/vector/vector.html
https://cpprefjp.github.io/reference/string/basic_string.html
| * たぶん C++ `==` は値の比較。オブジェクトの比較はポインタを使う (ポインタの `==` はメモリアドレスの比較) | ||
| * より厳密には `operator==` の実装依存、という話で、型によって違うという話だと思うが、個別の型でどう実装されているかまではチェックしていない | ||
| * https://en.cppreference.com/w/cpp/language/operators.html | ||
| * Java のジェネリクスみたいな話なんだと思っているが、ジェネリクス使った実装はやったことがない |
There was a problem hiding this comment.
Template はコンパイル時にソースコードを自動生成する技術で、Generics は型をつけておいて実行時に解決するものです。Java の Generics はキャストしているだけですね。
上の operator== は演算子のオーバーロードの話なのでまた別です。
There was a problem hiding this comment.
なるほど…これが根本的に違うものだというのは理解していませんでした。Template 、なんかマクロのような感じを受けますね (T をコンパイラが実際の type に入れ替えてクラスを生成している)。Template を勉強するときに改めて確認してみます。
There was a problem hiding this comment.
しっかり内容を検証してはいないですが、軽く Gemini に聞いてみた内容を記録しておきます。
1. Template はコンパイル時にソースコードを自動生成する技術
これは C++ の Template の特徴を正確に捉えています。
- 仕組み: C++ の
template <typename T>は、それ自体が直接コンパイルされる「コード」ではなく、「コードを生成するための設計図」です。 - 動作タイミング: コンパイラが、そのテンプレートが実際に使われた箇所(例:
std::vector<int>やMyFunction<double>())を見つけると、その型(この場合はintやdouble)を設計図に当てはめて、その型専用の具体的なコードをコンパイル時に自動生成します。このプロセスを「インスタンス化(実体化)」と呼びます。 - 結果:
std::vector<int>用のクラスコードとstd::vector<string>用のクラスコードは、コンパイル後のバイナリ内では全く別々のコードとして存在します。 - 利点: 型ごとに最適化されたコードが生成されるため、実行時のパフォーマンスが非常に高いです(実行時に「何の型だっけ?」と調べるコストや、型変換のコストがかからない)。
- 欠点: 使われる型ごとにコードが生成されるため、コンパイル時間が長くなったり、最終的な実行ファイルのサイズが大きくなったりする(コード肥大化)可能性があります。
2. Java の Generics はキャストしているだけですね
これも、Java の Generics の実装方法(型消去)を指した、やや簡略化した表現ですが、本質を突いています。
- 仕組み(型消去 - Type Erasure): Java の
ArrayList<String>といったジェネリクスは、コンパイル時の型チェックのためだけに使われます。 - 動作タイミング: ソースコード上では
ArrayList<String>にIntegerを入れようとするとコンパイルエラーになりますが、コンパイルされてバイトコード(Javaの実行ファイルのようなもの)になる段階で、<String>のような型情報はすべて消去されます。 - 結果: バイトコード上では、ジェネリクス導入前の古いコードと同じように、すべて
Object型として扱われます。そして、リストから値を取り出すlist.get(i)のような箇所で、コンパイラが自動的に(String)という型キャストのコードを裏で挿入します。 - 「キャストしてるだけ」の意味: このように、Java のジェネリクスは「コンパイル時に安全性をチェックし、実行時は(裏で)キャストに頼る仕組み」であるため、このように表現されることがあります。
- 利点: 古い Java(ジェネリクスがなかった時代)のコードとの互換性を保ちやすいです。
- 欠点: 実行時には型情報が失われているため、一部の操作(リフレクションなど)に制約があります。また、実行時にキャストのコストが(わずかですが)発生します。
3. Generics は型をつけておいて実行時に解決するものです
これは "Generics" という言葉を一般論として語っていますが、恐らくコメンテーターは C# の Generics のような実装を念頭に置いている可能性が高いです。
C++ (コンパイル時生成) と Java (型消去) の中間的な実装として、C# や .NET のジェネリクスがあります。
- 仕組み: C# のジェネリクスは、Java と違って型情報を消去しません。コンパイル後の中間コード(バイトコードに似たもの)にも、実行時にも型情報が保持されます。
- 動作タイミング: プログラムの実行時(正確には JIT コンパイル時)に、CLR(.NET の実行環境)が必要に応じて型固有のコードを生成します。
- 結果: C++ のように型固有の最適化されたコード(特に数値などの値型の場合)の恩恵を受けつつ、Java のように実行時まで型情報を保持する柔軟性も持ちます。
まとめ
このコメントは、以下の 3 者の実装方法の違いを対比しています。
- C++ Template: コンパイル時に型ごとの実コードを生成(静的・高速)。
- Java Generics: コンパイル時に型チェックし、バイトコードでは型情報を消去(実行時は実質キャスト)。
- (おそらく) C# Generics: 実行時まで型情報を保持し、実行時に型を解決・コード生成。
コードレビューの文脈では、「C++ の Template は Java のような実行時のキャスト処理(オーバーヘッド)を伴うものではなく、コンパイル時に解決される全く別物(より高速な仕組み)ですよ」という意図で発言されたものと推測されます。
141. Linked List Cycle
https://leetcode.com/problems/linked-list-cycle/