こんにちは。
最近勉強し始めたc#でWPFをやってみましたが、もう見たくもありません。
さて、今日はc++を使っていても意外になかなか覚えない、コンストラクタの暗黙の宣言 / 非宣言をまとめておきたいと思います。
関連して、よく問題になる暗黙のコピー、ムーブの問題についても書き留めておこうと考えています。
コンパイラが用意してくれる特殊関数は以下になります。(c++14時点)
デフォルトコンストラクタ
コピーコンストラクタ
コピー代入演算子
ムーブコンストラクタ
ムーブ代入演算子
デストラクタ
何事もなければこれらが全て暗黙的に宣言・定義されます。( class something {};
)
平和ですね。みんながみんなこうであればよいのですが、そうはいかないのです。
#コンストラクタが暗黙に宣言されるとき、されないとき
ざっくり以下に表にまとめました。
なお、constructorをctor, operatorをop, declarationをdeclと略しています。
[deprecated]は宣言されるものの非推奨の意(後述)です。
見方としては、例えば何かしらのコンストラクタを自前で書いた場合、横へ見ていって、デフォルトコンストラクタは宣言されない、コピームーブデストラクタはdefaultだな、といった感じです。
default ctor | copy ctor / op | move ctor / op | destructor | |
---|---|---|---|---|
any ctors | no decl | default | default | default |
copy ctor / op | no decl | - | no decl | default |
move ctor / op | no decl | [deprecated] | - | default |
destrctor | default | [deprecated] | [deprecated] | - |
パクリ元参考:
特殊メンバ関数とコンパイラによる暗黙宣言
本の虫: C++0xにすごい変更が来た
なお、メンバ変数や基底クラスでdeleteされている関数は暗黙的にdeleteされます。
これは宣言されない(no decl)ではなく、削除(delete)なので、仮に明示的なデフォルト宣言(=default)をしたとしてもdeleted functionという扱いになります。
暗黙的コピーとムーブが定義されることの問題
上の図では、3箇所に[deprecated]が入っています。
定義されるものの非推奨と言ったのは、これらは本来暗黙的に定義されてはならないと言ってもいいものだからです。
例えば、デストラクタを自前で用意したときのcopy / moveはdeprecatedになっていますね。
デストラクタは何かリソースを解放するためのものと言っても過言ではありません。何かリソースを解放しなければならないクラスというのは、そのリソースについて所有権を持っており、責任を負っているということです。
class sth0
{
int* p;
public:
sth0() p( new int() ) {}
~sth0() { delete p; }
};
リソースはなんでもいいですが、例えばこのようなクラスを書けば、ひと目で多重解放の危険があることが分かるでしょう。デストラクタを定義する場合のほとんどがこのようにリソースを管理する場合のはずです。
ゆえに、本当にデフォルト処理のコピーやムーブで構わないのであれば、それを明示してあげるべきです。
class sth1
{
public:
// sth1() = default; // if you need
sth1( const sth1& ) = default;
sth1& operator ( const sth1& ) = default;
sth1( sth1&& ) = default;
sth1& operator ( sth1&& ) = default;
~sth1() { std::cout << "output log" << std::endl; }
};
このクラスでは、デストラクタの処理はログを記録するだけです。
なのでコピーやムーブはデフォルトのものを使って欲しいと明示してあげます。
なお、コピーやムーブコンストラクタを宣言することでデフォルトコンストラクタが宣言されなくなります。
もし必要であればデフォルトコンストラクタもdefaultで明示してあげなくてはなりません。
ここまでくればムーブを明示的に宣言したときにコピーが暗黙宣言されてしまうことがdeprecatedであることの理由は分かるかと思います。
何にせよ、全ての特殊関数をいちいち明示的に宣言/削除してあげるのが確実ですね。
少なくとも、人に読ませるようなコードではそうしたほうがいいかもしれません。
class something
{
// ctor
something() noexcept = default;
// copy
something( const something& ) = default;
something& operator = ( const something& ) = default;
// move
something( something&& ) noexcept = default;
something& operator = ( something&& ) noexcept = default;
// dtor
~something() noexcept = default;
};