C++の初期化とは、変数やオブジェクトを作成するときに、最初の値や状態を決めることです。
プログラムでは、数値・文字列・配列・クラスのオブジェクトなど、さまざまなデータを扱います。
これらを作った直後にどのような値を持たせるかを決めるのが初期化です。
C++では、初期化の方法が複数あります。書き方によって意味が変わる場合もあるため、単に「値を入れる」と考えるだけでは不十分です。
特に、初期化と代入の違い、ローカル変数の未初期化、波括弧を使った初期化、クラスのメンバ初期化リストなどは、C++を学ぶうえで重要なポイントです。
初期化と代入の違い
初期化は作るときに値を決めること
初期化は、変数やオブジェクトを作成するタイミングで値を設定することです。
たとえば、整数型の変数を作るときに最初から値を与える場合、それは初期化です。
文字列オブジェクトを作るときに、最初から文字列を持たせる場合も初期化です。
C++では、見た目に等号が使われていても、宣言と同時に値を与えている場合は代入ではなく初期化として扱われます。
この点は初心者が混同しやすいところです。
等号を見ると「代入」と考えたくなりますが、C++では「変数を作ると同時に値を与えているか」「すでにある変数に後から値を入れているか」で意味が変わります。
代入は作ったあとに値を入れること
代入は、すでに存在している変数やオブジェクトに、あとから値を入れることです。
初期化では、変数やオブジェクトが作られる時点で値が決まります。
一方、代入では、まず変数やオブジェクトが作られ、その後に値が入ります。
基本型では違いが見えにくいこともありますが、クラス型ではこの差が重要です。
たとえば文字列オブジェクトの場合、初期化では最初から目的の文字列を持つ状態で作られます。
一方、代入では、いったん空の文字列オブジェクトなどとして作られ、その後に別の値が入ります。
そのため、C++では「初期化」と「代入」は別物として理解する必要があります。
C++の主な初期化方法
コピー初期化
コピー初期化は、等号を使って値を与える初期化方法です。
名前に「コピー」と付いていますが、必ずしも実際にコピー処理が発生するわけではありません。
現代のC++では、コンパイラの最適化や仕様上の扱いによって、不要なコピーが省かれることもあります。
コピー初期化は見た目が分かりやすいため、初心者にも理解しやすい書き方です。
ただし、等号を使っていても、宣言と同時に値を与えている場合は代入ではなく初期化です。
直接初期化
直接初期化は、丸括弧を使う初期化方法です。
クラス型では、コンストラクタを直接呼び出すようなイメージで使われます。
オブジェクトを作るときに、必要な値を直接渡して初期化する書き方です。
ただし、丸括弧を使う初期化には注意点があります。
空のオブジェクトを作ろうとして、型名と変数名のあとに空の丸括弧を書くと、オブジェクトの生成ではなく関数宣言として解釈されることがあります。
これはC++で有名な落とし穴です。
空のオブジェクトを作る場合は、丸括弧を空にして書くよりも、何も付けずに宣言するか、波括弧を使った初期化を使う方が分かりやすい場合があります。
リスト初期化
リスト初期化は、波括弧を使う初期化方法です。
C++11以降でよく使われる書き方で、現代的なC++では基本型やクラス型の初期化に広く使われます。
リスト初期化の大きな特徴は、危険な型変換を防ぎやすいことです。
たとえば、小数を整数に変換すると小数部分が失われます。
このような値の一部が失われる可能性のある変換を縮小変換と呼びます。
リスト初期化では、縮小変換が禁止されるため、意図しない値の変化を防ぎやすくなります。
安全性を重視する場合、波括弧を使った初期化は有効です。
ただし、波括弧は万能ではありません。
特に標準ライブラリのコンテナでは、丸括弧を使った場合と波括弧を使った場合で意味が変わることがあります。
そのため、型ごとの挙動を理解して使う必要があります。
コピーリスト初期化
コピーリスト初期化は、等号と波括弧を組み合わせた初期化方法です。
文法としては、コピー初期化とリスト初期化を合わせたような書き方です。
ただし、通常のコードでは、波括弧だけを使ったリスト初期化の方がシンプルに書けるため、コピーリスト初期化を積極的に使う場面はそれほど多くありません。
初心者の段階では、「こういう書き方もある」と理解しておく程度で十分です。
値初期化
値初期化は、型に応じた初期値で初期化する方法です。
基本型の場合、値初期化を行うとゼロ相当の値になります。
整数ならゼロ、浮動小数点数ならゼロ、小数点以下を含むゼロ、真偽値なら偽、文字型ならヌル文字になります。
クラス型の場合は、デフォルトコンストラクタが呼び出されます。
たとえば文字列型であれば、空文字列として初期化されます。
値初期化は、未初期化の変数を避けるために非常に便利です。
関数内で基本型のローカル変数を作る場合、初期化しないと不定値になります。
値初期化を使えば、安全な初期値を与えられます。
デフォルト初期化
デフォルト初期化とは
デフォルト初期化とは、明示的な初期値を与えずに変数やオブジェクトを作ることです。
ただし、デフォルト初期化の挙動は、基本型とクラス型で大きく異なります。
基本型のデフォルト初期化
関数内で整数や小数、真偽値などの基本型を初期値なしで宣言すると、その値は不定になります。
不定値とは、どのような値が入っているか分からない状態です。
その値を読み取ると、予期しない動作やバグの原因になります。
C++では、ローカルの基本型変数を初期化しないまま使うことは避けるべきです。
特に初心者のうちは、基本型の変数は必ず宣言時に初期化する習慣をつけると安全です。
クラス型のデフォルト初期化
クラス型の場合、デフォルト初期化ではデフォルトコンストラクタが呼び出されます。
たとえば文字列型であれば、初期値を明示しなくても空文字列として作られます。
標準ライブラリの多くのクラスは、初期値なしで作っても安全な状態になるように設計されています。
ただし、自作クラスの場合は、コンストラクタやメンバの初期化方法によって状態が変わります。
クラスを設計するときは、初期化されていないメンバが残らないように注意する必要があります。
ローカル変数・グローバル変数・static変数の初期化
ローカル変数の初期化
関数の中で宣言した変数をローカル変数といいます。
ローカル変数のうち、整数や小数などの基本型は、明示的に初期化しないと不定値になります。
そのまま使うと、思わぬ不具合につながります。
そのため、ローカルの基本型変数は、必要な初期値を明示して作るのが基本です。
値がまだ決まっていない場合でも、ゼロや偽などの安全な初期値を与えておくと安心です。
グローバル変数の初期化
関数の外で宣言した変数をグローバル変数といいます。
グローバル変数は、明示的に初期化しない場合でもゼロ初期化されます。
整数であればゼロ、ポインタであればヌルポインタ値になります。
ただし、グローバル変数はプログラム全体から参照できるため、使いすぎるとコードの見通しが悪くなります。
初期化の観点ではゼロ初期化されますが、設計上は必要最小限にするのが望ましいです。
static変数の初期化
static変数も、明示的に初期化しない場合はゼロ初期化されます。
関数内のstatic変数は、通常のローカル変数と違い、関数の呼び出しが終わっても値を保持します。
最初に使われるときに初期化され、その後は前回の値を引き継ぎます。
カウンタのように、関数呼び出しをまたいで状態を保持したい場合に使われます。
ただし、状態を持つことで処理が複雑になることもあるため、使いどころには注意が必要です。
ゼロ初期化
ゼロ初期化とは
ゼロ初期化とは、オブジェクトをゼロ相当の値で初期化することです。
整数ならゼロ、浮動小数点数ならゼロ、小数点以下を含むゼロ、真偽値なら偽、文字型ならヌル文字になります。
ポインタの場合は、ヌルポインタ値になります。
ゼロ初期化は、変数を安全な状態にしておくために役立ちます。
特に、初期値として特別な値が必要ない場合は、ゼロ相当の値にしておくと扱いやすくなります。
ポインタのゼロ初期化
ポインタをゼロ初期化すると、ヌルポインタ値になります。
ただし、ポインタが何も指していないことを明確に示したい場合は、ヌルポインタを表す専用のリテラルを使うと分かりやすくなります。
古いC++コードでは、ゼロやNULLを使ってポインタを初期化している場合があります。
しかし、現代C++では、ヌルポインタを表すにはnullptrを使うのが基本です。
クラスの初期化
クラスはコンストラクタで初期化される
C++では、クラスのオブジェクトを作るときにコンストラクタが呼び出されます。
コンストラクタは、オブジェクトが作られるタイミングで初期状態を整えるための特別な関数です。
メンバ変数に値を設定したり、必要な準備をしたりする役割を持ちます。
クラスを使う場合、初期化は単に値を入れるだけではありません。
オブジェクトが正しい状態で使えるように、コンストラクタで必要な設定を行います。
コンストラクタ本体での代入
コンストラクタの本体で、メンバ変数に値を入れることもできます。
ただし、この場合、厳密にはメンバを初期化しているのではなく、いったんメンバが作られたあとに代入していることになります。
基本型では大きな違いを感じにくい場合もありますが、文字列や配列、独自クラスなどでは、初期化と代入の違いが効率や正しさに関係することがあります。
そのため、クラスのメンバを設定する場合は、コンストラクタ本体で代入するよりも、メンバ初期化リストを使うのが基本です。
メンバ初期化リスト
メンバ初期化リストとは
メンバ初期化リストとは、コンストラクタでメンバ変数を初期化するための書き方です。
コンストラクタ名のあとに、どのメンバをどの値で初期化するかを指定します。
これにより、メンバ変数はオブジェクトが作られるタイミングで直接初期化されます。
C++でクラスを作る場合、メンバ変数の初期化にはメンバ初期化リストを使うのが基本です。
メンバ初期化リストを使う理由
メンバ初期化リストを使う理由は、メンバを作る時点で正しい値にできるからです。
コンストラクタ本体で代入すると、メンバはいったんデフォルト初期化され、その後で値が代入されます。
一方、メンバ初期化リストを使うと、最初から目的の値でメンバを作ることができます。
これは、文字列やコンテナ、自作クラスなどのクラス型メンバで特に重要です。
余計な初期化や代入を避けられるため、効率面でも有利になりやすいです。
constメンバはメンバ初期化リストで初期化する
constメンバは、あとから値を変更できません。
そのため、コンストラクタ本体で代入することはできません。
constメンバは、オブジェクトが作られる時点で値を決める必要があります。
そのため、必ずメンバ初期化リストで初期化します。
これはC++のクラス設計で非常に重要です。
constメンバを持つクラスでは、メンバ初期化リストを使わないと正しく初期化できません。
参照メンバもメンバ初期化リストで初期化する
参照メンバも、メンバ初期化リストで初期化する必要があります。
参照は、一度何かを参照すると、あとから別の対象を参照し直すことができません。
そのため、参照メンバもオブジェクトが作られるタイミングで、何を参照するかを決める必要があります。
コンストラクタ本体で参照メンバに値を入れようとしても、それは参照先を設定しているのではなく、参照先の値に代入しようとしていることになります。
メンバの初期化順序
初期化順序は宣言順で決まる
メンバ初期化リストでは、初期化の順番に注意が必要です。
C++では、メンバの初期化順序は、メンバ初期化リストに書いた順番ではなく、クラス内でメンバが宣言された順番で決まります。
つまり、初期化リストで後ろに書いたメンバが先に初期化されることはありません。
クラス定義の中で先に宣言されているメンバから順に初期化されます。
初期化リストは宣言順に合わせて書く
メンバの初期化順序による混乱を避けるため、メンバ初期化リストはクラス内の宣言順に合わせて書くのが基本です。
特に、あるメンバの初期化に別のメンバの値を使う場合は注意が必要です。
まだ初期化されていないメンバの値を使ってしまうと、不定値を使うことになり、危険です。
クラス設計では、メンバ同士の依存関係を意識し、宣言順と初期化順を分かりやすくそろえることが大切です。
クラス内メンバ初期化
クラス内でデフォルト値を設定できる
C++11以降では、クラス定義の中でメンバ変数に初期値を設定できます。
これを使うと、メンバに標準的なデフォルト値を持たせることができます。
たとえば、名前の初期値を「未設定」にしたり、年齢をゼロにしたり、有効状態を偽にしたりできます。
クラス内メンバ初期化を使うと、コンストラクタを書かない場合でも、メンバが安全な値で初期化されやすくなります。
コンストラクタの初期化が優先される
クラス内メンバ初期化でデフォルト値を設定していても、コンストラクタのメンバ初期化リストで別の値を指定した場合は、そちらが優先されます。
つまり、クラス内メンバ初期化は「標準の初期値」として働きます。
特別な値を指定したい場合は、コンストラクタ側で上書きするイメージです。
この仕組みを使うと、初期化漏れを防ぎながら、必要に応じて柔軟に値を設定できます。
配列の初期化
配列の基本的な初期化
配列は、複数の要素をまとめて持つデータ構造です。
C++では、配列を作るときに各要素の初期値を指定できます。
要素数を指定して初期値を並べることもできますし、初期値の数から要素数を推論させることもできます。
配列を初期化するときは、どの要素にどの値が入るかを意識することが大切です。
配列の一部だけを初期化した場合
配列の一部だけに初期値を与えた場合、残りの要素はゼロ初期化されます。
たとえば、5個の要素を持つ整数配列に対して、最初の2つだけ初期値を与えた場合、残りの3つはゼロになります。
これは、配列を安全に初期化するうえで便利な仕組みです。
配列をすべてゼロで初期化する
配列のすべての要素をゼロで初期化することもできます。
初期値を空の形で指定すると、配列の各要素が値初期化されます。
整数配列であれば、すべての要素がゼロになります。
配列を使うときは、未初期化の要素が残らないように注意することが重要です。
std::arrayの初期化
std::arrayとは
std::arrayは、固定長配列を扱うための標準ライブラリです。
生の配列と同じように要素数が固定されていますが、標準ライブラリの機能と組み合わせやすく、サイズの取得や範囲ベースの処理なども扱いやすいという特徴があります。
現代C++では、生の配列よりもstd::arrayを使う方が安全で分かりやすい場面が多くあります。
std::arrayのゼロ初期化
std::arrayも、要素をまとめて初期化できます。
すべての要素をゼロにしたい場合は、値初期化を使うことで各要素をゼロ相当の値にできます。
固定長のデータを扱う場合、std::arrayは便利な選択肢です。
std::vectorの初期化
std::vectorとは
std::vectorは、要素数をあとから変更できる可変長配列です。
C++で複数のデータを扱う場合、非常によく使われる標準コンテナです。
必要に応じて要素を追加したり削除したりできるため、生の配列よりも柔軟です。
std::vectorも、作成時に初期値を指定できます。
要素そのものを指定して初期化する
std::vectorでは、要素として持たせたい値を並べて初期化できます。
たとえば、複数の整数を最初から持つvectorを作ることができます。
この場合、並べた値がそのままvectorの要素になります。
要素数を指定して初期化する
std::vectorでは、要素数を指定して初期化することもできます。
この場合、指定した数だけ要素を持つvectorが作られます。
整数型の要素であれば、それぞれの要素はゼロで初期化されます。
要素数と初期値を指定する
std::vectorでは、要素数と初期値を指定して、同じ値を複数個持つvectorを作ることもできます。
たとえば、特定の値を5個持つvectorのような作り方ができます。
初期状態で同じ値を並べたい場合に便利です。
std::vectorは丸括弧と波括弧で意味が変わる
std::vectorの初期化では、丸括弧と波括弧で意味が変わる点に注意が必要です。
丸括弧を使う場合は、要素数や要素数と初期値を指定する意味になります。
一方、波括弧を使う場合は、基本的に中に並べた値そのものが要素になります。
たとえば、「3個の要素を作り、それぞれを10にする」という意味と、「3と10という2つの要素を持たせる」という意味はまったく違います。
std::vectorでは、この違いが非常に重要です。
C++の初期化でよくつまずくポイントなので、必ず押さえておきましょう。
構造体の初期化
構造体の基本的な初期化
構造体も、作成時にメンバを初期化できます。
構造体のメンバが順番に並んでいる場合、初期化時に与えた値は、その宣言順に対応します。
たとえば、座標を表す構造体であれば、最初の値が横方向、次の値が縦方向のように対応します。
このように、構造体のメンバを宣言順に初期化する仕組みを集成体初期化と呼びます。
構造体のメンバにデフォルト値を設定する
構造体のメンバにも、クラスと同じようにデフォルト値を設定できます。
メンバにデフォルト値を設定しておくと、構造体を空の状態で初期化した場合でも、安全な値を持たせられます。
特に、構造体のメンバが増えてくると、初期化漏れが起きやすくなります。
デフォルト値を設定しておくことで、そのようなミスを防ぎやすくなります。
C++20の指定初期化
メンバ名を指定して初期化できる
C++20以降では、構造体などのメンバ名を指定して初期化できます。
この方法を使うと、どの値がどのメンバに入るのかが明確になります。
メンバが多い構造体では、可読性が高くなります。
たとえば、ユーザー情報を表す構造体で、名前・年齢・有効状態などを持つ場合、メンバ名を指定して初期化すれば、値の意味が分かりやすくなります。
指定初期化では宣言順に注意する
C++の指定初期化では、基本的にメンバの宣言順に書く必要があります。
これはC言語の指定初期化と異なる点です。
C++では、メンバの順番を入れ替えて指定するとエラーになる場合があります。
そのため、指定初期化を使う場合も、構造体のメンバの宣言順を意識して書く必要があります。
ポインタの初期化
ポインタは必ず初期化する
ポインタは、メモリ上のどこかを指すための変数です。
ポインタを初期化しないままにすると、どこを指しているか分からない状態になります。
このようなポインタを使うと、プログラムが異常終了したり、予期しないメモリを書き換えたりする原因になります。
そのため、ポインタは必ず初期化しましょう。
何も指していない場合はnullptrを使う
ポインタが何も指していないことを表したい場合は、nullptrを使います。
nullptrは、現代C++でヌルポインタを表すための標準的な方法です。
古いコードではNULLやゼロが使われることもありますが、新しく書くコードではnullptrを使うのが基本です。
nullptrを使うことで、「このポインタは何も指していない」という意図が明確になります。
動的メモリ確保時の初期化
newを使う場合も初期化方法で挙動が変わる
C++では、newを使って動的にオブジェクトを作ることがあります。
この場合も、初期化の書き方によって挙動が変わります。
基本型を初期化なしで動的確保すると、その値は不定になります。
一方、値初期化を行うと、整数であればゼロになります。
つまり、動的メモリ確保でも「初期化されるかどうか」を意識する必要があります。
現代C++では生のnewを避けることが多い
現代C++では、生のnewやdeleteを直接使う場面は少なくなっています。
多くの場合、標準コンテナやスマートポインタを使う方が安全です。
動的に単一のオブジェクトを管理したい場合はスマートポインタ、複数の要素を扱いたい場合はstd::vectorなどを使うのが一般的です。
生のnewやdeleteを使うと、解放忘れや二重解放などの問題が起こりやすくなります。
そのため、特別な理由がなければ、標準ライブラリの仕組みを使う方が安全です。
autoと初期化
autoは初期値から型を推論する
autoを使うと、初期値から型が自動的に推論されます。
整数を与えれば整数型、小数を与えれば浮動小数点型、真偽値を与えれば真偽値型として推論されます。
autoは便利ですが、推論される型が自分の意図と一致しているかを意識する必要があります。
文字列リテラルとautoの注意点
文字列リテラルをautoで受けると、多くの場合、標準文字列型ではなく、文字列リテラルを指すポインタとして推論されます。
そのため、標準文字列型として扱いたい場合は、明示的に標準文字列型を使うか、文字列リテラル用のサフィックスを使う必要があります。
この点は、autoを使い始めたときに混乱しやすいポイントです。
autoと波括弧初期化の注意点
autoと波括弧を組み合わせると、型推論が分かりにくくなることがあります。
特に、等号と波括弧を組み合わせた場合、initializer_listとして推論されることがあります。
一方、C++17以降では、単一要素の直接リスト初期化では通常その要素の型として推論されます。
このように、autoと波括弧を組み合わせる場合は、C++のバージョンや書き方によって推論結果が変わることがあります。
初心者のうちは、autoを使う場合でも、型が分かりやすい書き方を心がけるとよいでしょう。
std::initializer_listと初期化
波括弧ではinitializer_listコンストラクタが優先されることがある
C++では、波括弧を使った初期化を行うと、std::initializer_listを受け取るコンストラクタが優先的に選ばれることがあります。
そのため、丸括弧を使った場合と波括弧を使った場合で、呼び出されるコンストラクタが変わることがあります。
この仕組みは、C++の初期化が分かりにくく感じられる理由の一つです。
std::vectorの初期化にも関係する
std::initializer_listの影響は、std::vectorの初期化でも重要です。
std::vectorでは、丸括弧を使うと「要素数」や「要素数と初期値」を指定する意味になります。
一方、波括弧を使うと「要素そのもの」を並べる意味になります。
この違いは、initializer_listを受け取るコンストラクタが関係しています。
波括弧による初期化は便利ですが、標準コンテナでは意味が変わる場合があるため注意が必要です。
const変数の初期化
const変数は宣言時に初期化する
const変数は、一度値を決めたら変更できない変数です。
そのため、基本的には宣言時に初期化する必要があります。
関数内で初期値なしのconst変数を宣言することはできません。
constは「あとから値を変えない」ことを保証する仕組みです。
そのため、作る時点で値が決まっていなければなりません。
クラスのconstメンバも初期化が必要
クラスのconstメンバも、オブジェクトを作る時点で初期化する必要があります。
コンストラクタ本体で値を代入しようとしても、constメンバには代入できません。
そのため、メンバ初期化リストを使って初期化します。
constメンバを持つクラスを書く場合は、メンバ初期化リストが必須になると考えてよいでしょう。
参照の初期化
参照は宣言時に初期化する
参照は、既存の変数に別名を付ける仕組みです。
参照は、宣言した時点で必ず何かを参照している必要があります。
あとから参照先を設定することはできません。
そのため、参照変数は宣言時に初期化する必要があります。
参照はあとから参照先を変えられない
参照は、一度ある変数を参照すると、別の変数を参照し直すことはできません。
参照に対して別の値を代入した場合、それは参照先を変えているのではなく、参照している元の変数に値を代入していることになります。
この点はポインタとの大きな違いです。
ポインタは別の場所を指すように変更できますが、参照は参照先を変更できません。
C++の初期化でよくある注意点
ローカル基本型の未初期化に注意する
C++で特に注意したいのが、ローカル基本型の未初期化です。
関数内で整数や小数などの基本型変数を初期化しないまま宣言すると、その値は不定になります。
そのまま読み取ると、バグや予期しない動作の原因になります。
基本型のローカル変数は、必ず初期値を与える習慣をつけましょう。
波括弧は便利だが万能ではない
波括弧による初期化は、縮小変換を防ぎやすく、安全なコードを書くのに役立ちます。
しかし、std::vectorのように、丸括弧と波括弧で意味が変わる型もあります。
そのため、「常に波括弧を使えばよい」と単純に考えるのではなく、使う型のコンストラクタや初期化の意味を理解することが大切です。
空の丸括弧は関数宣言になることがある
C++では、空のオブジェクトを作ろうとして空の丸括弧を付けると、関数宣言として解釈されることがあります。
これは「most vexing parse」と呼ばれる有名な落とし穴です。
空のオブジェクトを作りたい場合は、空の丸括弧を使わず、通常の宣言や波括弧による初期化を使うと分かりやすくなります。
C++の初期化方法の整理
基本的な初期化方法
C++には、主に次のような初期化方法があります。
コピー初期化は、等号を使って値を与える方法です。
直接初期化は、丸括弧を使って値を渡す方法です。
リスト初期化は、波括弧を使う方法です。値初期化は、型に応じたゼロ相当の値やデフォルト状態にする方法です。
それぞれ似ているように見えますが、型や文脈によって挙動が変わる場合があります。
クラスやコンテナの初期化
クラスでは、コンストラクタが初期化に関係します。
特に、メンバ変数の初期化にはメンバ初期化リストを使うのが基本です。
標準コンテナでは、初期化方法によって意味が変わることがあります。
特にstd::vectorでは、丸括弧と波括弧の違いに注意が必要です。
固定長配列にはstd::array、可変長配列にはstd::vectorを使うと、安全で扱いやすいコードを書きやすくなります。
実務でおすすめの初期化方針
基本型は明示的に初期化する
整数、小数、真偽値などの基本型は、明示的に初期化するのがおすすめです。
特にローカル変数では、初期化しないと不定値になります。
安全なコードを書くためには、変数を作るときに初期値を与える習慣をつけましょう。
値がまだ決まっていない場合でも、ゼロや偽などの安全な値で初期化しておくと、バグを防ぎやすくなります。
クラスのメンバはメンバ初期化リストで初期化する
クラスのメンバ変数は、コンストラクタ本体で代入するのではなく、メンバ初期化リストで初期化するのが基本です。
特に、constメンバや参照メンバは、メンバ初期化リストでなければ初期化できません。
また、文字列やコンテナなどのクラス型メンバでは、メンバ初期化リストを使うことで効率的に初期化しやすくなります。
デフォルト値はクラス内メンバ初期化を使う
クラスのメンバに標準的な初期値を持たせたい場合は、クラス内メンバ初期化を使うと分かりやすくなります。
これにより、コンストラクタを書かない場合でも、メンバが安全な値になります。
初期化漏れを防ぐうえでも有効です。
ポインタはnullptrで初期化する
ポインタを使う場合は、未初期化のままにせず、nullptrで初期化するのが基本です。
ただし、現代C++では、可能であれば生ポインタではなく、スマートポインタや標準コンテナを使う方が安全です。
メモリ管理を自分で行うコードはミスが起きやすいため、標準ライブラリを活用することが重要です。
波括弧を活用しつつ型ごとの違いに注意する
波括弧による初期化は、縮小変換を防げるため安全性が高い書き方です。
ただし、std::vectorのように丸括弧と波括弧で意味が変わるものもあります。
そのため、基本的には波括弧を活用しつつ、コンテナやクラスの初期化では、その型がどのようなコンストラクタを持っているかを意識しましょう。
C++の初期化で初心者が覚えるべきポイント
ローカルの基本型は未初期化にしない
関数内で基本型の変数を初期化しないと、不定値になります。
これはC++でよくあるバグの原因です。
整数・小数・真偽値などのローカル変数は、必ず初期化してから使いましょう。
等号があっても代入とは限らない
宣言と同時に値を与えている場合、等号があっても代入ではなく初期化です。
代入は、すでに存在している変数やオブジェクトに後から値を入れることです。
初期化と代入は別物として理解しましょう。
クラスではメンバ初期化リストを使う
クラスのメンバ変数は、メンバ初期化リストで初期化するのが基本です。
特に、constメンバや参照メンバを持つクラスでは、メンバ初期化リストが必要になります。
std::vectorは丸括弧と波括弧の違いに注意する
std::vectorでは、丸括弧を使う場合と波括弧を使う場合で意味が変わります。
丸括弧では要素数や初期値を指定し、波括弧では要素そのものを並べる意味になることがあります。
この違いは実務でもよく出てくるため、必ず覚えておきましょう。
空の丸括弧による宣言に注意する
C++では、空の丸括弧を使うとオブジェクト生成ではなく関数宣言として解釈されることがあります。
空のオブジェクトを作りたい場合は、通常の宣言や波括弧による初期化を使う方が安全です。
まとめ
C++の初期化は、変数やオブジェクトを作るときに最初の値や状態を決める仕組みです。
初期化には、コピー初期化、直接初期化、リスト初期化、コピーリスト初期化、値初期化など、複数の方法があります。
それぞれの書き方は似ていますが、型や文脈によって挙動が変わることがあります。
特に重要なのは、初期化と代入を区別することです。
宣言と同時に値を与える場合は初期化であり、すでに存在している変数にあとから値を入れる場合は代入です。
また、関数内の基本型ローカル変数は、初期化しないと不定値になります。
安全なコードを書くためには、基本型の変数は必ず初期化してから使うことが大切です。
クラスでは、メンバ変数をメンバ初期化リストで初期化するのが基本です。
constメンバや参照メンバは、メンバ初期化リストでなければ初期化できません。
波括弧によるリスト初期化は、縮小変換を防ぎやすく便利です。
ただし、std::vectorのように丸括弧と波括弧で意味が変わる型もあるため、使い分けには注意が必要です。
C++の初期化は細かいルールが多く、最初は複雑に感じるかもしれません。
しかし、まずは次のポイントを押さえると理解しやすくなります。
- ローカルの基本型は未初期化にしないこと。
- 等号があっても宣言時なら代入ではなく初期化であること。
- クラスのメンバはメンバ初期化リストで初期化すること。
- std::vectorでは丸括弧と波括弧の違いに注意すること。
これらを意識すれば、C++の初期化で起こりやすいミスを減らし、安全で読みやすいコードを書きやすくなります。
以上、C++の初期化についてでした。
最後までお読みいただき、ありがとうございました。
