💉

「効果的」な変数の初期化

2024/12/14に公開

C++では変数を様々な方法で初期化できます。まずはlong型の変数にint型の変数に入らないぐらい大きい数字を代入しましょう。

long big_number(2147483648);

次はbig_numberint型の変数を初期化したいと思います。

以下のやり方があります。

// 代入で初期化(copy initialization)
int small_number1 = big_number; // 警告は出るがコンパイルエラーにならない

// 丸括弧で初期化(direct initialization)
int small_number2(big_number); // 警告は出るがコンパイルエラーにならない

// C++11 から追加された中括弧で初期化(uniform/brace initialization)
int small_number3{big_number}; // !!ちゃんとコンパイルエラーになる!!

中括弧で初期化すると、型の異なる代入で初期化をしようとすると(例えばlong型変数をintで、double型変数をfloatで、など)、ちゃんと縮小変換のエラーになります。[1]

中括弧での初期化には様々な名前があって、中括弧(brace)を使うから一般的にbrace initialization(中各個での初期化)とも呼びますが、殆どの型を同じ書き方で初期化できるということでuniform initializationとも呼びます。
基本型の場合は、基本中括弧の方がお勧めです。

構造体とクラスも中括弧で初期化出来ますが、色々注意しないといけないことがあります。
構造体とクラスの場合は、丸括弧で初期化すると、指定する引数に合うコンストラクタがコールされるだけですが、中括弧の場合は、もう少し複雑です。

デフォルトコンストラクタのみ存在する構造体とクラスの場合は、中括弧で初期化すると、メンバー変数の初期値を定義の順番で指定できます。これをaggregate initializationと呼んでいて、デフォルトコンストラクタの1つです。

struct Test
{
    int x;
    int y;
    int z;
};

Test test1(2, 3); // 適切なコンストラクタがないせいでエラーになる
Test test2{2, 3}; // x = 2, y = 3 で初期化されて、z がデフォルト値で初期化される

しかし、カスタムのコンストラクタを1つでも定義すると、以下のように丸括弧でも中括弧でもそのコンストラクタしかコールできなくなります。

struct Test
{
    Test(int _x, int _y, int _z)
    : x(_x)
    , y(_y)
    , z(_z)
    {}
    
    int x;
    int y;
    int z;
};

Test test1(2, 3); // 適切なコンストラクタがないせいでエラーになる
Test test2{2, 3}; // 適切なコンストラクタがないせいでエラーになる

Test test3(1, 2, 3); // コンストラクタがコールされる
Test test4{1, 2, 3}; // コンストラクタがコールされる

これがあるから、構造体の場合は、基本コンストラクタを定義しない方が使いやすいです。

クラスの場合は基本カスタムのコンストラクタを定義するものなので、初期化は丸括弧でも中括弧でもいいです。

ただし、クラスでも構造体でも、以下のようなstd::initializer_list<T>を使うコンストラクタが定義されている場合は、中括弧での初期化の挙動が変わります。

MyClass(std::initializer_list<T>);

標準ライブラリのコンテナのクラスには大体そういうコンストラクタが定義されています。

std::vectorで試してみましょう。

std::vector<int> data1(5, 2); // data1: {2, 2, 2, 2, 2}
std::vector<int> data2{5, 2}; // data2: {5, 2}

全然違う結果になりますね。なんでこうなるかを見てみましょう。

丸括弧の場合は要素数と初期化する要素が指定できるコンストラクタがコールされます。

vector( size_type count, const T& value,
        const Allocator& alloc = Allocator() );

中括弧の場合は、以下のstd::initializer_list<T>を使うコンストラクタがコールされます。

vector( std::initializer_list<T> init,
        const Allocator& alloc = Allocator() );

なので、基本型の場合は、中括弧での初期化の方が安全でお勧めですが、構造体やクラスの場合は、ケースバイケースになります。


|cpp記事一覧へのリンク|

脚注
  1. 使用しているコンパイラのオプションに依存してエラーになるかどうかが変わる可能性があります。gccとclangなら-Wnarrowingというオプションで警告が出るようにして、-pedantic-errorsというオプションで警告がエラーになるようにできます。clangの場合は、デフォルトでエラーになるようになっているはずです。 ↩︎

Discussion