はじめに
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>
型のメンバ変数 odd
と even
を持っており、コンストラクタでは引数として渡された std::vector<int>
の各要素のうち奇数をodd
に偶数をeven
に振り分け初期化します.odd
と even
は const
で宣言されているため、コンストラクタの初期化リストで設定する必要があります.そこで,奇数のみ,もしくは偶数のみを取り出すヘルパー関数 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回走査しています.今回のケースではさほど問題ありませんが,odd
と even
の初期化にもっと複雑な計算が必要な場合や,それらを同時に計算したほうが効率的な場合,2回に分けて処理を行いたくない状況も考えられます.odd
と even
が const
でなければ,コンストラクタ本体でこれらを計算することで問題は簡単に解決しますが,どうしても const
にしたいという制約がある場合,他の方法を検討する必要があります.
2つのconstメンバ変数の同時初期化
そこで,ヘルパー関数を1回だけ呼び出し,odd
と even
の両方を同時に初期化する方法を示します.これにより,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)
)を呼び出しています.このコンストラクタでは,引数で受け取ったペアの first
と second
を使って、odd
とeven
をそれぞれ初期化しています。
おわりに
このテクニックはChatGPTに教えてもらいました.同様の方法で,3つ以上のメンバ変数を同時に初期化する場合も,std::tuple
を使うことで実現可能です.