[ 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宣言すると、関数の引数・戻り値に値渡しを使用することができなくなります。