日記的「駄目」プログラミング Yet Another Hacking Life

C++0xのtupleを使って関数から複数の戻り値を返す

12.05.2010 · Posted in C++, プログラミング

C++0xのSTLにはtupleテンプレートクラスが導入され、任意の型の任意の要素数の組を必要に応じて定義できるようになった。これを関数の戻り値とすることで複数のオブジェクトを返せないかというお話。ひどくいまさら感のある話題だとも思うし、自分は勉強の仕方がいい加減なので穴もあると思う。ただ、ここ最近、気になってしかたがなかった(使ってみたかった)機能ではあるのでまとめておく。

まず、それができてなにがうれしいか。関数の結果として複数の値を返したいときがままある。たとえば配列の最大要素と最小要素を同時に返すような場合など。この時、従来のC++では、思いつく限り以下の3つのスタイルがとられたと思う。

  1. 複数のポインタ引数をとる
  2. 複数の参照引数をとる
  3. 構造体で返す

で、それぞれ以下の問題がある。

  1. NULLチェックだるい
  2. RAIIと相性が悪い
  3. 構造体定義だるい、関数のバリエーションごとに定義すると思うともっとだるい

で、tupleを返すようにすれば「NULLチェックがいらず、RAIIのスタイルが維持でき、関数の戻り値の記述と構造体の定義が一ヶ所にまとまる」。万事解決。なのか?

とりあえず書こう。

#include <memory>
#include <map>
#include <utility>
#include <tuple>

#include <cstdio>
#include <cstring>

class Test
{
public:
	// constructor
	Test() : mData( new char[100] )
	{
		mData[0] = 0;
		printf("default constructor\n");
	}

	// copy constructor
	Test( const Test& test ) : mData( new char[100] )
	{
		memcpy(this->mData, test.mData, 100);
		printf("copy constructor\n");
	}

	// move constructor
	Test( Test&& test ) : mData( test.mData )
	{
		test.mData = NULL;
		printf("move constructor\n");
	}

	// destructor
	virtual ~Test() throw() { delete[] mData; };

	// copy operator
	Test& operator=(Test& test)
	{
		printf("copy operator\n");
		memcpy(this->mData, test.mData, 100);
		return *this;
	}

	// move operator
	Test& operator=(Test&& test)
	{
		printf("move operator\n");
		std::swap(this->mData, test.mData);
		return *this;
	}

	char* mData;
};

std::tuple<Test,Test,Test>
func()
{
	using namespace std;
	return std::tuple<Test, Test, Test>( Test(), Test(), Test() );
}

int
main(int argc, char* argv[])
{
	using namespace std;

	if(true)
	{
		// good style
		tuple<Test, Test, Test> myTuple = func();

		Test& test0 = get<0>(myTuple);
		Test& test1 = get<1>(myTuple);
		Test& test2 = get<2>(myTuple);
	}
	else
	{
		// bad style
		Test test0;
		Test test1;
		Test test2;
		tuple<Test&, Test&, Test&> myTuple(test0, test1, test2);
		myTuple = func();
	}

	return 0;
}

こんな感じになるのかな。こまかいところはご愛嬌。動作確認はGCC4.5.1。
good styleじゃないとRAIIできない上に、無駄なコンストラクタが走る。
good styleの実行結果。

default constructor
default constructor
default constructor
move constructor
move constructor
move constructor

bad styleの実行結果。

default constructor
default constructor
default constructor
default constructor
default constructor
default constructor
move constructor
move constructor
move constructor
move operator
move operator
move operator

good styleの不満は2つ。参照で名前をつけなければいけない点と、tupleのどのメンバが何を表すのかがコード上の仕様に現れない点。tuple使う限り仕方がないのかな。
多分なにか見逃してる気がする。

Leave a Reply