値指向vectorをunique_ptrでオブジェクト指向っぽくする
アンチオブジェクト指向(値指向)で何の役にも立たない出来損ないのSTLのコンテナ群も
C++11のunique_ptrとauto,moveその他の工夫により、少しは使えるものになりそうです。
とはいえ、ラッパーを被せないと使えません。ここではVectorを実用形にするラッパーの例を示します。
unique_ptrに関しては
c++11;unique_ptrとポリモーフィズムに説明を載せました。
オブジェクト指向vector
まずはstd::vectorの要素をunique_ptr
追加した機能は次のものです。
// upVector.hpp
#ifndef upVector_HPP
#define upVector_HPP
#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
namespace draft{
//=========================================================
template<class T>
class upVector:public std::vector<std::unique_ptr<T> >{
public:
// オブジェクト取り外し
std::unique_ptr<T> pop_back(){
std::unique_ptr<T> _ret(std::move(this->back()));
std::vector<std::unique_ptr<T> >::pop_back();
return _ret;
}
std::unique_ptr<T> erase(typename std::vector<std::unique_ptr<T> >::iterator itr_){
std::unique_ptr<T> _ret(itr_->release());
std::vector<std::unique_ptr<T> >::erase(itr_);
return _ret;
}
// ソート
void sort(std::function<bool(const std::unique_ptr<T>&s1
,const std::unique_ptr<T>&s2)> f_){
std::sort(std::vector<std::unique_ptr<T> >::begin()
,std::vector<std::unique_ptr<T> >::end(),f_);
}
void sort(){
sort([](const std::unique_ptr<T>&s1
,const std::unique_ptr<T>&s2){ return *s2>*s1; });
}
void rsort(){
sort([](const std::unique_ptr<T>&s1
,const std::unique_ptr<T>&s2){ return *s1>*s2; });
}
// ostream出力
friend std::ostream& operator<<(std::ostream& os_,const upVector<T>& container_){
std::string _dlmt="";
os_<<" [ ";
for(decltype(*container_.begin()) _ptr:container_){
os_<<_dlmt<<*_ptr;
_dlmt=" , ";
}
os_<<" ] ";
return os_;
}
};
#ifndef UP_DEFINED
#define UP_DEFINED
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>;
#endif
//=========================================================
}
#endif
次の様にアクセスできます。
#include "upVector.hpp" #include <string> #include <iostream> using namespace draft; using namespace std; class A{ public: virtual ~A(){} virtual string name()const{return "A";} friend ostream& operator<<(ostream&os,const A& a_){ return os<<a_.name(); } bool operator>(const A&a2_)const{ return this->name() > a2_.name(); } }; class B:public A { string id; public: B(const string&id_):id(id_){} string name()const override{ return string("B-")+id; } }; class C:public A { int id; public: C(int id_):id(id_){} string name()const override{ return string("C-")+to_string(id); } }; int main(int argc,char**argv){ upVector<A> _ss; _ss.push_back(new_up<B>("abc")); _ss.push_back(new_up<C>(5)); _ss.push_back(new_up<B>("xyz")); _ss.push_back(new_up<B>("ccc")); _ss.push_back(new_up<C>(3)); _ss.emplace_back(new B("ghi")); // こうも書けるが // ソートとostream出力 cout<<"元 :"<<_ss<<endl; _ss.sort(); cout<<"ソート後:"<<_ss<<endl; // オブジェクト取り外し up<A> _s=_ss.erase(_ss.begin()); cout<<"先頭外し:"<<*_s<<endl; cout<<"結果 :"<<_ss<<endl; _s=_ss.pop_back(); cout<<"最後外し:"<<*_s<<endl; cout<<"結果 :"<<_ss<<endl; _s=_ss.erase(_ss.begin()+1); cout<<"[1]外し :"<<*_s<<endl; cout<<"結果 :"<<_ss<<endl; }
push_back()は末尾にオブジェクトを追加するメソッドです。
emplace_back()を用いるとポインタの形を用いることができますが、推奨しません。
pop_back()はunique_ptrでオブジェクトを取り出します。
次の結果が得られます。
元 : [ B-abc , C-5 , B-xyz , B-ccc , C-3 , B-ghi ] ソート後: [ B-abc , B-ccc , B-ghi , B-xyz , C-3 , C-5 ] 先頭外し:B-abc 結果 : [ B-ccc , B-ghi , B-xyz , C-3 , C-5 ] 最後外し:C-5 結果 : [ B-ccc , B-ghi , B-xyz , C-3 ] [1]外し :B-ghi 結果 : [ B-ccc , B-xyz , C-3 ]
STLコンテナはオブジェクト管理ではなく値管理
先ず、十分認識しておかなければならないのがSTLコンテナはオブジェクト指向ではなく値指向であるということです。
仕方なく「値」にオブジェクト管理機能を入れます。
クラスXのコンテナが欲しい場合、直接コンテナを作るのではなく、クラスXを管理するクラスのコンテナを作るという、とんでもなくまどろっこしい方法をとります。まともな考えではありませんが仕方ありません。
通常のソートは値管理を前提にしているため、内容ではなく、unique_ptrの比較というおバカなものになります。
cout<<"元 :"<<_ss<<endl; sort(_ss.begin(),_ss.end()); cout<<"ソート後:"<<_ss<<endl; --- 結果 元 : [ B-abc , C-5 , B-xyz , B-ccc , C-3 , B-ghi ] ソート後: [ B-abc , B-xyz , C-5 , B-ccc , C-3 , B-ghi ]
ostreamへの出力も値管理を前提にしているため、エラーになります。
vector<string> _sx; copy(_sx.begin(), _sx.end(), ostream_iterator<string>(cout, "\n"));// OK // copy(_ss.begin(), _ss.end(), ostream_iterator<unique_ptr<A> >(cout, "\n"));// エラー
最後の要素を取り出す通常の手続き(back(),pop_back())に至ってはデータ不正のためSegmentation faultを引き起こします。
unique_ptr<A>& _up1= _ss.vector<unique_ptr<A> >::back();// 最後を取り出し _ss.vector<unique_ptr<A> >::pop_back(); // 最後を消去 cout<<_up1->name()<<endl; // 既に_up1の内容は消去されている --- Segmentation fault (コアダンプ)値指向のクラス、例えばstringやintを要素とする場合、back()で取り出すときにコピーされていますので、pop_back()でvector内の値をつぶしても問題ありません。
STLコンテナはそういう値指向なのです。オブジェクト指向のC++のライブラリとしては不適格としか言いようがありません。
C++の悲劇
C++の悲劇は何と言ってもSTLコンテナがオブジェクト指向を完璧に否定するとんでもないものだったことです。
無能なstringも問題ですが、コンテナがちゃんとオブジェクトをハンドリングするものであれば、使われたでしょう。
Javaはコンパイラは低能ですがライブラリは整っていました。
C++は本来この無能なSTLを捨てて、ポインタベースのコンテナを出すべきだったのです。
STLコンテナの無能さをコンパイラで何とかしようというのは間違ったやり方です。
仕様が複雑になり取り扱い困難となり、さらにはエラーメッセージが殆ど解釈不能なものとなります。
| 固定リンク