101
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[C++] 関数から複数の戻り値を返す

Last updated at Posted at 2016-08-21

C++は関数から一つの値しか返せない?


C++ってゅぅのゎ。。

関数から複数の値を返せないの。。。

そして言語仕様も複雑。。。

もぅマヂ無理。。。

Pythonしょ。。。

元ネタ:http://d.hatena.ne.jp/keyword/%A4%E2%A4%A5%A5%DE%A5%C2%CC%B5%CD%FD

C++でも簡単に関数から複数の値を返すことができる!

できるんです!(ただし、C++11以降)

C++11から追加されたstd::tupleを使います。以下はサンプルコードです。

tuple.cc
#include <iostream>
#include <string>
#include <tuple>

std::tuple<int, char, std::string> GreatFunction() {
  return std::forward_as_tuple(9, 'q', "qiita");
}

int main() {
  int a;
  char b;
  std::string c;
  std::tie(a, b, c) = GreatFunction();

  std::cout << a << ", " << b << ", " << c << std::endl;
  // 出力 -> 9, q, qiita
  return 0;
}
  1. 戻り値をstd::tupleにする
  2. std::forward_as_tuple()で値を返す
  3. std::tie()で受け取る

これで関数から任意の数の戻り値を返すことができます。戻り値が2つの場合はstd::pairでも同じことができます。

受け取る必要のない引数がある場合はstd::ignoreを使って以下のように書きます。

std::tie(a, std::ignore, c) = GreatFunction();

無駄な処理は無い?

このやり方でオブジェクトを戻り値として返す場合、引数でオブジェクトの参照を渡すやり方(void Function(MyClass &out)みたいな方法)に比べて無駄な処理が走ります。以下のコードのように自作クラスのオブジェクトを戻り値として返す場合を考えます。

myclass.cc
#include <iostream>
#include <tuple>

class A {
 public:
  A() {
    std::cout << "Default constructor" << std::endl;
  }

  A(const A &a) {
    std::cout << "Copy constructor" << std::endl;
  }

  A(const A &&a) {
    std::cout << "Move constructor" << std::endl;
  }

  A& operator=(const A &a) {
    std::cout << "Copy assignment" << std::endl;
    return *this;
  }

  A& operator=(const A &&a) {
    std::cout << "Move assignment" << std::endl;
    return *this;
  }
};

std::tuple<int, A> SuperFunction() {
  A a;
  return std::forward_as_tuple(1, std::move(a));
}

int main() {
  A a;
  std::tie(std::ignore, a) = SuperFunction();
  
  return 0;
}

class Aは各コンストラクタ、代入演算子が呼ばれたときに出力するようになっています。これを実行すると、


Default constructor
Default constructor
Move constructor
Move assignment

と出力されます。関数内と引数受け取り側でそれぞれDefault constructorが呼ばれ、return std::forward_as_tuple...でMove constructorが呼ばれ、std::tie...で戻り値をaに代入するときにMove assignmentが呼ばれています。つまり、Default constructor1回分とMove constructor, Move assignmentが無駄な処理として実行されてしまいます。なので、コンストラクタやムーブ代入が重い処理のオブジェクトの場合は注意が必要です。また、ムーブ不可なオブジェクトではコピーが走ってしまうのでこの方法は推奨できません。

そのような場合は、戻り値を以下のコードのようにポインタに変更すれば解決します。

myclass-pointer.cc
std::tuple<int, std::unique_ptr<A>> ExcellentFunction() {
  std::unique_ptr<A> a(new A());
  return std::forward_as_tuple(1, std::move(a));
}

int main() {
  std::unique_ptr<A> a;
  std::tie(std::ignore, a) = ExcellentFunction();
  
  return 0;
}

こうするとAのコンストラクタが1回呼ばれるだけなので安心して使えます(std::unique_ptrのコンストラクト、ムーブはありますが、大した処理ではないので問題ないでしょう)。

余談

C++17では複数の値の受け取り方が以下のようにシンプルになるそうです。(P0144R2, P0217R2

auto [a, b, c] = GreatFunction();

a,b,cの宣言が不要になります。これで無駄なコンストラクタが1つ省けるのかな?


(追記:@yohhoyさんのコメントより)
C++17から関数の戻り値の部分もstd::forward_as_tupleを使わずに以下のように簡潔に書けるようになるみたいです。(N4387, LWG2367

std::tuple<int, char, std::string> GreatFunction() {
  return {9, 'q', "qiita"};
}

まとめ

  • std::tuplestd::tieを使えば簡単に関数から複数の値を返すことができる
  • コンストラクト、ムーブが重いオブジェクトやムーブ不可なオブジェクトを返すときはstd::unique_ptrを使って返す
101
77
18

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
101
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?