1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

shared_ptr と weak_ptr でオーバーロードしたら何かうまく呼べない

Last updated at Posted at 2024-04-23

おことわり:初心者の日記です.

やろうとしたこと

なんかこんなのがあって……

//仮想関数Meow()を定義する型
class ICat
{
public:
    virtual ~ICat() = default;
    virtual bool Meow() = 0;
};

//↑の具体クラスの方々
class CatA : public ICat { /*略*/ }
class CatB : public ICat { /*略*/ }
...

ICat::Meow() をシーケンシャルに呼ぶ機構のようなものが欲しいと思った.

で,「登録しておいた ICat (の具体クラスの)オブジェクト群の Meow() を順繰りに呼び出すだけのやつ」というのを考えた.
こんなやつだ:

//Register() での登録順で Meow() を呼ぶやつ
class MeowSeq
{
private:
    std::list< PTR > m_Seq; //登録されたものを保持するlist
public:
    //登録
    void Register( PTR Cat ){   m_Seq.push_back(Cat);   }

    //実行
    void Exec()
    {
        auto i = m_Seq.begin();
        while( i != m_Seq.end() )
        {//※Meow()がfalseを返してきたやつは登録解除する
            if( (*i)->Meow() )
            {   ++i;    }
            else
            {   i = m_Seq.erase( i );   }
        }
    }
};

さて,私くらいの雑魚になるともうね……ご覧のようにこの時点で問題が生じるのだ.
「コレ, PTR の型ってのは実際何なの?」という問題 である.

何故問題が生じるのかというと…

  • ここで PTR がシンプルに ICat* であれば,とりあえず話は単純で「MeowSeq は登録された物の寿命については一切関与しませんよ」っていうことになるけど…
  • でも,関与してほしい場合もある.スマートポインタとかいうやつでも登録したいという気持ちが胸中にある.

…みたいなわがままなことを考えているから.

そんなわけで,上記コード内の PTR のところは一体どうしたらいいのだ? ということになったわけだ.

私は悩んだ.そしてこう思った.
使う側(登録する側)の都合だけ考えるなら,こうなるんじゃね? と:

//登録用メソッドをオーバーロードする.(最初はこの2つで考えてみる)
void Register( ICat *pCat );    //寿命は外側で管理するよ版
void Register( std::unique_ptr<ICat> &&upCat ); //寿命は勝手に管理してね版

やりたいのは多分こういうことだ.

m_Seq の型は?

上記の「オーバーロードすればよくね?」という話によって,未定だった PTR なる記述を1個削除することできたので,次の課題は std::list< PTR > m_Seq; の部分である.ここの PTR をやっつけねばならぬ.

unique_ptr が引数に突っ込まれてくるパターンがある以上,std::list< ICat* > m_Seq; という持ち方はできないだろうから,とりあえず std::list< std::unique_ptr<ICat> > m_Seq; という型で保持することを考えてみる.
すると問題は以下のようになる:

private:
    //こうするなら……
    std::list< std::unique_ptr<ICat> > m_Seq;
public:
    //こっちはOKだが……
    void Register( std::unique_ptr<ICat> &&upCat ){ m_Seq.push_back( std::move(upCat) );    }

    //問題:こっちはどうするのか?
    void Register( ICat *pCat ){    /* ? ?? ????*/  }

ICat*m_Seq に突っ込むことはできぬ……私は悩んだ.そして,こう考えた.
「だが…… 『ICat* をメンバに保持するやつ』の unique_ptr ならば m_Seq に突っ込めるのでは…?」と.
つまり,こうだ:

//こんなのがあれば…
class PtrWrapper : public ICat
{
private:
    ICat *m_ptr;
public:
    PtrWrapper( ICat *ptr ) : m_ptr(ptr) {}
    virtual bool Meow() override {  return m_ptr->Meow();   }
};

//こんな感じにしてしまえるのではあるまいか
void Register( ICat *pCat ){    m_Seq.push_back( std::make_unique<PtrWrapper>(pCat) );  }

なんとなく迷宮に迷い込み始めているような気もしてくるが,とりあえずなんかできた.

//使う側のテスト
CatA A;

