Hatena::ブログ(Diary)

wordiの日記 このページをアンテナに追加 RSSフィード

2005 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2006 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2007 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2009 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2010 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2011 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2012 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11| 12|
2013 | 01| 02| 03| 04| 05| 06| 07| 08| 09| 10| 11|
iPhone プログラミング D言語 日記 ゲーム 仕事 出来事 ねた ソフトウェア 囲碁
 

2013-11-28

[]C++14関数戻り値型推論 C++14関数の戻り値の型推論を含むブックマーク C++14関数の戻り値の型推論のブックマークコメント

戻り値型推論について

C++14から戻り値型推論が使えるようになり、例えば、以下の関数も正常に動作します。

auto getThree() { return 3; } // 戻り値の型はint

Boost Range Ovenを使ったサンプル

上記だけだと何が便利かピンと来ないですが、複雑な型をautoだけで済ませられるようになったという事は、templateを駆使したクラスを戻り値として使うのが手軽になったという事です。

以下がサンプル、環境はGCC 4.8.1、Boost 1.55.0です。

※コンパイル時に-std=c++1yを渡しています。

#include <iostream>
#include <vector>

#include <boost/range/adaptors.hpp>

using namespace std;
using namespace boost;
using namespace boost::adaptors;

template <class Range>
std::vector<typename Range::value_type> to_vector(const Range &r) {
	return {std::begin(r), std::end(r)};
}

class Array {
public:
	Array(std::vector<int> v)
		: v(v)
	{
	}

private:
	auto overQuery(int min) {
		return v
			| filtered([min](int x) {
				return x >= min;
			});
	}

public:
	// 指定の値以上がある?
	bool existOver(int min) {
		// ※boost::emptyは、渡されたRangeのiteratorのbegin、endが同じ位置がチェックしてるだけ
		return not empty(overQuery(min));
	}

	// 指定の値以上の一覧を返す
	std::vector<int> toOver(int min) {
		return to_vector(overQuery(min));
	}

private:
	std::vector<int> v;
};

int main(){

	auto a = Array({1, 2, 3, 4, 5});

	for (auto x : a.toOver(3)) cout << x << " ";
	cout << endl;

	cout << a.existOver(2) << std::endl;
	cout << a.existOver(6) << std::endl;

	return 0;
}

実行結果

3 4 5
1
0

配列に対して、ある値以上を取得したり、存在してるかをチェックしています。

従来だと無駄な処理が発生しないようにexistOverとtoOverそれぞれに似たような処理を書く必要がありました。

それがBoot Range Ovenによって内部関数のoverQueryに纏められ、

無駄な処理を行わずにコードの冗長性を省く事が出来ました、こりゃ便利。

2013-11-16

[]C++LINQライクライブラリC#LINQ覚書き C++のLINQライクライブラリとC#のLINQ覚書きを含むブックマーク C++のLINQライクライブラリとC#のLINQ覚書きのブックマークコメント

概要

LINQを使うに当たって、評価コストやメモリ使用量が気になったので自分用メモ

C#LINQを基準にして考え、cpplinqBoost.RangeのOven拡張の使い方、特性について書いていきます。


C#LINQ

クエリはIEnumerableな配列として作成されます、

作成時にはクエリ式は評価されず、ループやCount()の時に評価されます。

var lst = new [] {1, 2, 3, 4, 5, 6};

int query_counter = 0;
var q = lst
	.Where(x => {
		query_counter++;
		Console.Write(" a:" + x);
		return x % 2 == 0;
	})
	.Where(x => {
		query_counter++;
		Console.Write(" b:" + x);
		return x <= 4;
	})
	.Select(x => {
		query_counter++;
		Console.Write(" c:" + x);
		return x * 2;
	})
	;
Console.WriteLine("maked query");

int loop_counter = 0;
foreach (var x in q) loop_counter++;
Console.WriteLine();

Console.WriteLine("query_counter:" + query_counter + " loop_counter:" + loop_counter);

結果

maked query
 a:1 a:2 b:2 c:2 a:3 a:4 b:4 c:4 a:5 a:6 b:6
query_counter:11 loop_counter:2

C++のcpplinqライブラリ

クエリ*1はbase_rangeを継承した各コンテナクラスで表現されます、

内部では配列iteratorをコンテナへ格納し、クエリ式毎に各クエリ用のコンテナへと評価用のファンクタと共に格納していきます。

スーパークラスのクラス図

f:id:wordi:20131116174858p:image

サブクラスbuilderによって作成され、スーパークラスのrangeとファンクタをメンバに持ってます。

ちなみにcpplinqではbase_rangeがiteratorを持っていない為か、begin、endのメソッドが実装されていない為range-based forが使えず、

ループの時にはfromとnextを使う必要があります。

using namespace std;
using namespace cpplinq;
	
auto v = {1, 2, 3, 4, 5, 6};

using namespace cpplinq;
int query_counter = 0;
auto q = from(v)
	>> where ([&query_counter](const int &x) {
		query_counter++;
		cout << " a:" << x;
		return x % 2 == 0;
	})
	>> where ([&query_counter](const int &x) {
		query_counter++;
		cout << " b:" << x;
		return x <= 4;
	})
	>> select([&query_counter](const int &x) {
		query_counter++;
		cout << " c:" << x;
		return x * 2;
	})
	;
cout << endl << "maked query" << endl;

int loop_counter = 0;
while (q.next()) { const auto &x = q.front(); loop_counter++; }
// ※ここでqの役目は終わる
cout << endl;

cout << "query_counter:" << query_counter << " loop_counter:" << loop_counter << endl;

結果

maked query
 a:1 a:2 b:2 c:2 a:3 a:4 b:4 c:4 a:5 a:6 b:6
query_counter:11 loop_counter:2

C++Boost.RangeのOven拡張

クエリ*2はboost_iterator_rangeを継承した各コンテナクラスで表現されます。

スーパークラスのクラス図

f:id:wordi:20131116184415p:image

サブクラススーパークラスによって作成され、スーパークラスiteratorファンクタをメンバに持ってます。

こっちはcpplinqと違い配列に対してそのまま適用出来、

他とは違い、事前に評価が可能な所は先に評価が実行されます。

auto v = {1, 2, 3, 4, 5, 6};

using namespace boost::adaptors;
using namespace std;

int query_counter = 0;
auto q = v
	| filtered([&query_counter](const int &x) {
		query_counter++;
		cout << " a:" << x;
		return x % 2 == 0;
	})
	| filtered([&query_counter](const int &x) {
		query_counter++;
		cout << " b:" << x;
		return x <= 4;
	})
	| transformed([&query_counter](const int &x) { // 1.54.0からtransformedにもラムダ式使える、素敵
		query_counter++;
		cout << " c:" << x;
		return x * 2;
	})
	;
cout << endl << "maked query" << endl;

int loop_counter = 0;
for (const auto &x : q) loop_counter++;
cout << endl;

cout << "query_counter:" << query_counter << " loop_counter:" << loop_counter << endl;

結果

 a:1 a:2 b:2
maked query
 c:2 a:3 a:4 b:4 c:4 a:5 a:6 b:6
query_counter:11 loop_counter:2

まとめ

C++ではBoost.RangeのOven拡張が定番。

コンパイル時間が気になるならcpplinq*3

・パフォーマンスとメモリ使用量は、どちらも内部でiterator*4進めてファンクタで評価してるだけなので大差ないはず*5

・書き方の違いはどちらもLINQライクなので結局は慣れ。

*1:文脈上こう呼びます、厳密には違う

*2:文脈上ry

*3:Ovenでも無視できる程度だと思う

*4:cpplinqは厳密には違うけど

*5:測定したら、最適化なしはcpplinqが二倍程速く、-O2で最適化すると差が無かった

2013-11-07

[]OpenFLで使えるGUIライブラOpenFLで使えるGUIライブラリを含むブックマーク OpenFLで使えるGUIライブラリのブックマークコメント

このページには保存版として使えるように、黙々とOpenFL*1で使えるGUIを張っていこうかと思ってます

StablexUI

2013/11/08記載

  1. GitHub
  2. ドキュメントも充実
  3. http://lib.haxe.org/に公開されてる中では一番規模大きい?

軽く触ってみた感じ一番高機能。

UIXMLに記述して、それをUIBuilderクラスに食わせるとXMLパースされてUIが生成される仕組み。

デモはスマホUI最適化されてるけど、ゲーム用に特定位置に表示させる事も出来るかも。

後で自分でもUIを書いてみる

haxeui

2013/11/08記載

  1. GitHub
  2. ドキュメント
  3. http://lib.haxe.org/にも公開

こっちはCSSXMLに分かれていて、

XMLはXMLControllerを継承したクラスを用意してパースする、

