やりたいこと
親クラスで定義されたスマートポインタを引数に取る純粋抽象関数を子クラスで実装して使いたいが、やり方がよく分からなかったのでメモをする。
Sub1, Sub2とBaseクラスを継承したものを複数個用意し、Sub1同士、Sub2同士を比較する関数を持たせたいという場面を想定している。子クラスのインスタンスを使用する関数にてテンプレートで解決するのだが、IDEのオートコンプリート機能を使いたいので、テンプレートは出来れば使いたく無いという我儘からきた考えである。私の結論としては、テンプレートを使ったほうが良さそうということになった。このコードはdynamic_castを使っており安全でないらしいことと、スマートポインタを使っても生ポインタを取り出して使用している、といったことをして動かしているのでうまいコードではないような気がするが、これよりうまい方法が思いつかなかった。ちなみになぜdynamic_castは安全でないのかはまだ僕は知らない。
実験
最初のコード。生ポインタをcomp関数に入れると動くがスマートポインタを入れると動かない。
#include <iostream>
#include <memory>
using namespace std;
class Base {
public:
int val = 0;
virtual bool comp(const Base* obj) = 0;
virtual bool comp(const unique_ptr<Base> &obj) = 0;
};
class Sub : public Base {
public:
explicit Sub(int i){ this->val = i*2; }
bool comp(const Sub *obj) {
return this->val < obj->val;
};
bool comp(const Base *obj) override {
return comp(dynamic_cast<const Sub*>(obj));
};
// コンパイルエラー
bool comp(const unique_ptr<Sub> &obj) {
return this->val < obj->val;
};
bool comp(const unique_ptr<Base> &obj) override {
return comp(dynamic_cast<const unique_ptr<Sub>>(obj));
// return comp(dynamic_cast<const unique_ptr<Sub>&>(*obj));
// return comp(dynamic_cast<Sub*>(obj.get()));
};
};
int main() {
// 生ポインタを使うと動く
Base *pr1 = new Sub(1);
Base *pr2 = new Sub(2);
cout << (pr1->comp(pr2) ? "true" : "false") << endl;
// スマートポインタを使用したい
unique_ptr<Base> p1 = make_unique<Sub>(Sub(1));
unique_ptr<Base> p2 = make_unique<Sub>(Sub(2));
cout << (p1->comp(p2) ? "true" : "false") << endl;
// 生ポインタを入れると動く
cout << (p1->comp(p2.get()) ? "true" : "false") << endl;
return 0;
}
しかし、生ポインタを引数に取りたいので、ちょっと改善したらコンパイルは通るが実行時にエラーが起こった。
bool comp(const unique_ptr<Base> &obj) override {
// return comp(dynamic_cast<const unique_ptr<Sub>>(obj));
return comp(dynamic_cast<const unique_ptr<Sub>&>(*obj));
// return comp(dynamic_cast<Sub*>(obj.get()));
}
実行するとエラーが発生した。
>>> true
>>> libc++abi.dylib: terminating with uncaught exception of >>> type std::bad_cast: std::bad_cast
>>>
>>> Process finished with exit code 6
こちらが動くパターンだ。comp関数は引数にユニークポインタを受け取っているが、結局キャストするときは生ポインタを使いましょうということである。エラーになる理由がよくわかってないが、とりあえずこれで動くことは確認できた。生ポインタを外部から入れるよりは少しマシである。
bool comp(const unique_ptr<Base> &obj) override {
// return comp(dynamic_cast<const unique_ptr<Sub>>(obj));
// return comp(dynamic_cast<const unique_ptr<Sub>&>(*obj));
return comp(dynamic_cast<Sub*>(obj.get()));
}
bool comp(const unique_ptr<Base> &obj)
の中身を変更すると、コンパイルが通るようになったり、実行時にエラーを出さなったりしたが、どんどん生ポインタに近づいていった。動くコードではbool comp(const unique_ptr<Sub> &obj)
は使用されていないから、しかたない。ユニークポインタを、代入された関数が終了するまで保持し続けるのであればこれでもいいのかもしれないと思った。
ちなみに、スマートポインタのキャストを行いたい人は私以外にも存在するをとは確認できている。
https://stackoverflow.com/questions/26377430/how-to-do-perform-a-dynamic-cast-with-a-unique-ptr
謝辞
同僚のエンジニアのMさんに色々教えてもらったので感謝。