C++14関数の戻り値の型推論
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に纏められ、
無駄な処理を行わずにコードの冗長性を省く事が出来ました、こりゃ便利。
C++のLINQライクライブラリとC#のLINQ覚書き
概要
LINQを使うに当たって、評価コストやメモリ使用量が気になったので自分用メモ
C#のLINQを基準にして考え、cpplinq、Boost.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をコンテナへ格納し、クエリ式毎に各クエリ用のコンテナへと評価用のファンクタと共に格納していきます。
スーパークラスのクラス図
※サブクラスは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を継承した各コンテナクラスで表現されます。
スーパークラスのクラス図
※サブクラスはスーパークラスによって作成され、スーパークラスの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
OpenFLで使えるGUIライブラリ
このページには保存版として使えるように、黙々とOpenFL*1で使えるGUIを張っていこうかと思ってます
StablexUI
2013/11/08記載
- GitHub
- ドキュメントも充実
- http://lib.haxe.org/に公開されてる中では一番規模大きい?
軽く触ってみた感じ一番高機能。
UIをXMLに記述して、それをUIBuilderクラスに食わせるとXMLがパースされてUIが生成される仕組み。
デモはスマホのUIに最適化されてるけど、ゲーム用に特定位置に表示させる事も出来るかも。
後で自分でもUIを書いてみる
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ではラムダ式には推論はしない方が良いと思います、どうしても適用したい場合は上記の事に注意しながら実装するって事になるでしょう。
Haxe覚書き
実に1年と9カ月ぶりのエントリ、私は元気です。
ここでは自分用メモの為にも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と同じ使い方をしていると変にはまったり、乱用するとパフォーマンスに影響が出たりするかもしれません。
とりあえずこんな所です。
C++でクロージャ
JavaScriptでクロージャを触ってみて、C++でも検証してみました。
結論から先に言うと、参照カウントのスマートポインタ(std::shared_ptrやboost::shared_ptr)を使えば出来ました。
※1/10一部修正、詳しくはコメント欄
クロージャ概要
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
これがクラスでコピーコンストラクタが複数回走ってた原因でした。
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
スマートポインタをプログラム終了前のコールバック関数として使うってやり方です、
少し変な書き方なので突っ込み待ち