C++11 あたりから、つーか、VC++2008 でもあったような気がするのですが、C++ では C# の var のように auto が使えます。
VB.NET の dim が dim に変わったように、C++ の auto が auto に変わったわけですが、昔の auto を知らない方は、まあ、知らなくてもよいかと。事実上使わなかったし。
さて、C++ でも auto で型推論ができるようになった訳ですが、これにちょっと落とし穴があるってのを少し。
auto を何に使うかというと、最初は typedef の代わりですかね。よくやる std::vector<string>::iterator ってのを、var で書き直すと非常に楽になります。
1 2 3 4 5 | vector< string > vec; for ( vector< string >::iterator it=vec.begin(); it != vec.end(); ++it ) { ... } |
こんな風に横に長いコードが
1 2 3 4 5 | vector< string > vec; for ( auto it=vec.begin(); it != vec.end(); ++it ) { ... } |
という風に書けます。かつては、typedef をして
1 2 3 4 5 6 7 8 | vector< string > vec; typedef vector< string >::iterator VECTOR_IT: </p> <p> for ( VECTOR_IT it=vec.begin(); it != vec.end(); ++it ) { ... } |
というコードもあったのですが、これで無駄な typedef が駆逐されます。この typedef って #ifdef ができないので、結構厄介なのです。
■型推論を推論する落とし穴
皆さまご存じの通り、C++ には、「値型」と「ポインタ」って区別があります。更に云えば、「参照」ってのがあります。
先の型推論「auto」を使うと、
1 2 | vector< int > vec; auto bar = vec; |
としたときに、bar の中身は vec と同じになります。なので、一見、「参照」のように見えるので、
1 2 | vector< int > vec; vector< int > &bar = vec; |
と思い気や、実は違います。vector<int> のコピーになり、vec と bar は別ものなのですよ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <vector> #include <iostream> using namespace std; int main() { vector< int > vec; auto bar = vec; // 要素を追加する vec.push_back(1); cout << "vec:" << vec.size() << endl; cout << "bar:" << bar.size() << endl; return 0; } |
のコードを動かすと vec:1, bar:0 という値を得ます。
そこで「参照」であることを明確にして「&bar」のようにすると、
1 2 3 4 5 6 7 8 9 10 | int main() { vector< int > vec; auto &bar = vec; // 要素を追加する vec.push_back(1); cout << "vec:" << vec.size() << endl; cout << "bar:" << bar.size() << endl; return 0; } |
vec:1, bar:1 という値を得ます。
ちなみに、ポインタを推論させて 「&vec」と指定すると、うまく vec:1 bar:1 になります。
1 2 3 4 5 6 7 8 9 10 | int main() { vector< int > vec; auto bar = &vec; // 要素を追加する vec.push_back(1); cout << "vec:" << vec.size() << endl; cout << "bar:" << bar->size() << endl; return 0; } |
なので、C++ の auto による型推論は、bar と &bar と *bar をうまく使い分けないと駄目なんですよ。まあ、大抵の場合大丈夫なんですが、コピーコンストラクタが定義してある場合はコンパイルエラーにならないのではまりどころです。
ちなみに、C# の場合は、このような装飾子がないので、推論に任せるしかないって感じなんですけどね。暗黙の変換を利用すると、var と 型指定では違った値にするトリックもできるし、dynamic の場合は型が後から変換されるために更にややこしかったり。
autoのコピー云々でハマる人はautoでは無くコピーの仕様にハマっているのではないかと思うのでこれがautoのハマリポイントというのは違う気も
コピーに関しては同感なんですが(後で気づいた)、c# の場合、var ひとつでいけるのですが、c++ の場合は auto と auto & , auto * 等を使い分けないといけない、ってことです。逆に言えば「使い分けられる」ってことですね。
vector vec;
auto bar = vec;
は、
vector vec;
vector bar = vec;
なので、これが参照に見える人はいないと思います。
C#のオブジェクト風に考えるなら、
vector *vec;
auto bar = vec;
は、
vector *vec;
vector *bar = vec;
となり、予想通りですから、落とし穴というわけではないのではないでしょうか。
Oui. C++ でポインタに慣れていると不思議でもないのですが(実際、私も後から気づいた)、C# で var *bar = vector(…) なんてことを書かないので、一瞬「???」な感じになります。正確には、Java/C# は、参照型が主なので「全てがポインタなのだ」とわかると、C++ -> Java に移ったときに理解が早いか…ってのが10年前でした。先の auto の件は、C# -> C++ で悩んだパターンです。