C++でクラスを設計する際、メンバ変数を「いつ、どのように初期化するか」はプログラムの安全性とパフォーマンスに直結する非常に重要なテーマです。
コンストラクタの本体 { } の中で値を代入している方は多いかもしれませんが、実はC++にはより効率的で推奨される「初期化子リスト(メンバー初期化子リスト)」という仕組みが存在します。
: は何の意味があるの?」「代入と初期化リスト、結局どちらを使えばいい?」
「メンバ変数が初期化される順番に決まりはある?」
こうした疑問を解決するために、この記事ではC++のコンストラクタにおける初期化の仕組みを徹底解説します。
初期化子リストの基本的な書き方はもちろん、実務で避けられない const 定数や参照型の初期化方法、そして意図しないバグを防ぐための初期化順序のルールまで、詳しく見ていきましょう。
![]() 執筆者:マヒロ |
|
- OS:Windows 11 / macOS Sequoia
- IDE:Visual Studio / VS Code / IntelliJ IDEA
- その他:Chrome DevTools / 各言語最新安定版
※本メディアでは、上記環境にてコードの動作と情報の正確性を検証済みです。
メンバ初期化子リスト(初期化リスト)の基本と書き方
コンストラクタにおける初期化には、大きく分けて「コンストラクタ本体での代入」と「初期化子リスト」の2通りの方法があります。
パフォーマンスや文法上の制約を考えると、初期化子リストを使いこなすことがC++エンジニアとしての第一歩と言えるでしょう。
ここでは、C++のコンストラクタでコロン(:)を用いて変数を初期化する、基本的な書き方を確認しましょう。
コロンを使った初期化子リストの構文
初期化子リストは、コンストラクタの引数リストの直後、そして関数本体の前にコロン : を置くことで記述します。
#include <iostream>
#include <string>
class User {
private:
std::string name;
int age;
public:
// 引数リストの後に「: メンバ名(値)」と記述する
User(std::string n, int a) : name(n), age(a) {
// 本体は空でもOK
std::cout << "Userオブジェクトが生成されました。" << std::endl;
}
void show() {
std::cout << "名前: " << name << ", 年齢: " << age << std::endl;
}
};
int main() {
User user("田中", 25);
user.show();
return 0;
}
実行結果
Userオブジェクトが生成されました。
名前: 田中, 年齢: 25
コンストラクタ User(std::string n, int a) の直後に書かれた : name(n), age(a) という部分が、今回注目すべき初期化子リストです。
ここで指定されたメンバ変数は、コンストラクタの本体である { } が実行されるよりも前の段階で、引数として渡された値で初期化されます。
この記述方法の大きな特徴は、変数がメモリ上に生成される瞬間に値を確定させることができる点にあります。
本体内で name = n; と書くのは「生成した後の代入」ですが、このリスト形式は「生成と同時、すなわち真の意味での初期化」となります。
複数のメンバがある場合は、カンマで区切って列挙することで、一括して初期化を行うことが可能です。
なぜ「代入」ではなく「初期化リスト」が必要なのか
多くの初心者はコンストラクタの本体で値を代入してしまいがちですが、これでは非効率な処理が発生したり、そもそもコンパイルエラーになったりすることがあります。
C++の言語仕様を深く理解するほど、コンストラクタを通じたメンバ変数の初期化において、なぜ代入よりもリスト形式が推奨されるのか、その理由が見えてきます。
ここでは、代入では解決できない特定のケースやメリットについて掘り下げていきます。
constメンバや参照メンバの初期化
クラスのメンバに const 定数や参照型(&)が含まれている場合、コンストラクタ本体での代入は不可能です。
#include <iostream>
class Configuration {
private:
const int version; // 書き換え不能な定数
int& parentId; // 参照型
public:
// 初期化子リストを使わないとコンパイルエラーになる
Configuration(int v, int& p_id) : version(v), parentId(p_id) {
// version = v; // 本体でこれをやるとエラー!
}
void display() {
std::cout << "Ver: " << version << ", PID: " << parentId << std::endl;
}
};
int main() {
int id = 100;
Configuration config(1, id);
config.display();
return 0;
}
実行結果
Ver: 1, PID: 100
const メンバや参照メンバは、言語仕様上「定義と同時に初期化されなければならない」ため、コンストラクタ本体での代入という選択肢自体が存在しません。
もしリストを使わずにコンパイルしようとすれば、コンパイラから「初期化されていません」というエラーを受け取ることになります。
これは、堅牢なクラス設計において避けては通れない必須のテクニックと言えるでしょう。
パフォーマンス面のメリット
初期化子リストを使用することは、実行効率の向上にも貢献します。
intやdoubleなどの組み込み型の場合、本体での代入では「未初期化状態(ゴミデータが入った状態)」から代入が行われますが、特に注目すべきは std::string や自作クラスなどの「クラス型」のメンバ変数です。
クラス型メンバでは、初期化子リストを使わない場合、まずデフォルトコンストラクタで構築され、その直後に代入演算子で値が上書きされるという不要な処理が増える可能性があります。
初期化子リストを使えば最初から目的の値で構築されるため、無駄な呼び出しを最小限に抑えることができ、大量のオブジェクトを生成するシステムにおいて無視できない差となります。
メンバ変数の初期化順番のルール
初期化リストに書く順番と、実際にメンバが初期化される順番が異なるという事実は、中級者でも見落としがちなC++の落とし穴です。
これを正しく把握していないと、あるメンバを使って別のメンバを初期化しようとした際に、意図しない値を参照してバグを引き起こす危険性があります。
ここでは、C++のコンストラクタにおいてメンバ変数が実際に初期化される順序の鉄則について解説します。
宣言の順序がすべてを決める
コンストラクタにおける初期化の順番は、「初期化リストに書いた順番」ではなく「クラス定義で宣言された順番」で決まります。
#include <iostream>
class SequenceTest {
private:
int first; // 先に宣言
int second; // 後に宣言
public:
// リストではsecondを先に書いてみるが...
SequenceTest(int val) : second(val * 2), first(second + 1) {
std::cout << "first: " << first << ", second: " << second << std::endl;
}
};
int main() {
SequenceTest test(10);
return 0;
}
実行結果
first: (不定な値), second: 20
※ first の値は未定義動作のため、実行環境や最適化によって結果は変わります。
このコードで起きている不可解な現象を詳しく説明します。
初期化子リストでは、見た目上 second を先に初期化し、その値を使って first を決めようとしています。
しかし、C++の仕様により、実際には宣言順である first が一番最初に初期化されます。
このとき、first の計算に使おうとしている second はまだ初期化されておらず、不定な値(ゴミデータ)が入っています。
未初期化の変数を利用することは「未定義動作(Undefined Behavior)」であり、プログラムがクラッシュしたり予期せぬ数値が入ったりする原因になります。
この致命的なバグを防ぐためには、メンバ変数の宣言順と、初期化子リストの記述順を必ず一致させることが推奨されます。
構造体や最新の「初期化子リストを記述しない」挙動
クラスだけでなく構造体でも同様の仕組みが使えます。
また、特別な設定を行わずにメンバを規定値で初期化したい場合に知っておくと便利な挙動についても解説します。
構造体(struct)での初期化子リスト利用
#include <iostream>
struct Point {
int x;
int y;
// 構造体でもクラスと同様に初期化子リストを使える
Point(int px, int py) : x(px), y(py) {}
};
int main() {
Point p(10, 20);
std::cout << "X: " << p.x << ", Y: " << p.y << std::endl;
return 0;
}
C++において、構造体(struct)はデフォルトのアクセス権限が public である点を除けば、機能的にはクラスとほぼ同じです。
そのため、単純なデータ構造(trivial や standard-layout な型)を扱う場合でも、このようにコンストラクタを定義し、初期化子リストを使って値をセットすることが可能です。
初期化子リストを記述しない場合の挙動
最近のC++開発では、クラス宣言時にメンバ変数に直接デフォルト値を設定する「メンバ変数のデフォルト初期化」も多用されます。
このとき、コンストラクタで初期化子リストを記述しないとどうなるでしょうか。
class Data {
public:
int value = 100; // デフォルトメンバ初期化
// 初期化子リストを記述しない場合、上記のデフォルト値が使われる
Data() {}
// 特定のコンストラクタではリストを使って値を上書きできる
Data(int v) : value(v) {}
};
この仕組みを詳しく紐解くと、Data() コンストラクタには初期化子リストがありませんが、クラス宣言時に value = 100 と書かれているため、自動的にその値が適用されます。
特定のケースのみリストを使って上書きし、共通の初期値は宣言時にまとめることで、コードの重複を避け、保守性の高いクラスを構築することができます。
C++のスキルを活かして年収を上げる方法
以上、C++におけるコンストラクタでのメンバ変数初期化方法について解説しました。
C++を扱えるエンジニアは希少価値が高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。
なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。
転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。
| 年収アップにこだわりたい方 (平均アップ額138万円の実績) | テックゴー |
| 未経験・経験者問わず幅広く探したい方 | ユニゾンキャリア |
| 業界に精通した担当者に相談したい方 | キッカケエージェント |
| ゲーム業界への転職を志望する方 | ファミキャリ |
| エンジニア未経験からキャリアを築く方 | イーチキャリア |