サンプルを見た感じ、XMLUIのパーツを書いてコード上でそのパーツを配置する、なんて使い方も出来るみたい。

後で自分でもUIを書いてみる

*1HaxeとOpenFLの使い分けに迷ってます、言語の時はHaxe、環境の時はOpenFLで良いのかな?

2013-11-04

[]C++LINQ C++でLINQを含むブックマーク C++でLINQのブックマークコメント

cpplinqというC++11でLinqライクな事が出来るライブラリを使ってみた、という内容です。

なので若干釣り記事です。

コンパイラGCC 4.8.1(MinGW)を使用しています。


cpplinq概要

cpplinqでは、操作用のクラスへRange受け取った後、>>演算子によってクエリ式を実行していき、最終的にlistやvectorで結果を受け取ります、

内部ではクエリ式ごとに専用のクラスへと変換されていきますが、必要分のメンバしか持たない為ある程度コストが抑えられています。

また、C#のLINGと同じく評価されるまでクエリ式が実行されない遅延評価が実装されてます。

auto v = {1, 2, 3};
auto q = from(v) >> where([](const int &x){ cout << "hoge"; return true; }); // まだ未評価なので、hogeは出力されない

結果出力(ここでhogeが3回出力)
for (const auto &x : q >> to_vector()) { ... }

サンプルコード

基本的な使い方はこうなります。

#include <iostream>
#include "cpplinq.hpp"

namespace cpplinq {
	// 固定長配列用
	// @note : from_arrayだとconstな固定長配列を使えない & 関数名を統一する為のヘルパ関数
	template<typename TValueArray>
	CPPLINQ_INLINEMETHOD detail::from_range<typename detail::get_array_properties<TValueArray>::iterator_type> from(
			TValueArray & a 
		) throw ()
	{
		typedef typename std::remove_const<typename std::remove_reference<decltype(*std::begin(a))>::type>::type type;
		return from_iterators(
			const_cast<type *>(std::begin(a)),
			const_cast<type *>(std::end(a))
		);
	}
	
	// initializer_list用
	// @note : initialize_listを直接渡せるようにする為のヘルパ関数
	//         usage : cppinq::from({1, 2, 3, 4, 5})
	template<typename T>
	CPPLINQ_INLINEMETHOD detail::from_range<typename std::initializer_list<T>::const_iterator> from(
			std::initializer_list<T> const &  list
		) throw ()
	{
		return from_iterators(
			list.begin(),
			list.end()
		);
	}
}

int main() {
	
	using namespace cpplinq;
	{
		auto v = {1, 2, 3, 4 ,5};
		// {12.56f, 15.7f}
		auto q = from(v)
			>> where ([](const int &x) { return x > 3; })
			>> select([](const int &x) -> float { return x * 3.14f; })
			>> where ([](const float &x) { return x > 12.0f; }) // ここでは意味ない
			;
	} {
		// {2, 4}
		auto q = from({1, 2, 3, 4, 5})
			>> where([](const int &x) { return x % 2 == 0; })
			;
	}
	return 0;
}

ラムダ式型推論を使う時

ラムダ式型推論を使いたいのですが、autoはC++11ではまだ未実装です、

decltypeがあるにはありますが、Select後等では型が変わる為適用が難しいです、

型が変わる時は推論しないというのも手ですが、一貫性に欠けます。

template <class Range>
auto _f_rangetype(Range &&rng) ->
	typename std::remove_reference<
		decltype(*std::begin(rng))
	>::type
	;
// 配列要素の型取得
#define rangetype(rng) decltype(_f_rangetype(rng))

const int v[] = {1, 2, 3, 4 ,5};
// {12.56f, 15.7f}になる?
auto q = from(v)
	>> where ([](const rangetype(v) &x) { return x > 3; });
	>> select([](const rangetype(v) &x) -> float { return x * 3.14f; })

	//>> where ([](const rangetype(v) &x) { return x > 12.0f; }) // NG.(12.56fがfalseになる)
	>> where([](const float &x) { return x > 12.0f; }) // ここだけ推論してない
	;

受け取ったクエリのfrontメソッドを使えばdecltypeで推論出来ますが、一貫性を持たせようとすると冗長且つめんどくさいです。

template <class Query>
auto _f_querytype(Query &&q) ->
	typename std::remove_const<
		typename std::remove_reference<
			decltype(q.front())
		>::type
	>::type
	;
// クエリの型取得
#define querytype(q) decltype(_f_querytype(q))

const int v[] = {1, 2, 3, 4 ,5};
// {12.56f, 15.7f}
auto q1 = from(v) >> where([](const rangetype(v) &x) { return x > 3; });
auto q2 = q1 >> select([](const querytype(q1) &x) -> float { return x * 3.14f; });
auto q3 = q2 >> where([](const querytype(q2) &x) { return x > 12.0f; });

なので、C++11ではラムダ式には推論はしない方が良いと思います、どうしても適用したい場合は上記の事に注意しながら実装するって事になるでしょう。

まとめ

C++でもLINQを使いたいって時には凄く役立つライブラリだと思いますがC++11時点ではまだ若干使いづらい面があります、

しかし、C++14になれば非常に使えるライブラリだと思います。

2013-10-25

[]Haxe覚書き Haxe覚書きを含むブックマーク Haxe覚書きのブックマークコメント

実に1年と9カ月ぶりのエントリ、私は元気です。

ここでは自分用メモの為にもHaxeを触ってみて、この言語独特だと思うところを書いていきます。

Haxeについての解説は省略

Haxeの型

基本型 - Haxe

Haxeでは、整数値IntはFloatを継承して定義されてます。

Windows用にbuildされたC++のソースを見てみると、Intはint、Floatはfloat or doubleのプリミティブ型でした

※Floatの定義はhxcpp.h


・上記から、数値を扱うGenericな関数を用意する時にはFloatを基底の型として定義します。

function add<T:Float> (lhs:T, rhs:T):T { return lhs + rhs; }

{
  var a:Int = 2;
  var b:Int = 3;
  var c:Int = add(a, b); // 5(Int)
} {
  var a:Int = 2;
  var b:Int = 3;
  var c:Float = add(a, b); // 5(Float)
} {
  var a:Float = 2.1;
  var b:Float = 3.1;
  var c:Int = add(a, b); // compile error.(期待通り)
}

・合計値を算出するsum関数を作る時等は、C#みたいにdefaultキーワードが無いので、少し工夫が必要です。

function sum<T:Float> sum(iterable:Iterable<T>):T {
  var it = iterable.iterator();
  var ret = it.next();
  while (it.hasNext()) ret += it.next();
  return ret;
}

{
  var a:Int = sum([1, 2, 3]); // 6(Int)
} {
  var a:Float = sum([1, 2, 3]); // 6(Float)
} {
  var a:Int = sum([1.1, 2.2, 3.3]); // compile error.(期待通り)
}

試してみたら空配列もいけたのでたぶんこれでOKだと思います。


別解として、戻り値をFloatにするのも手かもしれませんが、個人的にはあまりお勧め出来ないと感じました

function sum<T:Float> sum(iterable:Iterable<T>):Float {
  var ret = 0.0;
  for (x in iterable) ret += x;
  return ret;
}

{
  var a:Int = sum([1, 2, 3]); // compile error.(ダウンキャストが必要)
} {
  var a:Int = cast(sum([1, 2, 3]), Int); // 6
} {
  var a:Int = cast(sum([1.2345, 2, 3]), Int); // run-time error.(一つFloatが紛れてしまってるが、コンパイルは成功してしまい、キャスト時に初めて実行時エラーが出る)
}

sum関数の中身は綺麗に書けるんですが、常にFloatで帰ってくる為、その後にダウンキャストが必要になってしまいます

(ローカルスコープ内でのみ使うのなら、Floatで受け取ってそのまま使ってもあまり問題にならないかもしれませんが)。


LINQライク

Lambdaと、足りない関数を用意すれば、名前は違えどLINQと同じ使い方が出来るとおもいます。

class LambdaExtensions {
  /// @note C# LINQで言うところのSelect
  public static function convert < T, U > (iterable:Iterable<T>, converter:T -> U):Iterable<U> {
    var ret = new Array<U>();
    for (x in iterable) ret.push(converter(x));
    return ret;
  }
  /// @note filterとconvertを別々に実行すると毎回配列が作られてしまう為纏めた
  public static function filterConvert< T, U > (iterable:Iterable<T>, filter:T -> Bool, converter:T -> U):Iterable<U> {
    var v = new Array<U>();
    for (x in iterable) if (filter(x)) v.push(converter(x));
    return v;
  }
}

{
  // [4, 8]
  var ary = [1, 2, 3, 4, 5]
    .filterConvert(
      function(x) { return x % 2 == 0; },
      function(x) { return x * 2; }
    );
}