MeowSeq Seq;
Seq.Register( &A ); //ただのポインタを渡すよ
Seq.Register( std::make_unique<CatB>() );   //unique_ptr を渡すよ

調子にのって追加する

OK,そしたら同様に shared_ptr もいけるハズ.

//またおんなじようなのを用意してやって……
class SPWrapper : public ICat
{
private:
    std::shared_ptr<ICat> m_sp;
public:
    SPWrapper( const std::shared_ptr<ICat> &sp ) : m_sp(sp) {}
    virtual bool Meow() override {  return m_sp->Meow();    }
};

//オーバーロードを増やすよ!
void Register( const std::shared_ptr<ICat> &spCat ){    m_Seq.push_back( std::make_unique<SPWrapper>(spCat) );  }

//---
//(使う側)
Seq.Register( std::make_shared<CatB>() );   //shared_ptr を渡せるイェーイ!

いけた.OKすぎる.
この調子で weak_ptr も追加だー!

//またまたこんなのを追加して……
class WPWrapper : public ICat
{
private:
    std::weak_ptr<ICat> m_wp;
public:
    WPWrapper( const std::weak_ptr<ICat> &wp ) : m_wp(wp) {}
    virtual bool Meow() override
    {
        auto sp = m_wp.lock();
        if( !sp )return false;
        else{   return sp->Meow();  }
    }
};

//オーバーロードを増やすよ!
void Register( const std::weak_ptr<ICat> &wpCat ){  m_Seq.push_back( std::make_unique<WPWrapper>(wpCat) );  }

と,追加したところで問題発生.

何か不便

weak_ptr のを追加したところで

Seq.Register( std::make_shared<CatB>() );   //shared_ptr を渡せるイェーイ!

が「イェーイ!」って言えなくなった.

error C2668: 'MeowSeq::Register': オーバーロード関数の呼び出しを解決することができません。

とコンパイルエラーになってしまったのだ.
shared_ptr<CatB> を渡すのだと shared_ptr<ICat>weak_ptr<ICat> のどっちにするのか決められん,というわけだ.

こいつは困った.これを回避するには以下のように書くことになるのだろうか?

//こういうのがあって,これを登録したいときには……
std::shared_ptr<CatB> spCatB;

//こんな感じに書かないとならないのか?
Seq.Register(    std::shared_ptr<ICat>( spCatB )    );  //引数に単純に spCatB って書けない

そこはかとなく不便な感じ.
もはや「イェーイ!」とかいうテンションでは引数渡せない.
一体どうすればいいのか…… 全ては闇の中だ……


追記

こういうのって,「オーバーロード」にこだわる必要性がどれだけあるのか…?
「意味的に同じことをするのだから(?) → 呼ぶ側を同じ字面で書きたい」的なところに 譲れない何か が実際あるものだろうか? とか考えると……

「別名のメソッドにすればいいよね」(という逃げの姿勢)でも良いのでは? みたいな気がする.
どうなん?

「何とは戦うべきで,何からは別に逃げてしまっても良いか」みたいな判断が,こう,おぼつかないというか.


その後

今現在,コレと意味的に同じような雰囲気の物を書いてみており,
そこではこの記事内の PTR に相当する型はシンプルに shared_ptr になっている.

  • unique_ptrshared_ptr に変換して保持すればいい.(コメントにて指摘いただいた)
  • ポインタ → 何もしないカスタムデリータを持たせた shared_ptr とする.
  • weak_ptr → 未対応

という感じだ.
weak_ptr を考慮しないことにした理由は「なんかコメントでボロクソに言われたから」とかではなく,単純に「要らなそう」だからだ.

「参照が循環でどうの」というよりも,
登録物と登録先の寿命の関係性がそんな予測できないような話を扱うのか? という面において
「とりあえずそんな面倒な話にする必要はない → そしたら weak_ptr の出番無いよね」という感じだ.

いただいたコメントの内容を理解できているわけじゃないのに,
なんかわからんけども,コメントで述べられていた形に寄っていく感じというかで……
「上級者ってSUGEEEEEE!」っていう(ある種 恐怖感(?) のようなものすら感じる).

1
1
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?