shared_ptrの便利なところその2
その1はどこ?
その1(auto-delete)に関しては私が書く必要がないので省略する
本題
生ポインタで動的ポリモーフィズムするときにめんどくさい事
struct Base{}; struct Derived : Base {}; int main() { Base* p = new Derived(); delete p; }
上記の書き方は、C++では好ましくない書き方と言われています
いえ、好ましくないどころか、非常に不味いです
まず自分でdeleteしてるのが不味いですが、それは置いておいて
と言うのもBase*
の指したDerived
オブジェクトが正しくは破棄できないからです
ポインタp
がBase*
型であるため、delete
が呼ばれたときに
Derived
のdestructor
が呼ばれないからです。
そうすると、継承されるかもしれないクラスは
以下のように書く制限が課せられることになります
// Baseクラスのdestructorを仮想関数化しておく struct Base{ virtual ~Base()=default; }; struct Derived : Base { ~Base()override=default; }; int main() { Base* p = new Derived(); delete p; }
しかし、です。
かもしれないで余分なコードを書くのはとてもナンセンスです
第一destructorは必ず呼ばれる(使用される)関数であるにもかかわらず
パフォーマンスの低下するvirtual
を着けておくなんて、無駄極まりない(※1)
継承先のために継承元クラスのdestructor
を書き換える
と言うプログラミングの仕方が非常にクソいです
すべてのクラスの継承は親クラスが制限できるべきである
と言う意見も有りますが、これはその問題とは異なる欠陥です
これは継承を明示的に禁止しているわけでなく、
継承できてしまった結果、バグが起こると言う事だからです
ただバグが潜み易いと言うだけなのです
これを回避しつつ、動的ポリモーフィズムする手段としては以下のようなものが考えられます
struct Base{}; struct Derived : Base {}; int main() { Base* p = new Derived(); static_cast<Derived*>(p); delete p; }
常に継承先にキャストしてdelete
するという発想です
これならBase
がvirtual
だろうが非virtual
だろうが関係なく、適切にオブジェクトを破棄できます
しかしこれも実用を考えれば使用するのは難しそうです
と言うのも、p
の指しているオブジェクトを
プログラマが知っていなければならないからです。
これではポリモーフィズムの意味がありません
struct Base{}; struct Derived : Base {}; int main() { Derived* p = new Derived(); delete p; }
と書いているのと同じです
スマートポインタではどうか
上記の悩みをあざ笑うかのように、std::shared_ptr
では
以下のような書き方をして安全にオブジェクトを破棄することができます
struct Base{}; struct Derived : Base {}; int main() { std::shared_ptr<Base> p( new Derived() ); // auto-delete }
なんということでしょう
shared_ptr<Base> p
はコンストラクタで受取った型を覚えておき、
destroyが呼ばれたときに(auto-deleteで死ぬときに)
その型でオブジェクトをdelete
する事ができるのです
つまり、一つ前のようなstatic_cast
してdelete
と同じことを自動でしてくれるのです
そのうえ
内部のポインタ型はBase*
型ですので、Base
型のインターフェースのまま
ポリモーフィズムを実現してくれるのです
スマートポインタの便利なところその2とは
この「破棄するときはプログラマがキャストすることなく、自動でキャストを行いdeleteを行ってくれる」
というところです
つまり、Base
型のdestructor
にvirtual
を要求しないのです!!!!!
私はこの事実を最近まで知りませんでした。
これはすばらしい。
スマートポインタとはよく言ったものです
実は親クラスのdestructor
にvirtual
を付けるのがいやで
あまり積極的にオブジェクト指向でプログラミング(動的ポリモーフィズム)
していなかったのですが、これを知った事でかなり抵抗が薄れました
積極的に採用していきたいですね!
(※1)
最近のコンパイラは賢く、「virtualも高速に動くようバイナリが吐かれる」みたいな話を聞きました
実際はどうなのでしょうか・・・