usingすれば拡張メソッドとして使えるので、名前は違えど使用感はLINQと同じ。


一応hxLINQというライブラリがありますが、

どうやらhaxelibから外れたらしく(haxelib install hxLINQしてもNo such Project : hxLINQと言われる)、

インストールするには、自身でgithubから持ってくる必要があります、

また、hxLINQのselectは中でnullを取り除いていたり、sumの戻り値がFloatになっていたり、関数を呼ぶ度に新しく配列を作り直しているため、

C#LINQと同じ使い方をしていると変にはまったり、乱用するとパフォーマンスに影響が出たりするかもしれません。


とりあえずこんな所です。

2012-01-08

[]C++クロージャ C++でクロージャを含むブックマーク C++でクロージャのブックマークコメント

JavaScriptクロージャを触ってみて、C++でも検証してみました。

結論から先に言うと、参照カウントのスマートポインタ(std::shared_ptrやboost::shared_ptr)を使えば出来ました。

※1/10一部修正、詳しくはコメント欄

クロージャ概要

JavaScriptサンプルソース

var counter = function(n) {
	return function() {
		return ++n;
	};
};

window.onload = function() {
	var hoge = counter(0);
	var foo = counter(10);
	
	document.writeln(hoge() + '<br>');
	document.writeln(hoge() + '<br>');
	document.writeln(hoge() + '<br>');
	
	document.writeln(foo() + '<br>');
	document.writeln(foo() + '<br>');
}

実行結果

1
2
3
11
12

counter()関数はインクリメントされたnを返す関数を返します、この引数nはcounter()関数が呼ばれて関数がreturnされた時にはスコープから外れているはずなんですが、束縛された関数hogeやfooのスコープが外れるまで延命されています。


C++で書くとどうなるか

C++11でラムダ式boost::shared_ptrを使って書き、VC++2010とgcc 4.6.1(MinGW)でビルドして実行できるのを確認しました。

#include <iostream>
#include <functional>

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

std::function<int()> counter(int n)
{
	//boost::shared_ptr<int> value(new int(n));
	auto value = boost::make_shared<int>(n);
	return [=]() -> int {
		return ++(*value);
	};
}

int main()
{
	auto hoge = counter(0);
	auto foo = counter(10);
	
	std::cout << hoge() << std::endl;
	std::cout << hoge() << std::endl;
	std::cout << hoge() << std::endl;

	std::cout << foo() << std::endl;
	std::cout << foo() << std::endl;

	return 0;
}

実行結果

1
2
3
11
12

JavaScriptの時と同じ実行結果になりました、

しかし何故これで動くのか不思議な気がします、ここでラムダ式の外部変数のキャプチャ方法と変数の生存期間についてもう少し突っ込んでみようと思います。


ラムダ式の外部変数のキャプチャ方法

外部変数をキャプチャする時は値コピーと参照が使えますが、値コピーはラムダ式を生成した時点で値コピーされ、ラムダ式内で読取専用の隠しメンバとして使えるようになり、参照は文字通り外部の変数を参照します。

#include <iostream>

int main()
{
	int n = 0;
	auto ref = [&]() {
		// nはmain()関数のnを参照している
		n++;
		std::cout << "ref() n:" << n << std::endl;
	};
	auto copy = [=]() {
		// nはこのラムダ式内用に値コピーされている
		// ラムダ式はconst修飾されている
		//n++; // ← read onlyの為出来ない
		std::cout << "copy() n:" << n << std::endl;
	};

	ref();
	ref();
	copy();
	copy();

	n = 10;

	ref();
	ref();
	copy();
	copy();

	return 0;
}

実行結果

ref() n:1
ref() n:2
copy() n:0
copy() n:0
ref() n:11
ref() n:12
copy() n:0
copy() n:0

ref()は参照、copy()は値コピーをしています、途中でnに10を代入していますが、ref()は実行するたびに参照している為代入が反映され、copy()は関数を作った時点で値コピーが完了している為反映されていません。


ラムダ式の外部変数のキャプチャ時の生存期間

C++では生ポインタを使うのでなければ、スコープから外れた時に自動的に開放されます、これはキャプチャ時も同じです。

下記サンプルでは変数の開放タイミングや値コピーが分かりやすいようにクラスを使っています。

#include <iostream>
#include <functional>

class MyClass
{
private:
	int copyCount; // コピーコンストラクタが呼ばれた回数を記録する
public:
	MyClass() : copyCount(0) { std::cout << "MyClass() copyCount:" << copyCount << std::endl; }
	MyClass(const MyClass &my) : copyCount(my.copyCount + 1) { std::cout << "MyClass(const MyClass &) copyCount:" << copyCount << std::endl; }
	~MyClass() { std::cout << "~MyClass() copyCount:" << copyCount << std::endl; }
	void Hoge() const { std::cout << "Hoge()" << std::endl; }
};

int main()
{
	std::cout << "program start" << std::endl;

	std::cout << "{" << std::endl;
	{

		std::function<void()> ref;
		std::function<void()> copy;
		std::cout << "  {" << std::endl;
		{
			MyClass my;
			std::cout << "ref()関数作成" << std::endl;
			ref = [&]() {
				std::cout << "ref()::";
				my.Hoge();
			};
			std::cout << "copy()関数作成" << std::endl;
			copy = [=]() {
				// myはこのラムダ式内用に値コピーされている
				// ラムダ式はconst修飾されている
				std::cout << "copy()::";
				my.Hoge();
			};
			std::cout << "関数作成完了" << std::endl;

			ref();
			copy();
		}
		std::cout << "  }" << std::endl;

		//ref(); // ← オブジェクトmyは開放されている為アクセス出来ない
		copy();
	}
	std::cout << "}" << std::endl;

	std::cout << "program end" << std::endl;

	return 0;
}

実行結果

program start
{
  {
MyClass() copyCount:0
ref()関数作成
copy()関数作成
MyClass(const MyClass &) copyCount:1
MyClass(const MyClass &) copyCount:2
MyClass(const MyClass &) copyCount:3
MyClass(const MyClass &) copyCount:4
~MyClass() copyCount:3
~MyClass() copyCount:2
~MyClass() copyCount:1
関数作成完了
ref()::Hoge()
copy()::Hoge()
~MyClass() copyCount:0
  }
copy()::Hoge()
~MyClass() copyCount:4
}
program end

VC++2010では何故か値コピーが4回実行されています(gccでは3回)、

※原因わかりました、詳しくは追記を参照

しかしそれ以外は予想通りで、myオブジェクトはスコープから外れた時点で開放され、値コピーされたmyオブジェクトも束縛元であるcopy()関数が開放された時に開放されています。


shared_ptrを使うとどうなるか

上記二つのサンプルを見ましたが、クロージャを使う為には、読取専用では「ない」外部の変数をキャプチャし、束縛した関数がスコープから外れるまで延命する必要があります、値コピーのキャプチャと参照のキャプチャの良いとこ取りな動作が必要ですがどうしましょう、ってなわけでshared_ptrが登場します。

#include <iostream>
#include <functional>

#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

class MyClass
{
public:
	MyClass() { std::cout << "MyClass()" << std::endl; }
	~MyClass() { std::cout << "~MyClass()" << std::endl; }
	void Hoge() { std::cout << "Hoge()" << std::endl; }
};

int main()
{
	std::cout << "program start" << std::endl;
	std::cout << "{" << std::endl;
	{

		std::function<void()> foo;
		std::cout << "  {" << std::endl;
		{
			//boost::shared_ptr<MyClass> my(new MyClass());
			auto my = boost::make_shared<MyClass>();
			std::cout << "f()関数作成" << std::endl;
			foo = [=]() {
				std::cout << "foo()::";
				my->Hoge();
			};
			std::cout << "関数作成完了" << std::endl;

			foo();
		}
		std::cout << "  }" << std::endl;

		foo();
	}
	std::cout << "}" << std::endl;
	std::cout << "program end" << std::endl;

	return 0;
}

実行結果

program start
{
  {
MyClass()
f()関数作成
関数作成完了
foo()::Hoge()
  }
foo()::Hoge()
~MyClass()
}
program end

shared_ptrは参照カウントの為、すべての参照元がスコープから外れた時点で初めて開放されます、なのでキャプチャしたい変数をshared_ptrにして値コピーしておけば、大本がスコープから外れてもキャプチャ側がshared_ptrを持っている為開放されずにアクセスでき、キャプチャ側が開放されればshared_ptrもスコープから外れて開放されるためメモリリークの心配も無く安心です。


実行時にはクロージャとして振る舞えてますが、中の仕組みはC++のままで動いてます、なのでRAIIな設計を保ったままクロージャも使えます、やったね!


