[ C++で開発 ]
C++のコンストラクタについて
本ページはおもに書籍「Effective C++」から
C++では、クラスを定義したときにコンストラクタを定義しなければ、コンパイラが自動的に引数なしのコンストラクタおよびコピーコンストラクタを補完します。
class Person {
std::string name;
};
と記述したクラスは、デフォルトコンストラクタ、デフォルトデストラクタ、デフォルトコピーコンストラクタ、デフォルト代入演算子関数がコンパイラによって補完され、実質以下のコードと同等になります。
class Person {
std::string name;
public:
Person() {}
~Person() {}
Person(const Person& aPerson) { ... }
Person& operator=(const Person& rhs) { ... }
};
ここで、メンバー変数がstd::string& nameやconst std::stringであると変更が
コピーコンストラクタが走る状況
void func1(Person a);
Person func2();
Person p1;
Person p2(p1); // コピーコンストラクタが実行、--- (1)
Person p3 = p2; // コピーコンストラクタが実行、代入演算子関数は実行されない
func1(p3); // コピーコンストラクタが実行、引数への値渡し
p1 = func2(); // コピーコンストラクタが実行、戻り値の値渡し
std::stringをメンバーに持つクラスPersonを考えます。
class Person {
std::string name;
public:
Person(const std::string &aName) { name = aName; }
};
これで一見よいように思われます。しかし、このコードでは余分な処理が発生します。
2.のときに、stringクラスのコンストラクタが実行されます。続いてコンストラクタ本体の実行において、=演算子を呼び出してstringインスタンスをコピーします。したがって、2.で実行したコンストラクタは無意味です。
class Person {
std::string name;
public:
Person(const std::string &aName) : name(aName) {}
};
class Person {
std::string name;
public:
Person() : name() {}
};
class Student : public Person {
int id;
public:
Student() : Person(), id(0) {}
};
引数が1つのコンストラクタは、思いもよらない変換を行うことがあります。
class Counter {
int count;
public:
Count(int init) : count(init) {}
};
と引数にint型を1つ取るコンストラクタを定義したクラスがあると、以下のような局面でCounterインスタンスが生成されます。
Counter pageHit;
:
pageHit = 14;
右辺値のint型の14をCounterに変換しようとします。その際Counter(14)が呼ばれることになります。このとき、コンストラクタにexplicit宣言を追加すると、変換が行われず、コンパイルエラーになります。
class Counter {
int count;
public:
explicit Count(int init) : count(init) {}
};
明示的にコンストラクタを呼び出すか、キャストを指定する必要があります。
Counter pageHit;
:
pageHit = Counter(14);
pageHit = static_cast<Counter>(14);
コピーコンストラクタも引数が1つですが、これをexplicit宣言すると、関数の引数・戻り値に値渡しを使用することができなくなります。