おことわり:初心者の日記です.
やろうとしたこと
なんかこんなのがあって……
//仮想関数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_ptr
→shared_ptr
に変換して保持すればいい.(コメントにて指摘いただいた) - ポインタ → 何もしないカスタムデリータを持たせた
shared_ptr
とする. -
weak_ptr
→ 未対応
という感じだ.
weak_ptr
を考慮しないことにした理由は「なんかコメントでボロクソに言われたから」とかではなく,単純に「要らなそう」だからだ.
「参照が循環でどうの」というよりも,
登録物と登録先の寿命の関係性がそんな予測できないような話を扱うのか? という面において
「とりあえずそんな面倒な話にする必要はない → そしたら weak_ptr
の出番無いよね」という感じだ.
いただいたコメントの内容を理解できているわけじゃないのに,
なんかわからんけども,コメントで述べられていた形に寄っていく感じというかで……
「上級者ってSUGEEEEEE!」っていう(ある種 恐怖感(?) のようなものすら感じる).