[C++] そういえば auto の落とし穴を示しておくと

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 で書き直すと非常に楽になります。

vector<string> vec;
for ( vector<string>::iterator it=vec.begin(); it != vec.end(); ++it )
{
  ...
}

こんな風に横に長いコードが

vector<string> vec;
for ( auto it=vec.begin(); it != vec.end(); ++it )
{
  ...
}

という風に書けます。かつては、typedef をして

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」を使うと、

vector<int> vec;
auto bar = vec;

としたときに、bar の中身は vec と同じになります。なので、一見、「参照」のように見えるので、

vector<int> vec;
vector<int> &bar = vec;

と思い気や、実は違います。vector<int> のコピーになり、vec と bar は別ものなのですよ。

#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」のようにすると、

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 になります。

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 の場合は型が後から変換されるために更にややこしかったり。

カテゴリー: C++ パーマリンク

[C++] そういえば auto の落とし穴を示しておくと への4件のフィードバック

  1. 南山まさかず のコメント:

    autoのコピー云々でハマる人はautoでは無くコピーの仕様にハマっているのではないかと思うのでこれがautoのハマリポイントというのは違う気も

    • masuda のコメント:

      コピーに関しては同感なんですが(後で気づいた)、c# の場合、var ひとつでいけるのですが、c++ の場合は auto と auto & , auto * 等を使い分けないといけない、ってことです。逆に言えば「使い分けられる」ってことですね。

  2. s のコメント:

    vector vec;
    auto bar = vec;
    は、
    vector vec;
    vector bar = vec;
    なので、これが参照に見える人はいないと思います。

    C#のオブジェクト風に考えるなら、

    vector *vec;
    auto bar = vec;
    は、
    vector *vec;
    vector *bar = vec;

    となり、予想通りですから、落とし穴というわけではないのではないでしょうか。

    • masuda のコメント:

      Oui. C++ でポインタに慣れていると不思議でもないのですが(実際、私も後から気づいた)、C# で var *bar = vector(…) なんてことを書かないので、一瞬「???」な感じになります。正確には、Java/C# は、参照型が主なので「全てがポインタなのだ」とわかると、C++ -> Java に移ったときに理解が早いか…ってのが10年前でした。先の auto の件は、C# -> C++ で悩んだパターンです。

コメントは停止中です。