c++11;unique_ptrとポリモーフィズム
アンチオブジェクト指向(値指向)で何の役にも立たない出来損ないのSTLのコンテナ群も
C++11のunique_ptrとauto,moveその他の工夫により、少しは使えるものになりそうです。
先ずはunique_ptrのメモ
- 派生クラスをベースクラスポインタで管理
- rawポインタの取得と取り外し
- コピー、ムーブの禁止とstd::move()による値移動
- 関数、引数と戻り値
- make_unique;提案new_upとしたい
- コード例(コンパイル可)
- auto_ptr
派生クラスをベースクラスポインタで管理
オブジェクトをポインタで管理することと、 派生クラスのオブジェクトをベースクラスのポインタで管理することが、 オブジェクト指向のキモです。
unique_ptr<T>は自動delete機能を持ったポインタです。
rawポインタと同じく派生クラスをベースクラスのunique_ptrで取り扱うことができます。
クラスA,B,CがありB,CがAの派生だとして、次のようにAのunique_ptrにB,Cのオブジェクトをセットすることができます。
unique_ptr<A> _p1(new A()); unique_ptr<A> _p2(new B()); unique_ptr<A> _p3(new C());rawポインタと同じように*,->オペレータを使ってアクセスできます。
A,B,Cが仮想関数doIt()を持つとして、上記_p1,_p2,_p3で呼び出すとすべてunique_ptr<A>であるにも関わらず、A,B,CそれぞれのクラスのdoIt()が呼ばれます。
_p1->doIt(); // AのdoIt() _p2->doIt(); // BのdoIt() _p3->doIt(); // CのdoIt()勿論解体時にはそれぞれのクラスの解体子が呼ばれます(~A()がvirtualである必要があります)
rawポインタの取得と取り外し
get()でポインタを取得することができます。release()でポインタを取り外すことができます。
A* _raw_p1= _p1.get(); // ポインタの取得。
// _p1はポインタを管理したまま。
A* _raw_p2= _p2.release(); // ポインタを取り外す。
// _p2は空になります。
if( _p1 ){} // 真(オブジェクト管理中)
if( _p2 ){} // 偽(オブジェクト空、非管理中)
releaseで取り外したポインタは別途deleteする必要があります。空のunique_ptrにget(),release()を作用させるとnullptrが返ってきます。
コピー、ムーブの禁止とstd::move()による値移動
unique_ptrのコピーやムーブは禁止されています。std::moveを用いて内容(ポインタ)を移動することができます。
unique_ptr<A> _p4(new A()); unique_ptr<A> _p5(new B()); _p5=_p4; // エラー _p5=std::move(_p4); // _p5の内容はdeleteされ_p4の内容が_p5に移る
関数、引数と戻り値
引数としてunique_ptrの参照体,const参照体,実体を持つ次のような関数があるとします。戻り値はunique_ptr実体です。
#include <list> static std::list<std::unique_ptr<A> > values; std::unique_ptr<A> funcA(std::unique_ptr<A>& x_ // 参照体 ,const std::unique_ptr<A>& y_ // const参照体 ,std::unique_ptr<A> z_ // 実体 ){ values.push_back(std::move(x_)); // 呼び出し側が空になる //vect.push_back(std::move(y_)); // 入れられない values.push_back(std::move(z_)); // オブジェクト移動 std::unique_ptr<A>& _p_top= values.front();// STLコンテナはおバカなので std::unique_ptr<A> _ret(_p_top.release()); // rawポインタで移動 values.pop_front(); // ... return _ret; }この関数では引数を自分のlistに取り込んでいます。取り込むにあたりstd::moveを使いオブジェクトを移動します。
const参照体の場合は移動できませんので、ここでは無処理になっています。
オブジェクト管理移動を前提として値を返す場合はunique_ptr実体とします。関数側で保持しているオブジェクトを見せるだけの場合は参照体もあります、
呼び出し側は次の様な形となります。
std::unique_ptr<A> _p41;
std::unique_ptr<A> _p42;
std::unique_ptr<A> _p51=funcA(_p41 // 破壊される
,_p42 // 無事
,std::unique_ptr<A>(new X_B_A(73))
);
make_unique;提案new_upとしたい
C++14ではmake_unique
auto _p= std::make_unique<C_A>(19,20,21);
// unique_ptr<C_A> _p=unique_ptr<C_A>(new C_A(19,20,21));と同じ
C++11では用意されていませんが、次のような定義で代替できます。
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
shared_ptrに関してはC++11でmake_sharedが用意されています。
mこれはオブジェクトをnewする手続きですので"make_xxx"というのはとても不適切な名前です。
次のような定義を置くことを提案します。
namespace dfaft{
//----------------------------------------
// uniq_ptrの補助
template<typename T, typename... Args>
std::unique_ptr<T> new_up(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<typename T>
using up=std::unique_ptr<T>;
//----------------------------------------
}
newを使います。unique_ptrはuniqueと省略するのではなくupにします。ついでにupという表記も用意します。次のような書き方ができるようになります。
using namespace draft;
list<up<A> > func71(int i1_,int i2_){
up<A> _p71(new_up<A>(i1_,0)); // 構築子の引数でも可
auto _p72=new_up<A>(i2_,5); // =でも可,autoも可
list<up<A> > _ret; // up<T>はコンテナ要素可
_ret.push_back(std::move(_p71));
_ret.push_back(std::move(_p72));
_ret.push_back(new_up<A>(i1_,i2_)); // 関数引数に直接記述も可
return _ret;
}
up/downのupに見えてしまうというのはちょっと検討の余地があるかもしれませんが、make_uniqueは意味不明であり、unique_ptrは至る所で使用するには表記が長すぎます。
コード例(コンパイル可)
色々な記述を含むコンパイル可能なソースを示します。
auto_ptr
auto_ptrというゴミがかつて存在していました。
バグの温床ともいうべき仕様で絶対に使ってはならない、rawポインタの方がはるかに安全だと思っていたところ、C++11では「非推奨」になっていました。
そりゃそうだわ。
| 固定リンク

