アンチオブジェクト指向(値指向)で何の役にも立たない出来損ないのSTLのコンテナ群も
C++11のunique_ptrとauto,moveその他の工夫により、少しは使えるものになりそうです。
先ずはunique_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が追加され、次のようにunique_ptrの生成ができます。
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は至る所で使用するには表記が長すぎます。
コード例(コンパイル可)
色々な記述を含むコンパイル可能なソースを示します。
// upTest.cpp:
#include <string>
#include <iostream>
using namespace std;
// (0) 次の派生関係のクラスがあるとして
class A {
public:
int v1,v2;
A(int v1_,int v2_):v1(v1_),v2(v2_){}
virtual ~A(){}
virtual std::string id(){ return std::string("A")+to_string(v1); }
};
class B_A:public A{ // B_AはAの派生
public:
B_A(int v1_,int v2_):A(v1_,v2_){}
virtual std::string id(){ return std::string("B")+to_string(v1); }
};
class C_A:public A{ // C_AはAの派生
public:
int v3;
C_A(int v1_,int v2_,int v3_):A(v1_,v2_),v3(v3_){}
virtual std::string id(){ return std::string("C")+to_string(v3); }
};
class X_B_A:public B_A{ // X_B_AはBの派生,Aの孫派生
public:
X_B_A(int v1_):B_A(v1_,0){}
virtual std::string id(){ return std::string("X")+to_string(v1); }
};
// (1) <mempry>をインクルード
#include <memory>
// (1-2) C++11の場合は次の定義を入れるとして
namespace std11{
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
//===============================================
std::unique_ptr<A> funcA(std::unique_ptr<A>& x_
,const std::unique_ptr<A>& y_
,std::unique_ptr<A> z_
);
int main(int argc,char**argv){
// (2) ===== 基本初期化
// (2-1) ポインタから作成
std::unique_ptr<A> _p21(new A(7,13));
// (2-2) ポインタ代入形式初期化は不可
//std::unique_ptr<A> _p22= new A(7,13);
// (2-3) ポインタmove式による初期化不可
//std::unique_ptr<A> _p23=std::move(new A(7,13));
// (2-4) 既にある変数による単純初期化は不可
//std::unique_ptr<A> _p24(_p21);
// (2-5) unique_ptr代入式による初期化
std::unique_ptr<A> _p25= std::unique_ptr<A>(new A(7,13));
// (2-6) move式を用いた既にある変数による初期化
std::unique_ptr<A> _p26(std::move(_p21));
// (2-7) move式を用いた既にある変数による初期化-2
std::unique_ptr<A> _p27= std::unique_ptr<A>(std::move(_p25));
// (2-8) 既にあるポインタによる初期化(推奨しない)
A* _r28= new A(7,13);
std::unique_ptr<A> _p28(_r28);
// (2-9) make_uniqueによる初期化 (C++14の場合はstd::で)
auto _p29= std11::make_unique<A>(7,13);
// (3) ===== 基本アクセス
// (3-1) *,->でアクセス
fprintf(stdout,"(3-1) 26.v1=%d 27.v2=%d\n",(*_p26).v1,_p27->v2);
// (3-2) ポインタの取得
A* _r32=_p26.get();
printf("(3-2) addr=%p v1=%d 26.addr=%p\n",_r32,_r32->v1,_p26.get());
// (3-3) ポインタの取り外し
A* _r33=_p26.release();
printf("(3-3) addr=%p v1=%d 26.addr=%p\n",_r33,_r33->v1,_p26.get());
// (3-4) ポインタを持っているかどうかの判断
if( _p26 ){
//...
}
// (3-5) ポインタへのキャストは不可
//printf("(3-5) addr=%p\n",(A*)_p26);
// (3-6) 単純代入不可
//_p01 = _p04;
// (3-7) move式により代入
_p28 = std::move(_p27);
//============================ 派生のアクセス ========================
// (4) ===== 派生初期化
// (4-1) 子派生
std::unique_ptr<A> _p41(new B_A(11,12));
std::cerr<<_p41->id()<<std::endl;
std::unique_ptr<A> _p42(new C_A(19,20,21));
std::cerr<<_p42->id()<<std::endl;
// (4-3) 孫派生
std::unique_ptr<A> _p43(new X_B_A(2));
std::cerr<<_p43->id()<<std::endl;
//============================ 関数呼び出し ========================
// (5-1)
//std::unique_ptr<A> _p51=funcA(_p41
// ,_p42
// ,_p43 エラー
// );
// (5-2)
printf("(5-2:0) _p41.addr=%p _p41.id=%s\n",_p41.get(),_p41->id().c_str());
std::unique_ptr<A> _p51=funcA(_p41 // 破壊される
,_p42 // 無事
,std::unique_ptr<A>(new X_B_A(73))
);
printf("(5-2:1) _p41.addr=%p\n",_p41.get());
cerr<<"(5-2:2) _p42="<<_p42->id()<<endl;
printf("(5-2:3) _p51.addr=%p _p51.id=%s\n",_p51.get(),_p51->id().c_str());
}
//------------------
// (6) 関数の引数と戻り値
#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_
,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()); // ...
values.pop_front(); // ...
return _ret;
}
//---------------------
// (7) new_up<T>およびup<T>の提案
namespace draft{
//----------------------------------------
// 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>;
//----------------------------------------
}
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;
}
auto_ptr
auto_ptrというゴミがかつて存在していました。
バグの温床ともいうべき仕様で絶対に使ってはならない、rawポインタの方がはるかに安全だと思っていたところ、C++11では「非推奨」になっていました。
そりゃそうだわ。