追記

キャプチャするラムダ式が2つ以上ならshared_ptrでもいいですが、1つでキャプチャする変数もintやdoubleなどの基本型の場合はオーバースペックです、というのもラムダ式の実態は関数オブジェクトで値コピーの時はキャプチャ対象をメンバとしてコピーしています。

int n;
auto hoge = [n]() { std::cout << "hoge() n:" << n << std::endl; }

int n;
class F {
	int n;
public:
	F(int n) : n(n) {}
	F(F && other) : n(static_cast<int&&>(other.n)) {}
	F &operator=(const F&) = delete;

	void operator()() const
	{
		std::cout << "hoge() n:" << n << std::endl;
	}

};
auto hoge = F(n);

とも書けるんですね、なのでキャプチャ箇所が1箇所でキャプチャ対象が基本型ならmutableで関数オブジェクトconstをはずしてやればいいです。

int n;
auto hoge = [n]() mutable {
	n++; // ← 出来る
	std::cout << "hoge() n:" << n << std::endl;
}

参考

http://d.hatena.ne.jp/faith_and_brave/20081211/1228989087


これがクラスでコピーコンストラクタが複数回走ってた原因でした。

gintenlabogintenlabo 2012/01/09 23:00 複数の箇所から使い回さない場合には
std::function<int()> counter(int n) {
return [n]() mutable -> int {
return ++n;
};
}
で十分な気もします.
あと new より make_shared を使ったほうが良いですよー.

wordiwordi 2012/01/10 01:25 こんな書き方が出来るんですね
調べてみたらキャプチャされたメンバは非constでラムダ式自体がconstな関数オブジェクト何ですね。。。
で、メンバにmutableを指定していると、なるほです。
情報ありがとうございます。
> あと new より make_shared を使ったほうが良いですよー.
みたいですね、書き直します。

gintenlabogintenlabo 2012/01/10 02:04 別に mutable なラムダはメンバ変数に mutable を付けてるわけじゃないですよー.
通常の( mutable でない)ラムダ式は, operator() が const 修飾されている(のでキャプチャされた変数は変更できない)のですが,
mutable を付けたラムダ式は operator() が const 修飾されていないので,キャプチャした変数を変更できる(代わりに const 参照経由で operator() を呼び出せない)ってことです.

wordiwordi 2012/01/10 08:14 ああ、なるほど。。。

2011-12-29

[][]Singletonパターンを色々検証してみた Singletonパターンを色々検証してみたを含むブックマーク Singletonパターンを色々検証してみたのブックマークコメント

Singletonクラスに依存関係のあるメンバを置く際にどうやればいいのか色々検証してみました


C++Singletonパターンを書く時の基本のコード

class Singleton
{
private:
	static Singleton *me;
	Singleton() { std::cout << "Singleton()" << std::endl; }
	~Singleton() { std::cout << "~Singleton()" << std::endl; }
public:
	static Singleton *GetInstance() {
		if (me == 0)
			me = new Singleton();
		return me;
	}
	static void DeleteInstance() {
		if (me != 0)
			delete me;
	}
	int x;
	void hoge() { std::cout << "hoge()" << std::endl; }
};
Singleton *Singleton::me = 0;

int main()
{
	Singleton::GetInstance()->x = 3;
	std::cout << "x:" << Singleton::GetInstance()->x << std::endl;
	Singleton::GetInstance()->hoge();
	Singleton::DeleteInstance();

	std::cout << "program end" << std::endl;
	return 0;
}

実行結果

Singleton()
x:3
hoge()
~Singleton()
program end

このやり方はプログラムの終了前にdeleteされてますので依存関係のあるメンバを置くことが出来ます、

しかし明示的に開放してあげないといけないため、C++の流儀に合っていませんし、生ポインタもむき出しです。


スマートポインタを使って自動的に開放してみる

まずはboost::shared_ptrを使った実装

class Singleton
{
private:
	static boost::shared_ptr<Singleton> me;
	Singleton() { std::cout << "Singleton()" << std::endl; }
	~Singleton() { std::cout << "~Singleton()" << std::endl; }
public:
	static boost::shared_ptr<Singleton> &GetInstance() {
		if (me.get() == 0)
			me.reset(new Singleton(), [](Singleton *p) { delete p; } );
		return me;
	}
	int x;
	void hoge() { std::cout << "hoge()" << std::endl; }
};
boost::shared_ptr<Singleton> Singleton::me;

int main()
{
	Singleton::GetInstance()->x = 3;
	std::cout << "x:" << Singleton::GetInstance()->x << std::endl;
	Singleton::GetInstance()->hoge();

	std::cout << "program end" << std::endl;
	return 0;
}

実行結果

Singleton()
x:3
hoge()
program end
~Singleton()

ラムダ式クロージャの役割を果たしてくれてますのでprivateメンバのmeにもアクセス出来ています


ただboost::shared_ptrは参照カウントなので、Singletonなクラスにはstd::unique_ptrやboost::interprocess::unique_ptrを使った方がスマートかと思います

って事でboost::interprocess::unique_ptrの実装

class Singleton
{
private:
	struct SingletonDeleter
	{
		void operator()(Singleton *p) { delete p; }
	};
	typedef boost::interprocess::unique_ptr<Singleton, SingletonDeleter> SingletonPtr;
	static SingletonPtr me;
	Singleton() { std::cout << "Singleton()" << std::endl; }
	~Singleton() { std::cout << "~Singleton()" << std::endl; }
public:

	static SingletonPtr &GetInstance() {
		if (me.get() == 0)
			me.reset(new Singleton());
		return me;
	}
	int x;
	void hoge() { std::cout << "hoge()" << std::endl; }
};
Singleton::SingletonPtr Singleton::me;

int main()
{
	// boost::shared_ptrの時と同じ
}

実行結果

boost::shared_ptrの時と同じ

この二つのやり方は自動的にdeleteをしてくれているものの、グローバル領域の為開放順序が定まっておらず、

依存関係のあるクラスメンバがあるとdeleteの途中でエラーが発生したりする場合があります、

ただし大抵の本やサイトではこのような書き方をしてSingletonに依存関係のあるメンバは持たすな、という意見で一致してるように思います。


って事で解決法

色々検証した結果、したのような書き方になりました

class Singleton
{
private:
	struct SingletonDeleter
	{
		void operator()(Singleton *p) { delete p; }
	};
	typedef boost::interprocess::unique_ptr<Singleton, SingletonDeleter> SingletonPtr;
	static SingletonPtr me;
	Singleton() { std::cout << "Singleton()" << std::endl; }
	~Singleton() { std::cout << "~Singleton()" << std::endl; }
public:

	static SingletonPtr &GetInstance() {
		if (me.get() == 0)
			me.reset(new Singleton());
		return me;
	}
	static void DeleteInstance() { if (me.get() != 0) me.reset(); }
	int x;
	void hoge() { std::cout << "hoge()" << std::endl; }
};
Singleton::SingletonPtr Singleton::me;

int main()
{
	{
		struct Caller {
			void operator()(int *p) {
				delete p;
				Singleton::DeleteInstance();
			}
		};
		boost::interprocess::unique_ptr<int, Caller> caller(new int(-1)); // int型はdummy

		Singleton::GetInstance()->x = 3;
		std::cout << "x:" << Singleton::GetInstance()->x << std::endl;
		Singleton::GetInstance()->hoge();
	}

	std::cout << "program end" << std::endl;
	return 0;
}

実行結果

Singleton()
x:3
hoge()
~Singleton()
program end

スマートポインタプログラム終了前のコールバック関数として使うってやり方です、

少し変な書き方なので突っ込み待ち

2011-11-29

[]MinGWBoost 1.48.0を使う MinGWでBoost 1.48.0を使うを含むブックマーク MinGWでBoost 1.48.0を使うのブックマークコメント

ビルドが必要なboost::threadやboost::regexを使いたい時にはまったので私的メモ、

試したOSはWindows7

環境

gcc 4.6.1(MinGW)(※リンクはTopページ)

Boost 1.48.0

当然ながらMinGWのbinにはパスを通しておく。

手順

解凍したboostのパスにて、以下のコマンドでビルド用のbjam.exeを作る

bootstrap.bat gcc

生成されたbjam.exeを使い、ライブラリビルドする

bjam.exe -sTOOLS=mingw --toolset=gcc --stagedir="boostのパス\stage\mingw" --without-python --without-mpi --build-type=complete variant=debug,release link=static threading=multi runtime-link=static release debug stage -j4

ビルドするのは静的リンクのみ、理由は後述

※-jnでビルド時の並列処理数を記述、この辺はビルドを行うマシンのスペックと相談


