エラーになるのは何故?(デフォルトコンストラクタの合成)

コード(1)

// main.cpp

struct A
{
    A (int a)
    {
    }
};

int main()
{
    A a;
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:12: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)

コード(2)

// main.cpp

struct A
{
    A (int a)
    {
    }
};

struct B
{
    A a;
};

int main()
{
    B b;
}

コンパイルエラー

$ g++ main.cpp
main.cpp: In constructor 'B::B()':
main.cpp:11: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)
main.cpp: In function 'int main()':
main.cpp:17: note: synthesized method 'B::B()' first required here

コード(3)

// main.cpp

struct A
{
    A (int a)
    {
    }
};

struct B
    : A
{
};

int main()
{
    B b;
}

コンパイルエラー

$ g++ main.cpp
main.cpp: In constructor 'B::B()':
main.cpp:12: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)
main.cpp: In function 'int main()':
main.cpp:17: note: synthesized method 'B::B()' first required here

答え

デフォルトコンストラクタがないから

解説

クラスにコンストラクタがない場合は、デフォルトコンストラクタがコンパイラによって合成される。しかし、以下のような場合にデフォルトコンストラクタは合成されない。

  • 明示的に定義されたデフォルトコンストラクタ以外のコンストラクタが 1 つ以上あるクラス ... コード(1)
  • デフォルトコンストラクタを持たないクラス型のメンバ変数を持つクラス ... コード(2)
  • デフォルトコンストラクタを持たないクラス型を継承するクラス ... コード(3)

デフォルトコンストラクタを持たないメリットはあまりない。そのため、空という意味を持つ特殊なオブジェクトを生成するためのデフォルトコンストラクタを別途宣言したりすることが多い。
デフォルトコンストラクタがないとできないことは、関連エントリを参照。

エラーになるのは何故?(デフォルトコンストラクタの呼び出し)

コード(1)

// main.cpp

struct A
{
    A(int a)
    {
    }
};

int main()
{
    A a;
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:12: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)

コード(2)

// main.cpp

struct A
{
    A(int a)
    {
    }
};

int main()
{
    A* a = new A[10];
    delete[] a;
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:12: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)

コード(3)

// main.cpp

struct A
{
    A(int a)
    {
    }
};

int main()
{
    A a[10];
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:12: error: no matching function for call to 'A::A()'
main.cpp:5: note: candidates are: A::A(int)
main.cpp:4: note:                 A::A(const A&)

コード(4)

// main.cpp

#include <vector>

struct A
{
    A(int a)
    {
    }
};

int main()
{
    std::vector<A> a(10);
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In constructor 'std::vector<_Tp, _Alloc>::vector(size_t, const _Tp&, const _Alloc&) [with _Tp = A, _Alloc = std::allocator<A>]':
main.cpp:14: error: no matching function for call to 'A::A()'
main.cpp:7: note: candidates are: A::A(int)
main.cpp:6: note:                 A::A(const A&)

答え

クラス A がデフォルトコンストラクタを持たないから

解説

クラスがデフォルトコンストラクタを持たない場合、以下のことができなくなる。

  • 変数の引数なしの初期化 ... コード(1)
  • 配列の動的割り付け ... コード(2)
  • 配列初期化子無しの、静的配列の定義 ... コード(3)
  • vector などの大きさを取るコンストラクタ .. コード(4)

コード (3) のような場合は、以下のように配列初期化子を用いることで静的配列の定義が可能である。

// main.cpp

struct A
{
    A(int a)
    {   
    }   
};

int main()
{
    A a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
}

表示される値は何?(コンストラクタ初期化子の順序とメンバ変数の定義順序)

コード

// main.cpp

#include <iostream>

struct A
{
    A() 
        : a(1), b(a)
    {   
        std::cout << b << std::endl;
    }   

    int b, a;
};

int main()
{
    A a;
}

答え

不定な int 型の値

解説

メンバ変数は定義順に初期化される。コンストラクタ初期化子の順序はまったく関係ない

非 const 参照の初期化子に右辺値式(一時オブジェクトを生成する式)を用いることはできる?

コード(1)

// main.cpp
#include <cmath>

int main()
{
    double &a = sqrt(2.0);
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:6: error: invalid initialization of non-const reference of type 'double&' from a temporary of type 'double'

コード(2)

// main.cpp
#include <iostream>

void sub(int &a)
{
    std::cout << a << std::endl;
}

int main()
{
    sub(1);
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:11: error: invalid initialization of non-const reference of type 'int&' from a temporary of type 'int'
main.cpp:4: error: in passing argument 1 of 'void sub(int&)'

答え

できない

解説

右辺値は一時的なオブジェクトであり、非 const 参照によって値を書き換えられる意味がない
もちろん、 const 参照は右辺値を参照することができる

long 型非 const 参照の初期化子に、 int 型の式を用いることはできる?

コード(1)

// main.cpp
int main()
{
    int a = 0;
    long &b = a;
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:5: error: invalid initialization of reference of type 'long int&' from expression of type 'int'

コード(2)

// main.cpp
#include <iostream>

void sub(long &a)
{
    std::cout << a << std::endl;
}

int main()
{
    int a = 0;
    sub(a);
}

コンパイルエラー

$ g++ main.cpp 
main.cpp: In function 'int main()':
main.cpp:12: error: invalid initialization of reference of type 'long int&' from expression of type 'int'
main.cpp:4: error: in passing argument 1 of 'void sub(long int&)'

答え

できない

解説

もし、非 const 参照の初期化子に違う型の式を用いることができたとしても、結局以下の問題に行き着く

  1. 型が違うので、暗黙の型変換が必要
  2. 暗黙の型変換は、右辺値(一時オブジェクト)を生成する
  3. 非 const 参照は、右辺値を参照することができない

const 参照は右辺値を参照することができるので、異なる型の式を初期化子に用いることができる