並ゲームプログラマの書き捨て場

ゲームプログラマやってる私の日記、雑記、備忘録、公開場所

unique_ptrでrange-based-for

君の領地はここからここまでだ。djannです。

 

スタック・オーバーフローを見ていたところ、unique_ptr<T[]>でrange-based-forを用いたいという質問があった。というわけで早速作ってみた。

 


#include <iostream>
#include <memory>


// unique_wrapperとmake_unique_wrapperを作るためのヘルパ - かなり手抜きその1.
// 配列か判定する - 結果はvalueが1 or 0で判別.
template <class vType> struct is_array {
	enum { value = 0 };	// falseである.
};
template <class vType> struct is_array<vType[]> {
	enum { value = 1 };	// trueである.
};
// trueのときだけ型が存在する - SFINAEに用いる.
template <bool Condition, class vType>
struct condition_type {};
template <class vType>
struct condition_type<true, vType> {
	using type = vType;
};
// 型から配列要素を抜く - 配列じゃなければ元のままに.
template <class vType>
struct remove_array {
	using type = vType;
};
template <class vType>
struct remove_array<vType[]> {
	using type = vType;
};

// 通常版と配列版を同時にラッピングしている分、若干危険かもしれない・・・?.
template <class vType>
class unique_wrapper : public std::unique_ptr<vType> {
	std::size_t num_array;
	using MyType = unique_wrapper<vType>;
	using PtrType = typename remove_array<vType>::type;
public:
	unique_wrapper(MyType const &) = delete;
	// 1個版と配列版と両方のコンストラクタを持つ - make_unique_wrapperのfriendにしてprivate化するか?.
	unique_wrapper(PtrType *ptr) : std::unique_ptr<vType>(ptr), num_array(1){}
	unique_wrapper(PtrType *ptr, std::size_t num) : std::unique_ptr<vType>(ptr), num_array(num){}
	unique_wrapper(MyType &&rhs) : std::unique_ptr<vType>(rhs.release()), num_array(rhs.num_array){}
	
	// 最低限このようなインターフェースを持つiteratorと
	class iterator {
		PtrType *ptr_;
	public:
		iterator(PtrType *ptr) : ptr_(ptr){}
		~iterator(){}

		PtrType *operator ++(){ ++ptr_; return ptr_; }
		PtrType &operator *(){ return *ptr_; }
		bool operator !=(iterator rhs){ return *ptr_ != *rhs; }
	};
	// 上記を戻すbeginとendというインターフェースがあればrange-based forは動く.
	iterator begin(){ return this->get(); }
	iterator end(){ return this->get() + num_array; }
};

// make_unique_wrapper自体の各種 - かなり手抜きその2.
template <class vType>
unique_wrapper<typename condition_type<is_array<vType>::value == 1, vType>::type> make_unique_wrapper(std::size_t n){
	unique_wrapper<vType> wrap(new typename remove_array<vType>::type[n], n);
	return wrap;
}
template <class vType, class ...Args>
unique_wrapper<typename condition_type<is_array<vType>::value == 0, vType>::type> make_unique_wrapper(Args... args){
	unique_wrapper<vType> wrap(new vType((args)...));
	return wrap;
}


struct Foo {
	~Foo(){
		std::cout << "Foo destructor." << std::endl;
	}
	void print(){
		std::cout << "My name is Foo." << std::endl;
	}
};


int main()
{
	// use test code.
	{
		unique_wrapper<int[]> ptr = make_unique_wrapper<int[]>(7);
		{
			for (auto &n : ptr){
				std::cout << "Please number and enter : ";
				std::cin >> n;
			}
		}

		for (auto n : ptr){
			std::cout << n << std::endl;
		}
		std::cin.get();
		std::cin.get();
	}	// この段階でちゃんとdelete[]が働く.
	{
		// 折角なんでFooさんでテスト.
		unique_wrapper<Foo> ptr = make_unique_wrapper<Foo>();
		ptr->print();
	}

	std::cin.get();
}


 

大事なのは、unique_wrapper::iteratorのoperator overload群とunique_wrapper::beginとunique_wrapper::endを用意することで、後は何も気にせずにrange-based-forに投げ込めるということだ。

現在のC++は標準ライブラリだけでなく、言語機能にもテンプレートが深く入り込んでいるが、このように「特定のインターフェースが存在すれば、派生関係がなくとも同様の機能が使用できる」というのが、実にC++らしくまた、拡張性もよく型安全ないいところだろう。

C++0xの頃、このような特定のインターフェースの群をコンセプトとしてまとめて扱う提案があったという。残念ながら一旦保留という形になったが、今でもまだ議論は続き、いずれC++の標準として定義されると思われる。

 

※ちなみにunique_wrapper::iteratorだが、実は必要ない。というのも、ポインタには各operator++, operator*, operator!=がそれぞれ元から使用可能だからだ。そうはいかないクラスをrange-based-forで用いたいと思った際のたたき台として、今回はわざわざiteratorを用意することにした。