次にboost::threadをincludeした時にwarningが出ないように、以下のパッチを施す、

参考にしたのはここ

ファイルのパスは boostへのパス\boost\thread\win32\thread_heap_alloc.hpp

行数は59行目辺り

//        inline BOOST_THREAD_DECL void* allocate_raw_heap_memory(unsigned size)
        inline void* allocate_raw_heap_memory(unsigned size)
    	{
            void* const heap_memory=detail::win32::HeapAlloc(detail::win32::GetProcessHeap(),0,size);
            if(!heap_memory)
            {
                boost::throw_exception(std::bad_alloc());
            }
            return heap_memory;
        }

//        inline BOOST_THREAD_DECL void free_raw_heap_memory(void* heap_memory)
        inline void free_raw_heap_memory(void* heap_memory)
        {
            BOOST_VERIFY(detail::win32::HeapFree(detail::win32::GetProcessHeap(),0,heap_memory)!=0);
        }

ここまでで準備完了。


次にboost::threadを使ったサンプルをBoostライブラリを静的リンクでビルドして実行してみる。

ソースは、以前書いたSleep Sortのソースを使おう。

sleepsort.cpp

#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind/bind.hpp>


#define BOOST_SLEEP(n) boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds((n)))

std::vector<int> SleepSort(std::vector<int> v)
{
    boost::mutex m;
    std::vector<int> ret;

    std::function<void(int)> f = [&](int n) {
        BOOST_SLEEP(n * 100);

        {
            boost::mutex::scoped_lock lock(m);
            ret.push_back(n);
        }
    };


    boost::thread_group group;

    std::for_each(v.begin(), v.end(), [&](int n) {
        group.create_thread(boost::bind(f, n));
    });

    group.join_all();

    return ret;
}

int main()
{
    int a[] = {3, 1, 8, 7, 2, 4, 9, 6, 0, 5};
    int n = sizeof(a) / sizeof(a[0]);
    std::vector<int> v(a, a + n);

    v = SleepSort(std::move(v));

    std::for_each(v.begin(), v.end(), [](int n) {
        std::cout << n << " ";
    });

    return 0;
}

ビルドする時は以下のコマンド

g++ sleepsort.cpp -o sleepsort.exe -std=c++0x -DBOOST_THREAD_USE_LIB -Iboostのincludeパス -Lboostのlibパス -lboost_thread-mgwXX-mt-s-X_XX

MinGWではVisual Studioの時とは違い、-lオプションで明示的にライブラリを指定する必要がある(参考)

※静的リンクなので、 libファイル(MinGWでは.a)はmt-s-X_XXのようになっているのに注意

※静的リンクの時には、 -DBOOST_THREAD_USE_LIB オプションを付加する


これでsleepsort.exeを実行すれば実行結果が確認できる。


はまった事

Boostライブラリビルドする際、動的リンク用も生成するように

bjam -sTOOLS=mingw --toolset=gcc --stagedir="boostのパス\stage\mingw" --without-python --without-mpi --build-type=complete variant=debug,release link=static,shared threading=multi runtime-link=static,shared release debug stage -j4

とすると、Boostライブラリの静的リンク用を使ってサンプルをビルドする際に

MinGWのld.exeがboost_thread-mgwXX-mt-s-X_XXが見つからないというエラーを出した。

なのでBoostライブラリを静的リンクで使う際には、Boostライブラリは静的リンク用だけビルドした方がいいかもしれない。

2011-10-31

[][]atwikijQuery pluginのtablesorterを使ってテーブルをソート対応してみた atwikiでjQuery pluginのtablesorterを使ってテーブルをソート対応してみたを含むブックマーク atwikiでjQuery pluginのtablesorterを使ってテーブルをソート対応してみたのブックマークコメント


対応させる為に必要なファイル

必要なファイルをダウンロードする

これらを作成したwikiから見えるパスへ準備しておく

(今回は同じwiki内へ用意)


概要

atwiki記法でテーブルを書くと、

|項目1|項目2|
|1|2|
|5|3|

table タグでテーブルが作られる

項目1項目2
12
53

ソースはこのようになっている

<table>
	<tr><td>項目1</td><td>項目2</td></tr>
	<tr><td>1</td><td>2</td></tr>
	<tr><td>5</td><td>3</td></tr>
</table>

atwiki記法で作られたテーブルは簡易的なものなので、tablesorterに対応させる為、table タグにclass="tablesorter"を追加、thead タグ、 tbody タグを追加、column部分の tr タグ内の td を th へ修正する必要がある。

<table class="tablesorter">
  <thead>
	<tr><th>項目1</th><th>項目2</th></tr>
  </thead>
  <tbody>
	<tr><td>1</td><td>2</td></tr>
	<tr><td>5</td><td>3</td></tr>
  </tbody>
</table>

対応方法

JavaScriptを使い上記の修正を施す、

デモページのソース全文はこちら*1

jQuery plugin tablesorterのデモ

<!-- これが元になるテーブル、atwiki記法で書かれています -->
#divid(tablesorter-src){
|名前|戦闘力|Lv|
|クリ○ン|600|20|
|ゴク○|800|25|
|ピッ○ロさん|780|23|
|ヤム○ャ|380|33|
}


#divid(makeTableButton){
}

<!-- ここにソート対応したテーブルが作られます -->
#divid(tablesorter-dest){
}


// -------------------- ここからしたは弄る必要なし -----------------
#include_js(http://www18.atwiki.jp/worditest/pub/jquery.min.js)
#include_js(http://www18.atwiki.jp/worditest/pub/jquery.tablesorter.js)
#javascript(){
	// ボタン作成
	$("#makeTableButton").append("<input type='button' value='ソート対応のテーブルを作成' onclick='makeTable()' />");

	function makeTable() {

	  // 移動元と移動先のテーブル
	  var src = $("#tablesorter-src").children("table");
	  $("#tablesorter-dest").append("<table>");
	  var dest = $("#tablesorter-dest").children("table");

	  // 移動先テーブルの準備
	  dest.addClass("tablesorter");
	  dest.append("<thead id='tableHead'>");
	  dest.append("</thead>");
	  dest.append("<tbody id='tableBody'>");
	  dest.append("</tbody>");

	  // 移動元から移動先へ
	  srcHead = src.find(".atwiki_tr_1");
	  srcHead.html(srcHead.html().replace(/td/ig, "th"));
	  srcHead.appendTo("#tableHead");
	  src.find("tr").appendTo("#tableBody");

	  // 準備が出来たのでソート対応へ
	  dest.tablesorter({sortList:[[0,0]], widgets: ['zebra']});

	  // 移動後に必要ないものの後始末
	  $("#tablesorter-src").empty();
	  $("#makeTableButton").html("<input type='button' value='作成完了!' disabled />");
	}
}

不満点

セキュリティ上仕方ないかもしれないが、atwikiの編集モードを管理者のみにしていないとJavaScriptが有効にならない、誰でもページを編集出来て、且つセキュリティ維持する良い方法無いだろうか。

*1JavaScriptを有効にする為には編集権限を管理者のみにする必要があるけど、そうするとソースが読めない為ここに書いておく

2011-06-30

[]演算から見るC++0x rvalue referenceのパフォーマンスチェック(後編) 演算から見るC++0x rvalue referenceのパフォーマンスチェック(後編)を含むブックマーク 演算から見るC++0x rvalue referenceのパフォーマンスチェック(後編)のブックマークコメント

前編からの続き

コード

今回は4x4の行列クラスを乗算した時のそれぞれのパフォーマンスを検証します。


行列クラスのヘッダファイル

Matrix.h

#ifndef MATRIX_H__
#define MATRIX_H__

// C++0xで導入された rvalue reference を用いる場合はON
#define RVALUE_REFERENCE

class Matrix44
{
public:
	Matrix44();
	Matrix44(
	    float nm00, float nm01, float nm02, float nm03,
	    float nm10, float nm11, float nm12, float nm13,
	    float nm20, float nm21, float nm22, float nm23,
	    float nm30, float nm31, float nm32, float nm33
	);
	Matrix44(const Matrix44 &t);
	~Matrix44();

	Matrix44 &operator=(const Matrix44 &rhs);
	Matrix44 operator*(const Matrix44 &rhs) const;
	void Set(
	    float nm00, float nm01, float nm02, float nm03,
	    float nm10, float nm11, float nm12, float nm13,
	    float nm20, float nm21, float nm22, float nm23,
	    float nm30, float nm31, float nm32, float nm33
	);

#ifdef RVALUE_REFERENCE
	Matrix44(Matrix44 && t);
	Matrix44 &operator=(Matrix44 && rhs);
	// a  *  (b * c) の様に、右辺が一時オブジェクトの時に使う
	// ※今回のサンプルでは使っていない、資料用
	Matrix44 && operator*(Matrix44 && rhs) const;
	// (a * b)  *  c の様に、左辺が一時オブジェクトの時に使う
	friend Matrix44 && operator*(Matrix44 && lhs, const Matrix44 &rhs);
	// (a * b)  *  (c * d) の様に、両辺一時オブジェクトの時に使う
	// ※今回のサンプルでは使っていない、資料用
	friend Matrix44 && operator*(Matrix44 && lhs, Matrix44 && rhs);
#endif

