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

変換コンストラクタ

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

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

T a = 9; // 暗黙的なコンストラクタ呼び出し
T b(9);  // 明示的なコンストラクタ呼び出し
// ↑ いずれも`T(int v)`のコンストラクタが呼ばれる

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

目次

スポンサーリンク

様々な場面での利用

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

struct T { T(int v) {} }; // 変換コンストラクタ`T(int v)`を定義

T fn(T a) {
  return 8; // 変換コンストラクタが呼ばれる(戻り値型への変換)
}

int main() {
  fn(9); // 変換コンストラクタが呼ばれる(実引数型への変換)
  static_cast<T>(7); // 変換コンストラクタが呼ばれる(明示的なキャスト)
}

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

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

struct T {
  int i;
  T(int a, int b = 1) : i(a * b) {}
};

T v = 9; // OK

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

struct T {
  T(int a) {} // 余計な定義になってしまう
  T(int a, int b = 1) {}
};

T v = 9; // エラー:Conversion from 'int' to 'T' is ambiguous

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

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

struct T {
  T(int a)               { puts("1"); }
  T(int a, int b)        { puts("2"); }
  T(int a, int b, int c) { puts("3"); }
};

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

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

struct T {
  T(int a)               { puts("1"); }
  T(int a, int b)        { puts("2"); }
  T(int a, int b, int c) { puts("3"); }
  T(std::initializer_list<int> list) { puts("4"); }
};

T a = {1};       // "4"
T b{1};          // "4"
T c = {1};       // "4"
T d = {1, 2};    // "4"
T e = {1, 2, 3}; // "4"

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

T f = 1;      // "1"
T g(1);       // "1"
T h(1, 2);    // "2"
T i(1, 2, 3); // "3"
// T j(1, 2, 3, 4); // error: no matching constructor for initialization of 'T'
// T k{1, 2, 3, 4}; // "4"

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

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

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

T a = 9;    // error: no viable conversion from 'int' to 'T'
T b = {9};  // error: chosen constructor is explicit in copy-initialization
T c(9);     // OK
T d{9};     // OK
T e = T(9); // OK
T f = static_cast<T>(9); // OK

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

暗黙的なコンストラクタ呼び出しによる初期化方法は一見すると=演算子によってコピー代入や暗黙的なコピーコンストラクタ呼び出しが行われてしまうようにも思えるが、実際にはコンパイラ側の最適化によって、初期化のみが行われるようになることがほとんどである。つまり暗黙的/明示的いずれの記法も内部的には同じ初期化処理が行われ、処理コストは変わらないものとなる。なおこの挙動はC++17以降、言語仕様によって保証されている。

要するに、この場合のT v = 9;という式は、厳密には変換後の一時オブジェクトを代入する式となるが、実際には単なる初期化式として扱われることになる。

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

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

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

広告

広告