読者です 読者をやめる 読者になる 読者になる

Move semantics に対応した Boost.Containersとは?

ということで、再びBoost.Containerに戻ってきました。
(コピーコンストラクタと、ムーブコンストラクタについてよくわからない場合は前の記事へ)
ムーブセマンティクスに対応する、というのはどういうことかを考えます。


では、ムーブコンストラクタに対応したクラスをBoost.Containerを利用して定義します。

class MoveSemanticsObject
{
	BOOST_MOVABLE_BUT_NOT_COPYABLE(MoveSemanticsObject)
public:
	MoveSemanticsObject()
	{
		cout << "MoveSemanticsObject::MoveSemanticsObject()" << endl;
	}

	~MoveSemanticsObject()
	{
		cout << "MoveSemanticsObject::~MoveSemanticsObject()" << endl;
	}
	MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject)) {
		
	}
	MoveSemanticsObject& operator=(BOOST_RV_REF(MoveSemanticsObject)) {
		return *this;
	}
};

C++03には右辺値参照はありませんので、
BOOST_RV_REF
がrvalue referenceをエミュレーションしたムーブコンストラクタをつくってくれます。

#ifdef BOOST_HAS_RVALUE_REFS 
#define BOOST_RV_REF(TYPE) TYPE && 
#else 
#define BOOST_RV_REF(TYPE) boost::rv< TYPE >& 
#endif 

こんな感じ。
&& がC++ 0xで正式に対応されるムーブコンストラクタのカタチですが、今は無視してください。
rvalue referenceとかキニシナイ!


で、

typedef boost::container::vector<MoveSemanticsObject> BoostVectorMoveSemanticsObjectList;

int main()
{
	BoostVectorMoveSemanticsObjectList v;
	MoveSemanticsObject m1;
	MoveSemanticsObject m2;
	
	v.push_back(boost::move(m1));
	v.push_back(boost::move(m2));

	return 0;
}

実行。

MoveSemanticsObject::MoveSemanticsObject()
MoveSemanticsObject::MoveSemanticsObject()
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject::~MoveSemanticsObject()
MoveSemanticsObject::~MoveSemanticsObject()
MoveSemanticsObject::~MoveSemanticsObject()
MoveSemanticsObject::~MoveSemanticsObject()
MoveSemanticsObject::~MoveSemanticsObject()

vectorが拡張(リアロケーション)される際に「コピーコンストラクタ」ではなく
「ムーブコンストラクタ」である
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
が呼ばれていますね。
これは「ムーブコンストラクタ」なので所有権を移動すればそれで構いません。


ということは、
vectorが拡張する際に
「複製を大量に作らなければならないコスト」
を抑え、
「所有権を移動するコスト」
だけにすることができます。

では、所有権の移動テスト。
valueの所有権をMoveSemanticsObjectに持たせ、それがどのように移動するかを示したコードです。

#include <boost/container/vector.hpp>
#include <boost/move/move.hpp>

namespace {
int uniqueNumber_ = 100;
}

class MoveSemanticsObject
{
	BOOST_MOVABLE_BUT_NOT_COPYABLE(MoveSemanticsObject)
public:
	MoveSemanticsObject()
	: valueOwnership_(new int(uniqueNumber_++))
	{
		cout << "MoveSemanticsObject::MoveSemanticsObject()" << endl;
	}

	~MoveSemanticsObject()
	{
		cout << "MoveSemanticsObject::~MoveSemanticsObject(ownership:" << (hasOwnership() ? "YES" : "NO") << ")" << endl;
		delete valueOwnership_;
	}
	
	MoveSemanticsObject(const MoveSemanticsObject& rhs)
	{
		cout << "MoveSemanticsObject(const MoveSemanticsObject& rhs)" << endl;
	}
	
	MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject) rhs)
	: valueOwnership_(rhs.valueOwnership_)
	{
		cout << "MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))" << endl;
		rhs.valueOwnership_ = 0;
	}
	
	MoveSemanticsObject& operator=(BOOST_RV_REF(MoveSemanticsObject) rhs) {
		cout << "MoveSemanticsObject& operator=(BOOST_RV_REF(MoveSemanticsObject))" << endl;
		valueOwnership_ = rhs.valueOwnership_;
		rhs.valueOwnership_ = 0;
		return *this;
	}
	int value() const {
		return *valueOwnership_;
	}
	
	bool hasOwnership() const {
		return valueOwnership_ != 0;
	}
private:
	int* valueOwnership_;
};

typedef boost::container::vector<MoveSemanticsObject> BVMOList;
typedef BVMOList::iterator BVMOListIt;

int main()
{
	BVMOList v;
	MoveSemanticsObject m1;
	MoveSemanticsObject m2;
	MoveSemanticsObject m3;
	MoveSemanticsObject m4;
	
	cout << "----- 1st push_back -----" << endl;
	v.push_back(boost::move(m1));
	cout << "----- 2nd push_back -----" << endl;
	v.push_back(boost::move(m2));
	cout << "----- 3rd push_back -----" << endl;
	v.push_back(boost::move(m3));
	cout << "----- 4th push_back -----" << endl;
	v.push_back(boost::move(m4));
	

	cout << "----- vector ownership check -----" << endl;
	for (BVMOListIt it = v.begin(); it != v.end(); ++it) {
		if ((*it).hasOwnership()) {
			cout << (*it).value() << endl;
		} else {
			cout << "Lost Ownership" << endl;
		}
	}
	
	cout << "----- object ownership check -----" << endl;
	if (m1.hasOwnership()) {
		cout << m1.value() << endl;
	} else {
		cout << "Lost Ownership" << endl;
	}

	return 0;
}

結果。
まず、このvectorはpush_backで既に「ムーブコンストラクタ」が呼ばれるので
所有権が「コンテナの中にあるMoveSemanticsObject」に移ります。
これは、m1, m2, m3, m4全てにおいて同様です。
そうして、push_backを続けていくと、
アロケーションが起きるので
ここで新しい領域にvectorがコピーされますが、
ここでも「ムーブコンストラクタ」によって所有権が移動します。
念のためコピーコンストラクタが呼ばれないか監視していますが、呼ばれていません。

ここでもし「コピーコンストラクタ」が呼ばれた場合には、
通常のオブジェクトにおける「コピーコンストラクタ」は「複製しなければならない」に従い、
valueOwnership_を複製しなければなりません。
ここではたかだかintですが、
これでもnewとdeleteの回数が増加することは解ると思います。
このオブジェクトがもし膨大な情報を持っているならば「複製」することはとてもばかげたことかもしれません。
これが「ムーブセマンティクス」ならば単なるポインタのコピーで済みます。


こうしてコストを軽くすることが
「移動」と「コピー」を使い分けを意識できる「ムーブセマンティクス(move semantics)」の利点です。


では、次は右辺値参照(==BOOST_RV_REF==rvalue reference)について調べます。
(続く)

MoveSemanticsObject::MoveSemanticsObject()
MoveSemanticsObject::MoveSemanticsObject()
MoveSemanticsObject::MoveSemanticsObject()
MoveSemanticsObject::MoveSemanticsObject()
----- 1st push_back -----
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
----- 2nd push_back -----
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
----- 3rd push_back -----
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
----- 4th push_back -----
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject(BOOST_RV_REF(MoveSemanticsObject))
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
----- vector ownership check -----
100
101
102
103
----- object ownership check -----
Lost Ownership
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:NO)
MoveSemanticsObject::~MoveSemanticsObject(ownership:YES)
MoveSemanticsObject::~MoveSemanticsObject(ownership:YES)
MoveSemanticsObject::~MoveSemanticsObject(ownership:YES)
MoveSemanticsObject::~MoveSemanticsObject(ownership:YES)