	// コンストラクタが呼ばれた回数をカウントする
	static long constructorCount;

	friend void Matrix44Mul(Matrix44 &out, const Matrix44 &m1, const Matrix44 &m2);

private:
	union U
	{
		struct
		{
			float m00, m01, m02, m03;
			float m10, m11, m12, m13;
			float m20, m21, m22, m23;
			float m30, m31, m32, m33;
		};
		float m[16];
	};

	// ※std::swapで渡せるようにしておく
	U u;
};

#endif // MATRIX_H__


行列クラスのソースファイル

Matrix.cpp

#include "Matrix.h"

#include <iostream>

long Matrix44::constructorCount = 0;

// 処理を無効化するかどうか
// ※一時オブジェクトや関数のオーバーヘッド計測をする時に有効化します
//#define PROC_DISABLE

Matrix44::Matrix44()
{
#ifndef PROC_DISABLE
	constructorCount++;
	u.m00 = 1.f; u.m01 = 0.f; u.m02 = 0.f; u.m03 = 0.f;
	u.m10 = 0.f; u.m11 = 1.f; u.m12 = 0.f; u.m13 = 0.f;
	u.m20 = 0.f; u.m21 = 0.f; u.m22 = 1.f; u.m23 = 0.f;
	u.m30 = 0.f; u.m31 = 0.f; u.m32 = 0.f; u.m33 = 1.f;
#endif
	//std::cout << "Matrix44()" << std::endl;
}

Matrix44::Matrix44(
    float nm00, float nm01, float nm02, float nm03,
    float nm10, float nm11, float nm12, float nm13,
    float nm20, float nm21, float nm22, float nm23,
    float nm30, float nm31, float nm32, float nm33
)
{
#ifndef PROC_DISABLE
	constructorCount++;
	u.m00 = nm00; u.m01 = nm01; u.m02 = nm02; u.m03 = nm03;
	u.m10 = nm10; u.m11 = nm11; u.m12 = nm12; u.m13 = nm13;
	u.m20 = nm20; u.m21 = nm21; u.m22 = nm22; u.m23 = nm23;
	u.m30 = nm30; u.m31 = nm31; u.m32 = nm32; u.m33 = nm33;
#endif
	//std::cout << "Matrix44(float nm00-nm33)" << std::endl;
}

Matrix44::Matrix44(const Matrix44 &t)
{
#ifndef PROC_DISABLE
	constructorCount++;
	u.m[0] = t.u.m[0]; u.m[1] = t.u.m[1]; u.m[2] = t.u.m[2]; u.m[3] = t.u.m[3];
	u.m[4] = t.u.m[4]; u.m[5] = t.u.m[5]; u.m[6] = t.u.m[6]; u.m[7] = t.u.m[7];
	u.m[8] = t.u.m[8]; u.m[9] = t.u.m[9]; u.m[10] = t.u.m[10]; u.m[11] = t.u.m[11];
	u.m[12] = t.u.m[12]; u.m[13] = t.u.m[13]; u.m[14] = t.u.m[14]; u.m[15] = t.u.m[15];
#endif
	//std::cout << "Matrix44(const Matrix44 &t)" << std::endl;
}

Matrix44::~Matrix44() { }

Matrix44 &Matrix44::operator=(const Matrix44 &rhs)
{
#ifndef PROC_DISABLE
	u.m[0] = rhs.u.m[0]; u.m[1] = rhs.u.m[1]; u.m[2] = rhs.u.m[2]; u.m[3] = rhs.u.m[3];
	u.m[4] = rhs.u.m[4]; u.m[5] = rhs.u.m[5]; u.m[6] = rhs.u.m[6]; u.m[7] = rhs.u.m[7];
	u.m[8] = rhs.u.m[8]; u.m[9] = rhs.u.m[9]; u.m[10] = rhs.u.m[10]; u.m[11] = rhs.u.m[11];
	u.m[12] = rhs.u.m[12]; u.m[13] = rhs.u.m[13]; u.m[14] = rhs.u.m[14]; u.m[15] = rhs.u.m[15];
#endif
	//std::cout << "operator=(const Matrix44 &rhs)" << std::endl;
	return *this;
}

Matrix44 Matrix44::operator*(const Matrix44 &rhs) const
{
	//std::cout << "operator*(const Matrix44 &rhs)" << std::endl;
#ifdef PROC_DISABLE
	return Matrix44();
#else
	return Matrix44(
	           u.m00 * rhs.u.m00 + u.m01 * rhs.u.m10 + u.m02 * rhs.u.m20 + u.m03 * rhs.u.m30,
	           u.m00 * rhs.u.m01 + u.m01 * rhs.u.m11 + u.m02 * rhs.u.m21 + u.m03 * rhs.u.m31,
	           u.m00 * rhs.u.m02 + u.m01 * rhs.u.m12 + u.m02 * rhs.u.m22 + u.m03 * rhs.u.m32,
	           u.m00 * rhs.u.m03 + u.m01 * rhs.u.m13 + u.m02 * rhs.u.m23 + u.m03 * rhs.u.m33,

	           u.m10 * rhs.u.m00 + u.m11 * rhs.u.m10 + u.m12 * rhs.u.m20 + u.m13 * rhs.u.m30,
	           u.m10 * rhs.u.m01 + u.m11 * rhs.u.m11 + u.m12 * rhs.u.m21 + u.m13 * rhs.u.m31,
	           u.m10 * rhs.u.m02 + u.m11 * rhs.u.m12 + u.m12 * rhs.u.m22 + u.m13 * rhs.u.m32,
	           u.m10 * rhs.u.m03 + u.m11 * rhs.u.m13 + u.m12 * rhs.u.m23 + u.m13 * rhs.u.m33,

	           u.m20 * rhs.u.m00 + u.m21 * rhs.u.m10 + u.m22 * rhs.u.m20 + u.m23 * rhs.u.m30,
	           u.m20 * rhs.u.m01 + u.m21 * rhs.u.m11 + u.m22 * rhs.u.m21 + u.m23 * rhs.u.m31,
	           u.m20 * rhs.u.m02 + u.m21 * rhs.u.m12 + u.m22 * rhs.u.m22 + u.m23 * rhs.u.m32,
	           u.m20 * rhs.u.m03 + u.m21 * rhs.u.m13 + u.m22 * rhs.u.m23 + u.m23 * rhs.u.m33,

	           u.m30 * rhs.u.m00 + u.m31 * rhs.u.m10 + u.m32 * rhs.u.m20 + u.m33 * rhs.u.m30,
	           u.m30 * rhs.u.m01 + u.m31 * rhs.u.m11 + u.m32 * rhs.u.m21 + u.m33 * rhs.u.m31,
	           u.m30 * rhs.u.m02 + u.m31 * rhs.u.m12 + u.m32 * rhs.u.m22 + u.m33 * rhs.u.m32,
	           u.m30 * rhs.u.m03 + u.m31 * rhs.u.m13 + u.m32 * rhs.u.m23 + u.m33 * rhs.u.m33
	       );
#endif
}

void Matrix44::Set(
    float nm00, float nm01, float nm02, float nm03,
    float nm10, float nm11, float nm12, float nm13,
    float nm20, float nm21, float nm22, float nm23,
    float nm30, float nm31, float nm32, float nm33
)
{
#ifndef PROC_DISABLE
	u.m00 = nm00; u.m01 = nm01; u.m02 = nm02; u.m03 = nm03;
	u.m10 = nm10; u.m11 = nm11; u.m12 = nm12; u.m13 = nm13;
	u.m20 = nm20; u.m21 = nm21; u.m22 = nm22; u.m23 = nm23;
	u.m30 = nm30; u.m31 = nm31; u.m32 = nm32; u.m33 = nm33;
#endif
}

#ifdef RVALUE_REFERENCE
Matrix44::Matrix44(Matrix44 && t)
{
#ifndef PROC_DISABLE
	constructorCount++;
	//std::swap(u, t.u);
	// 正しくは↑swapだが、 ↓代入の方が若干早い
	u.m[0] = t.u.m[0]; u.m[1] = t.u.m[1]; u.m[2] = t.u.m[2]; u.m[3] = t.u.m[3];
	u.m[4] = t.u.m[4]; u.m[5] = t.u.m[5]; u.m[6] = t.u.m[6]; u.m[7] = t.u.m[7];
	u.m[8] = t.u.m[8]; u.m[9] = t.u.m[9]; u.m[10] = t.u.m[10]; u.m[11] = t.u.m[11];
	u.m[12] = t.u.m[12]; u.m[13] = t.u.m[13]; u.m[14] = t.u.m[14]; u.m[15] = t.u.m[15];
#endif
	//std::cout << "Matrix44(Matrix44 && t)" << std::endl;
}

Matrix44 &Matrix44::operator=(Matrix44 && rhs)
{
#ifndef PROC_DISABLE
	//std::swap(u, rhs.u);
	// 正しくは↑swapだが、 ↓代入の方が若干早い
	u.m[0] = rhs.u.m[0]; u.m[1] = rhs.u.m[1]; u.m[2] = rhs.u.m[2]; u.m[3] = rhs.u.m[3];
	u.m[4] = rhs.u.m[4]; u.m[5] = rhs.u.m[5]; u.m[6] = rhs.u.m[6]; u.m[7] = rhs.u.m[7];
	u.m[8] = rhs.u.m[8]; u.m[9] = rhs.u.m[9]; u.m[10] = rhs.u.m[10]; u.m[11] = rhs.u.m[11];
	u.m[12] = rhs.u.m[12]; u.m[13] = rhs.u.m[13]; u.m[14] = rhs.u.m[14]; u.m[15] = rhs.u.m[15];
#endif
	//std::cout << "operator=(Matrix44 && rhs)" << std::endl;
	return *this;
}

Matrix44 && Matrix44::operator*(Matrix44 && rhs) const
{
#ifndef PROC_DISABLE
	rhs.Set(
	           u.m00 * rhs.u.m00 + u.m01 * rhs.u.m10 + u.m02 * rhs.u.m20 + u.m03 * rhs.u.m30,
	           u.m00 * rhs.u.m01 + u.m01 * rhs.u.m11 + u.m02 * rhs.u.m21 + u.m03 * rhs.u.m31,
	           u.m00 * rhs.u.m02 + u.m01 * rhs.u.m12 + u.m02 * rhs.u.m22 + u.m03 * rhs.u.m32,
	           u.m00 * rhs.u.m03 + u.m01 * rhs.u.m13 + u.m02 * rhs.u.m23 + u.m03 * rhs.u.m33,

	           u.m10 * rhs.u.m00 + u.m11 * rhs.u.m10 + u.m12 * rhs.u.m20 + u.m13 * rhs.u.m30,
	           u.m10 * rhs.u.m01 + u.m11 * rhs.u.m11 + u.m12 * rhs.u.m21 + u.m13 * rhs.u.m31,
	           u.m10 * rhs.u.m02 + u.m11 * rhs.u.m12 + u.m12 * rhs.u.m22 + u.m13 * rhs.u.m32,
	           u.m10 * rhs.u.m03 + u.m11 * rhs.u.m13 + u.m12 * rhs.u.m23 + u.m13 * rhs.u.m33,

	           u.m20 * rhs.u.m00 + u.m21 * rhs.u.m10 + u.m22 * rhs.u.m20 + u.m23 * rhs.u.m30,
	           u.m20 * rhs.u.m01 + u.m21 * rhs.u.m11 + u.m22 * rhs.u.m21 + u.m23 * rhs.u.m31,
	           u.m20 * rhs.u.m02 + u.m21 * rhs.u.m12 + u.m22 * rhs.u.m22 + u.m23 * rhs.u.m32,
	           u.m20 * rhs.u.m03 + u.m21 * rhs.u.m13 + u.m22 * rhs.u.m23 + u.m23 * rhs.u.m33,

	           u.m30 * rhs.u.m00 + u.m31 * rhs.u.m10 + u.m32 * rhs.u.m20 + u.m33 * rhs.u.m30,
	           u.m30 * rhs.u.m01 + u.m31 * rhs.u.m11 + u.m32 * rhs.u.m21 + u.m33 * rhs.u.m31,
	           u.m30 * rhs.u.m02 + u.m31 * rhs.u.m12 + u.m32 * rhs.u.m22 + u.m33 * rhs.u.m32,
	           u.m30 * rhs.u.m03 + u.m31 * rhs.u.m13 + u.m32 * rhs.u.m23 + u.m33 * rhs.u.m33
	);
#endif
	//std::cout << "operator*(Matrix44 && rhs)" << std::endl;
	return std::move(rhs);
}

Matrix44 && operator*(Matrix44 && lhs, const Matrix44 &rhs)
{
#ifndef PROC_DISABLE
	lhs.Set(
	           lhs.u.m00 * rhs.u.m00 + lhs.u.m01 * rhs.u.m10 + lhs.u.m02 * rhs.u.m20 + lhs.u.m03 * rhs.u.m30,
	           lhs.u.m00 * rhs.u.m01 + lhs.u.m01 * rhs.u.m11 + lhs.u.m02 * rhs.u.m21 + lhs.u.m03 * rhs.u.m31,
	           lhs.u.m00 * rhs.u.m02 + lhs.u.m01 * rhs.u.m12 + lhs.u.m02 * rhs.u.m22 + lhs.u.m03 * rhs.u.m32,
	           lhs.u.m00 * rhs.u.m03 + lhs.u.m01 * rhs.u.m13 + lhs.u.m02 * rhs.u.m23 + lhs.u.m03 * rhs.u.m33,

	           lhs.u.m10 * rhs.u.m00 + lhs.u.m11 * rhs.u.m10 + lhs.u.m12 * rhs.u.m20 + lhs.u.m13 * rhs.u.m30,
	           lhs.u.m10 * rhs.u.m01 + lhs.u.m11 * rhs.u.m11 + lhs.u.m12 * rhs.u.m21 + lhs.u.m13 * rhs.u.m31,
	           lhs.u.m10 * rhs.u.m02 + lhs.u.m11 * rhs.u.m12 + lhs.u.m12 * rhs.u.m22 + lhs.u.m13 * rhs.u.m32,
	           lhs.u.m10 * rhs.u.m03 + lhs.u.m11 * rhs.u.m13 + lhs.u.m12 * rhs.u.m23 + lhs.u.m13 * rhs.u.m33,

	           lhs.u.m20 * rhs.u.m00 + lhs.u.m21 * rhs.u.m10 + lhs.u.m22 * rhs.u.m20 + lhs.u.m23 * rhs.u.m30,
	           lhs.u.m20 * rhs.u.m01 + lhs.u.m21 * rhs.u.m11 + lhs.u.m22 * rhs.u.m21 + lhs.u.m23 * rhs.u.m31,
	           lhs.u.m20 * rhs.u.m02 + lhs.u.m21 * rhs.u.m12 + lhs.u.m22 * rhs.u.m22 + lhs.u.m23 * rhs.u.m32,
	           lhs.u.m20 * rhs.u.m03 + lhs.u.m21 * rhs.u.m13 + lhs.u.m22 * rhs.u.m23 + lhs.u.m23 * rhs.u.m33,

	           lhs.u.m30 * rhs.u.m00 + lhs.u.m31 * rhs.u.m10 + lhs.u.m32 * rhs.u.m20 + lhs.u.m33 * rhs.u.m30,
	           lhs.u.m30 * rhs.u.m01 + lhs.u.m31 * rhs.u.m11 + lhs.u.m32 * rhs.u.m21 + lhs.u.m33 * rhs.u.m31,
	           lhs.u.m30 * rhs.u.m02 + lhs.u.m31 * rhs.u.m12 + lhs.u.m32 * rhs.u.m22 + lhs.u.m33 * rhs.u.m32,
	           lhs.u.m30 * rhs.u.m03 + lhs.u.m31 * rhs.u.m13 + lhs.u.m32 * rhs.u.m23 + lhs.u.m33 * rhs.u.m33
	);
#endif
//	std::cout << "operator*(Matrix44 && lhs, const Matrix44 &rhs)" << std::endl;
	return std::move(lhs);
}

Matrix44 && operator*(Matrix44 && lhs, Matrix44 && rhs)
{
//	std::cout << "operator*(Matrix44 && lhs, Matrix44 && rhs)" << std::endl;
	// 一つ↑の Matrix &&operator*(Matrix && lhs, const Matrix44 &rhs) を呼ぶ
	return std::move(lhs) * const_cast<const Matrix44 &>(static_cast<Matrix44 &>(rhs));
}

#endif // RVALUE_REFERENCE


