shared_ptrの便利なところその2

その1はどこ?

その1(auto-delete)に関しては私が書く必要がないので省略する

本題

動的ポリモーフィズムをするときに役立つ特性を持っています

生ポインタで動的ポリモーフィズムするときにめんどくさい事

struct Base{};

struct Derived : Base {};

int main()
{
   Base* p = new Derived();
   delete p;
}

上記の書き方は、C++では好ましくない書き方と言われています
いえ、好ましくないどころか、非常に不味いです

まず自分でdeleteしてるのが不味いですが、それは置いておいて
と言うのもBase*の指したDerivedオブジェクトが正しくは破棄できないからです
ポインタpBase*型であるため、deleteが呼ばれたときに
Deriveddestructorが呼ばれないからです。

そうすると、継承されるかもしれないクラスは
以下のように書く制限が課せられることになります

// 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するという発想です これならBasevirtualだろうが非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型のdestructorvirtualを要求しないのです!!!!!  

私はこの事実を最近まで知りませんでした。
これはすばらしい。
スマートポインタとはよく言ったものです

実は親クラスのdestructorvirtualを付けるのがいやで
あまり積極的にオブジェクト指向でプログラミング(動的ポリモーフィズム
していなかったのですが、これを知った事でかなり抵抗が薄れました
積極的に採用していきたいですね!

(※1)
最近のコンパイラは賢く、「virtualも高速に動くようバイナリが吐かれる」みたいな話を聞きました
実際はどうなのでしょうか・・・