【C++】変換コンストラクタとは【詳細解説と処理コスト】


一般的に、実引数を一つだけとるようなコンストラクタは変換コンストラクタ(converting constructor)と呼ばれ、暗黙的なコンストラクタ呼び出しが行える。

struct A {
  int a;
  A(int v) : a(v) {} // 変換コンストラクタ
};

A a = 9; // 暗黙的なコンストラクタ呼び出し
A a(9);  // 明示的なコンストラクタ呼び出し

// ↑ いずれも`A(int v)`コンストラクタが呼ばれる

このように、暗黙の型変換を実現することが出来る。

スポンサーリンク

様々な場面での利用

この暗黙の変換コンストラクタ呼び出しは、戻り値を返す際や関数呼出し時の実引数内でも行われる。

A fn(A a) {
  return 8; // 変換コンストラクタが呼ばれる
}

fn(9); // 変換コンストラクタが呼ばれる

printf("%d", static_cast<A>(7).a); // 呼ばれる

デフォルト引数との組み合わせ

なお、引数が二つ以上のコンストラクタであっても、二つ目以降の仮引数に対してデフォルト引数を指定することで、変換コンストラクタの暗黙的な呼び出しを行わせることができる。

struct A {
  int a;
  A(int v, int w = 1) : a(v * w) {}
};

A a = 9; // OK

当然、デフォルト引数を利用する際にはコンストラクタの重複定義に注意する必要がある。

struct A {
  A(int v) {} // 余計な定義になってしまう
  A(int v, int w = 1) {}
};

A a = 9; // エラー:Conversion from 'int' to 'A' is ambiguous

複数の引数を取る変換コンストラクタ

複数の引数を取る変換コンストラクタは、{}による初期化式を用いて呼び出す事ができる。

struct A {
  A(int v)               { puts("1"); }
  A(int v, int w)        { puts("2"); }
  A(int v, int w, int x) { puts("3"); }
};

A a = 1;         // "1"
A b = {1, 2};    // "2"
A c = {1, 2, 3}; // "3"

初期化子リスト(initializer_list)を受け取るコンストラクタを宣言した場合、{}による初期化式はinitializer_listを受け取るコンストラクタを優先的に呼び出すようになる。

struct A {
  A(int v)               { puts("1"); }
  A(int v, int w)        { puts("2"); }
  A(int v, int w, int x) { puts("3"); }
  A(std::initializer_list<int> list) { puts("4"); }
};

A x = {1};       // "4"
A x{1};          // "4"
A x = {1};       // "4"
A x = {1, 2};    // "4"
A x = {1, 2, 3}; // "4"

この場合、複数の引数を受け取るコンストラクタは、丸括弧を用いて明示的に呼び出す必要がある。

A x = 1;      // "1"
A x(1);       // "1"
A x(1, 2);    // "2"
A x(1, 2, 3); // "3"
// A x(1, 2, 3, 4); // No matching constructor for initialization of 'A'

変換コンストラクタを無効にする方法

変換コンストラクタの暗黙的な呼び出しを禁止するためにはexplicitキーワードを利用する。

struct A {
  explicit A(int v) {}
};

A a = 9;    // NO: No viable conversion from 'int' to 'A'
A d = {9};  // NO: Chosen constructor is explicit in copy-initialization
A b(9);     // OK
A c{9};     // OK
A d = A(9); // OK

変換コンストラクタと処理コスト

暗黙的なコンストラクタ呼び出しによる初期化方法は一見すると=演算子によってコピー代入や暗黙的なコピーコンストラクタ呼び出しが行われてしまうようにも思えるが、実際には初期化のみが行われる。つまり暗黙的/明示的いずれの記法も内部的には同じ初期化処理が行われ、処理コストは変わらない。

この場合のA a = 9;という式は、変換後のオブジェクトを代入する式であると勘違いされやすいが、実際は単なる初期化式として扱われる。

ただし、初期化後の変数に対して再代入を行う際には、変換コンストラクタの呼び出しとコピー代入処理が同時に働き、余計な処理コストを産んでしまうことになるため引き続き注意したい。

A a;   // デフォルトコンストラクタ呼び出し
a = 9; // 変換コンストラクタ & コピー代入

A b = 9; // 変換コンストラクタのみが働く

広告

関連するオススメの記事