0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++の初期化リストで複数のメンバ変数を同時に初期化

Last updated at Posted at 2024-10-01

はじめに

C++のコンストラクタ初期化リストでは,メンバ変数はクラス定義で宣言された順に,対応するコンストラクタ引数を渡して初期化されます.そのため、基本的にはメンバ変数の初期化は逐次的に行われます.しかし,計算結果を使って複数のメンバ変数を同時に初期化したい場合には工夫が必要です.その工夫の話です.

constなメンバ変数の初期化

constなメンバ変数の初期化は,コンストラクタの初期化リストで行う必要があります.次がその例です.

#include <iostream>
#include <vector>

class A {
  const std::vector<int> odd;
  const std::vector<int> even;

  // ヘルパ関数:奇数もしくは偶数だけ取り出したベクトルを返す.
  static std::vector<int> func(const std::vector<int> &vec, int modulo) {
    std::vector<int> result;
    for (auto elem : vec) {
      if (elem % 2 == modulo) {
        result.push_back(elem);
      }
    }
    return result;
  }

 public:
  // ヘルパ関数funcを2回呼び,oddとevenをそれぞれ初期化.
  A(const std::vector<int> &x)
      : odd(std::move(func(x, 1))), even(std::move(func(x, 0))) {}

  const std::vector<int> &get_odd() const { return odd; }
  const std::vector<int> &get_even() const { return even; }
};

int main() {
  std::vector<int> x = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
  A a(x);

  std::cout << "odd:";
  for (auto elem : a.get_odd()) {
    std::cout << " " << elem;
  }
  std::cout << std::endl;
  std::cout << "even:";
  for (auto elem : a.get_even()) {
    std::cout << " " << elem;
  }
  std::cout << std::endl;
}

クラスAは,std::vector<int> 型のメンバ変数 oddeven を持っており、コンストラクタでは引数として渡された std::vector<int> の各要素のうち奇数をoddに偶数をevenに振り分け初期化します.oddevenconst で宣言されているため、コンストラクタの初期化リストで設定する必要があります.そこで,奇数のみ,もしくは偶数のみを取り出すヘルパー関数 func を用いて,odd(func(x, 1))even(func(x, 0)) のように初期化リスト内で呼び出し,その返り値を用いて初期化しています.実行結果は次のようになり,正しいことが確認できます.

odd: 3 1 1 5 9 5 3 5
even: 4 2 6

この方法では,func を2回呼び出しているため,入力として渡された std::vector<int> を2回走査しています.今回のケースではさほど問題ありませんが,oddeven の初期化にもっと複雑な計算が必要な場合や,それらを同時に計算したほうが効率的な場合,2回に分けて処理を行いたくない状況も考えられます.oddevenconst でなければ,コンストラクタ本体でこれらを計算することで問題は簡単に解決しますが,どうしても const にしたいという制約がある場合,他の方法を検討する必要があります.

2つのconstメンバ変数の同時初期化

そこで,ヘルパー関数を1回だけ呼び出し,oddeven の両方を同時に初期化する方法を示します.これにより,std::vector<int> を1度だけ走査し,効率的な初期化が可能になります.次のソースコードがその実装例です.

#include <iostream>
#include <utility>
#include <vector>

class A {
  const std::vector<int> odd;
  const std::vector<int> even;

  A(std::pair<std::vector<int>, std::vector<int>> p)
      : odd(std::move(p.first)), even(std::move(p.second)) {}

  static std::pair<std::vector<int>, std::vector<int>> func(const std::vector<int> &vec) {
    std::vector<int> odd;
    std::vector<int> even;
    for (const auto &elem : vec) {
      if (elem % 2 == 1) {
        odd.push_back(elem);
      } else {
        even.push_back(elem);
      }
    }
    return {odd, even};
  }

 public:
  A(const std::vector<int> &x) : A(func(x)) {}

  const std::vector<int> &get_odd() const { return odd; }
  const std::vector<int> &get_even() const { return even; }
};

int main() {
  std::vector<int> x = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
  A a(x);

  std::cout << "odd:";
  for (const auto &elem : a.get_odd()) {
    std::cout << " " << elem;
  }
  std::cout << std::endl;
  std::cout << "even:";
  for (const auto &elem : a.get_even()) {
    std::cout << " " << elem;
  }
  std::cout << std::endl;
}

ヘルパー関数 func は,std::vector<int> から偶数と奇数をそれぞれとりだした std::vector<int> をペアにして返す関数に変更しました.そして,コンストラクタでは,この func の返り値を引数として、別のprivateなコンストラクタ(A(const std::pair<std::vector<int>, std::vector<int>> &p))を呼び出しています.このコンストラクタでは,引数で受け取ったペアの firstsecond を使って、oddevenをそれぞれ初期化しています。

おわりに

このテクニックはChatGPTに教えてもらいました.同様の方法で,3つ以上のメンバ変数を同時に初期化する場合も,std::tuple を使うことで実現可能です.

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?