コンストラクタは、OOP(オブジェクト指向プログラミング)における特別なメンバ関数の一種であり、オブジェクトを作成し、メンバ変数を初期化するために使用されます。C++ では、コンストラクタはクラスと同じであり、戻り値はありません。
以下の場合には、カスタムコンストラクタが必要です:
- クラスのメンバの初期化方法をカスタマイズする場合
- クラスのオブジェクトを作成する際に関数を呼び出す場合
コンストラクタは通常
public
ですが、protected
、private
などに宣言することもできます。
デフォルトコンストラクタ#
デフォルトコンストラクタは、引数を提供せずに初期化されるコンストラクタです。クラスの定義時に明示的に定義する必要はありません。明示的に定義しない場合、コンパイラはデフォルトコンストラクタを自動的に生成します。デフォルトコンストラクタの例は次のとおりです:
class MyClass{
public:
MyClass(){
// 何かを行う..
}
}
デフォルトコンストラクタの「最も困難な解析」について#
C++ の解析プログラムは、宣言に偏っているため、エディタが生成したデフォルトコンストラクタを呼び出して括弧を使用しようとすると、警告が表示されます:
class MyClass{};
int main(){
MyClass myClass();
// 警告 C4930
}
この例では、MyClass myClass (); は、MyClass のデフォルトコンストラクタを呼び出して myClass オブジェクトを作成することを期待どおりに行いません。代わりに、この行のコードは実際には関数宣言として解釈され、オブジェクト宣言ではありません。ここでは、myClass は引数を取らず、MyClass 型のオブジェクトを返す関数と解釈されます。
この現象は、「最も困難な解析」(Most Vexing Parse)と呼ばれ、C++ 言語の有名な構文解析の問題です。この問題は、C++ の文法の複雑さに起因し、一部の文法構造が文脈によって異なる複数のものとして解釈される可能性があるため、コンパイラは式を関数宣言ではなくオブジェクトの構築として解釈することを優先します。
この問題を回避するために、オブジェクトを明示的に作成します:
int main() {
MyClass myClass;
}
パラメータ化コンストラクタ#
パラメータ化コンストラクタは、複数の引数を持つ初期化を行います:
class MyClass{
public:
int x, y;
MyClass(int a, int b): x(a), y(b){
// 何かを行う..
}
}
ここでは、メンバ初期化リストを使用しています。式の具体的な形式は次のとおりです:
identifier(argument)
メンバ初期化リストは、コロンの後に続くすべての式で構成されます:
MyFunc(int a, int b, int c):
m_a(a), m_b(b), m_c(c){}
コピーコンストラクタ#
コピーコンストラクタは、同じ型のオブジェクトを使用して別の新しいオブジェクトを初期化するために使用されます。通常、オブジェクトのコピーのシナリオで使用されます:
class MyClass{
public:
int x, y;
MyClass(const MyClass& mc):
x(mc.x), y(mc.y) {}
}
上記のシンプルな例では、メンバはスカラーです。このような単純な場合、コンパイラはコピーコンストラクタを生成するため、自分で定義する必要はありません。ただし、コピーコンストラクタに特別な動作が必要な場合(例:新しいメモリを割り当てる場合など)、コピーコンストラクタを自分で実装する必要があります。
コンストラクタを定義する際には、コピー代入演算子=
をオーバーロードする必要があります。
ムーブコンストラクタ#
ムーブコンストラクタは、C++11 以降の新機能であり、リソースを 1 つのオブジェクトから別のオブジェクトに移動するために使用されます。通常、右辺値参照をサポートするシナリオで使用されます。ムーブコンストラクタは、既存のオブジェクトのデータの所有権を新しい変数に移譲し、元のデータをコピーしないことを特徴とします。最初の引数として rvalue 参照を使用し、後続の引数はデフォルト値を持つ必要があります。大きなオブジェクトを渡す場合、ムーブコンストラクタを使用するとプログラムの効率が大幅に向上します:
//todo lvalue と rvalue
class MyClass{
public:
char *str;
MyClass(MyClass && obj): str(obj.str){
obj.str = nullptr; // 元のオブジェクトが解放される際にメモリを解放するのを防ぐ
}
}
デリゲートコンストラクタ#
デリゲートコンストラクタは、C++11 以降の新機能であり、1 つのコンストラクタが別のコンストラクタを呼び出すことを許可します。これにより、コードの解耦がさらに進みます:
class MyClass{
public:
int x, y, z;
MyClass(int a, int b):x(a), y(b), z(0) {}
MyClass(int a, int b, int c): Point(a, b) {z = c;}
}
継承コンストラクタ#
継承コンストラクタは、C++11 以降の概念であり、基本クラスからコンストラクタを継承し、using
キーワードを使用して実現します:
class Base{
public:
Base(int x){};
};
class MyClass: public Base{
using Base::Base; // Baseのコンストラクタを継承する
}
明示的コンストラクタと暗黙的コンストラクタ#
explicit
キーワードで宣言されたコンストラクタは、直接初期化にのみ使用できます。暗黙的な変換が必要な場合にコンストラクタが意図せず呼び出されるのを防ぎます:
class MyClass {
public:
explicit MyClass(int a) {}
};