void Matrix44Mul(Matrix44 &out, const Matrix44 &m1, const Matrix44 &m2)
{
#ifndef PROC_DISABLE
	float m[16];
	m[0] = m1.u.m[0] * m2.u.m[0] + m1.u.m[1] * m2.u.m[4] + m1.u.m[2] * m2.u.m[8] + m1.u.m[3] * m2.u.m[12];
	m[1] = m1.u.m[0] * m2.u.m[1] + m1.u.m[1] * m2.u.m[5] + m1.u.m[2] * m2.u.m[9] + m1.u.m[3] * m2.u.m[13];
	m[2] = m1.u.m[0] * m2.u.m[2] + m1.u.m[1] * m2.u.m[6] + m1.u.m[2] * m2.u.m[10] + m1.u.m[3] * m2.u.m[14];
	m[3] = m1.u.m[0] * m2.u.m[3] + m1.u.m[1] * m2.u.m[7] + m1.u.m[2] * m2.u.m[11] + m1.u.m[3] * m2.u.m[15];

	m[4] = m1.u.m[4] * m2.u.m[0] + m1.u.m[5] * m2.u.m[4] + m1.u.m[6] * m2.u.m[8] + m1.u.m[7] * m2.u.m[12];
	m[5] = m1.u.m[4] * m2.u.m[1] + m1.u.m[5] * m2.u.m[5] + m1.u.m[6] * m2.u.m[9] + m1.u.m[7] * m2.u.m[13];
	m[6] = m1.u.m[4] * m2.u.m[2] + m1.u.m[5] * m2.u.m[6] + m1.u.m[6] * m2.u.m[10] + m1.u.m[7] * m2.u.m[14];
	m[7] = m1.u.m[4] * m2.u.m[3] + m1.u.m[5] * m2.u.m[7] + m1.u.m[6] * m2.u.m[11] + m1.u.m[7] * m2.u.m[15];

	m[8] = m1.u.m[8] * m2.u.m[0] + m1.u.m[9] * m2.u.m[4] + m1.u.m[10] * m2.u.m[8] + m1.u.m[11] * m2.u.m[12];
	m[9] = m1.u.m[8] * m2.u.m[1] + m1.u.m[9] * m2.u.m[5] + m1.u.m[10] * m2.u.m[9] + m1.u.m[11] * m2.u.m[13];
	m[10] = m1.u.m[8] * m2.u.m[2] + m1.u.m[9] * m2.u.m[6] + m1.u.m[10] * m2.u.m[10] + m1.u.m[11] * m2.u.m[14];
	m[11] = m1.u.m[8] * m2.u.m[3] + m1.u.m[9] * m2.u.m[7] + m1.u.m[10] * m2.u.m[11] + m1.u.m[11] * m2.u.m[15];

	m[12] = m1.u.m[12] * m2.u.m[0] + m1.u.m[13] * m2.u.m[4] + m1.u.m[14] * m2.u.m[8] + m1.u.m[15] * m2.u.m[12];
	m[13] = m1.u.m[12] * m2.u.m[1] + m1.u.m[13] * m2.u.m[5] + m1.u.m[14] * m2.u.m[9] + m1.u.m[15] * m2.u.m[13];
	m[14] = m1.u.m[12] * m2.u.m[2] + m1.u.m[13] * m2.u.m[6] + m1.u.m[14] * m2.u.m[10] + m1.u.m[15] * m2.u.m[14];
	m[15] = m1.u.m[12] * m2.u.m[3] + m1.u.m[13] * m2.u.m[7] + m1.u.m[14] * m2.u.m[11] + m1.u.m[15] * m2.u.m[15];


	out.u.m[0] = m[0]; out.u.m[1] = m[1]; out.u.m[2] = m[2]; out.u.m[3] = m[3];
	out.u.m[4] = m[4]; out.u.m[5] = m[5]; out.u.m[6] = m[6]; out.u.m[7] = m[7];
	out.u.m[8] = m[8]; out.u.m[9] = m[9]; out.u.m[10] = m[10]; out.u.m[11] = m[11];
	out.u.m[12] = m[12]; out.u.m[13] = m[13]; out.u.m[14] = m[14]; out.u.m[15] = m[15];
#endif
}


main関数があるソースファイル

main.cpp

#include <iostream>

#include <boost/progress.hpp>

#include "Matrix.h"


// 乗算用の関数を用いる場合はON
//#define USE_MULTIPLE_METHOD

// ※↑の他に、Matrix.hに rvalue reference を使うかどうかの
//    プリプロセッサが定義されています。

int main()
{
	boost::progress_timer timer;

	Matrix44 t1, t2, t3, t4, t5, t6, t7, t8, t9;
	Matrix44 t;
	for (int i = 0; i < 1000000; i++)
	{
#ifndef USE_MULTIPLE_METHOD
		t = t1 * t2 * t3 * t4 * t5 * t6 * t7 * t8 * t9;
#else
		Matrix44Mul(t, t1, t2);
		Matrix44Mul(t, t, t3);
		Matrix44Mul(t, t, t4);
		Matrix44Mul(t, t, t5);
		Matrix44Mul(t, t, t6);
		Matrix44Mul(t, t, t7);
		Matrix44Mul(t, t, t8);
		Matrix44Mul(t, t, t9);
#endif
	}

	std::cout << "コンストラクタが呼ばれた回数:" << Matrix44::constructorCount << std::endl;

	return 0;
}


計測

※(7/1)gccに-O2を付けて計測したと勘違いしていた為結果が間違ってました、なので再度計測して書き直しています(太字が加筆です)。

下記が、演算子関数、rvalue referenceを導入した演算子それぞれの実行時間です、コンパイラgcc 4.5.2(MinGW)、Visual C++ 2010(以下VC++2010)の二つで検証、gccは-O2オプション最適化VC++2010はReleaseモードとDebugモードそれぞれで計測しています。

gccは-O2オプション有りと無し、VC++2010はReleaseモードとDebugモードそれぞれで計測しています。

ちなみに、gccコンパイルオプションは下記の通りです。

gcc main.cpp Matrix.cpp -std=c++0x -lstdc++ [-O2] -Iboostのincludeパス -Lboostのlibパス


先ずは、プリプロセッサPROC_DISABLEを有効化した状態で一時オブジェクトの生成や関数オーバーヘッド計測

演算の種類コンストラクタが呼ばれた回数(オブジェクト生成数)10回平均(gcc -O2有り)10回平均(gcc -O2無し)10回平均(VC++2010 Release)10回平均(VC++2010 Debug)
演算子80000100.038sec0.070sec0.018sec0.680sec
関数を使う演算100.012sec0.016sec0.006sec0.199sec
rvalue referenceを導入した演算子10000100.022sec0.050sec0.010sec0.469sec

rvalue referenceを導入した演算子よりも関数の方がオーバーヘッドが小さいようですが、VC++2010のReleaseモードでは微々たる差になっています、それではプリプロセッサPROC_DISABLEを無効化し、行列の乗算時間を加えるとどうなるでしょうか。

演算の種類コンストラクタが呼ばれた回数(オブジェクト生成数)10回平均(gcc -O2有り)10回平均(gcc -O2無し)10回平均(VC++2010 Release)10回平均(VC++2010 Debug)
演算子80000100.337sec0.572sec0.229sec1.352sec
関数を使う演算100.306sec0.492sec0.256sec0.758sec
rvalue referenceを導入した演算子10000100.304sec0.562sec0.248sec1.315sec

gccではオーバーヘッド計測の時こそ差があったものの、-O2で最適化すると従来通りに関数を使用した方法とrvalue referenceを導入した演算子が同じ速度となっています。

VC++2010のReleaseモードだとなぜか従来の演算子を使った方法が一番速くなるという結果になりました。

詳しくは不明ですが、おそらく定数を用いた最適化の為に余分なコードが省かれているのだと思います、

なので、試しにMatrix.cppとMatrix.hをReleaseモードでビルドしてMatrix.libを生成し、main.cppをDebugモードでコンパイルして、Matrix.libとリンクするという方法で検証してみると、

演算の種類コンストラクタが呼ばれた回数(オブジェクト生成数)10回平均(VC++2010 MatrixをRelease、MainをDebug)
演算子80000100.310sec
関数を使う演算100.282sec
rvalue referenceを導入した演算子10000100.286sec

という結果になりました、おそらくこれが実際に使用した時の速度になるのかと思います。


まとめ

gcc 4.5.2(MinGW)では関数オーバーヘッドの方がまだまだ小さく、従来通りに関数を使用した方法が良いでしょうが、VC++2010ではrvalue referenceを導入すれば演算子を使って分かりやすく書け、実行速度も落ちないのではないかと思います。

gcc 4.5.2(MinGW)、VC++2010共にrvalue referenceを導入すれば演算子を使って分かりやすく書け、実行速度も落ちないのではないかと思います。

 
Connection: close