Help us understand the problem. What is going on with this article?

純粋仮想関数と抽象クラス、スマートポインタ

More than 1 year has passed since last update.

やりたいこと

親クラスで定義されたスマートポインタを引数に取る純粋抽象関数を子クラスで実装して使いたいが、やり方がよく分からなかったのでメモをする。
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さんに色々教えてもらったので感謝。

kakibara
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした