squirrel_code @ ウィキ
http://w.atwiki.jp/squirrel_code/
squirrel_code @ ウィキ
ja
2013-09-04T00:29:59+09:00
1378222199
-
Double dispatch がめんどくさい
https://w.atwiki.jp/squirrel_code/pages/30.html
* Double dispatch がめんどくさい
#right{last update: &update(format=Y/m/d (D))}
ヤブから棒ではあるが,
Rectangle rectangle;
Circle circle;
PenSVG pen_svg;
PenPNG pen_png;
rectangle.drawWith(pen_svg);
circle.drawWith(pen_svg);
rectangle.drawWith(pen_png);
circle.drawWith(pen_png);
…というようなことがしたい.要は四角形とか円とか,ひょっとしたら楕円とか菱形とかいろんな図形があって, PNG とか SVG とか,ひょっとしたら JPEG とかに出力するペンがあって,どの図形も,どのペンでも自在に組み合わせて描きたい,という意味である.
** その1
簡単にプログラムを書くとこんな感じ.
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Rectangle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
class Circle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
#include <iostream>
void
Rectangle::drawWith(PenToPNG & pen)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
Rectangle::drawWith(PenToSVG & pen)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
Circle::drawWith(PenToPNG & pen)
{
std::cout << "Circle: PNG" << std::endl;
}
void
Circle::drawWith(PenToSVG & pen)
{
std::cout << "Circle: SVG" << std::endl;
}
// Pen.hpp
class PenToPNG
{
};
class PenToSVG
{
};
// Pen.cpp
// no line
画像フォーマット云々に踏み込むつもりはないので,関数の中身はテキスト出力で代用した.たった2種類の図形,2種類のペンではあるが,なかなかに長い.そして似たような行が延々と並ぶ.そして図形の種類やペンの種類を増やすときはとりあえず 4yy C-w l p C-v jjj :'<,'>s/Rectangle/Triangle/g ...
なんてことは間違ってもしてはいけない…のだが,それ以前に SVG とか PNG とか,特定の画像フォーマットに依存するコードが図形クラスの中で実装してあるのがなんとも気持ちが悪い.まずはここをなんとかしよう.
** その2
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Rectangle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
class Circle
{
public:
void
drawWith(PenToPNG & pen);
void
drawWith(PenToSVG & pen);
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(PenToPNG & pen)
{
pen.draw(*this);
}
void
Rectangle::drawWith(PenToSVG & pen)
{
pen.draw(*this);
}
void
Circle::drawWith(PenToPNG & pen)
{
pen.draw(*this);
}
void
Circle::drawWith(PenToSVG & pen)
{
pen.draw(*this);
}
// Pen.hpp
class Rectangle;
class Circle;
class PenToPNG
{
public:
void
draw(Rectangle & rectangle);
void
draw(Circle & rectangle);
};
class PenToSVG
{
public:
void
draw(Rectangle & rectangle);
void
draw(Circle & rectangle);
};
// Pen.cpp
#include "Pen.hpp"
#include "Shape.hpp"
#include <iostream>
void
PenToPNG::draw(Rectangle & rectangle)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
PenToPNG::draw(Circle & rectangle)
{
std::cout << "Circle: PNG" << std::endl;
}
void
PenToSVG::draw(Rectangle & rectangle)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
PenToSVG::draw(Circle & rectangle)
{
std::cout << "Circle: SVG" << std::endl;
}
これで画像の詳細に関わる実装がペンクラスに移動した.さて,Rectangle も Circle も drawWith メソッドを持ち,PenToSVG も PenToPNG も draw メソッドを持っている.同じ操作を受け付けるということは,基底クラスを作ってやることで,ぽりもーふぃっく(ひらがな)できる可能性があるわけだ.そこで「一般の図形」を表すクラス Shape と一般のペンを表すクラス Pen をつくり, Rectangle と Circle を Shape の, PenToSVG と PenToPNG を Pen の派生クラスにしてみよう.ついでにリファレンスもポインタに変えておく.
** その3
// Shape.hpp
class PenToPNG;
class PenToSVG;
class Shape
{
public:
virtual
~Shape()
{ }
virtual void
drawWith(PenToPNG * pen) = 0;
virtual void
drawWith(PenToSVG * pen) = 0;
};
class Rectangle
: public Shape
{
public:
virtual void
drawWith(PenToPNG * pen) final override;
virtual void
drawWith(PenToSVG * pen) final override;
};
class Circle
: public Shape
{
public:
virtual void
drawWith(PenToPNG * pen) final override;
virtual void
drawWith(PenToSVG * pen) final override;
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(PenToPNG * pen)
{
pen->draw(this);
}
void
Rectangle::drawWith(PenToSVG * pen)
{
pen->draw(this);
}
void
Circle::drawWith(PenToPNG * pen)
{
pen->draw(this);
}
void
Circle::drawWith(PenToSVG * pen)
{
pen->draw(this);
}
// Pen.hpp
class Rectangle;
class Circle;
class Pen
{
public:
virtual
~Pen()
{ }
virtual void
draw(Rectangle * rectangle) = 0;
virtual void
draw(Circle * rectangle) = 0;
};
class PenToPNG
: public Pen
{
public:
virtual void
draw(Rectangle * rectangle) final override;
virtual void
draw(Circle * rectangle) final override;
};
class PenToSVG
: public Pen
{
public:
virtual void
draw(Rectangle * rectangle) final override;
virtual void
draw(Circle * rectangle) final override;
};
// Pen.cpp
#include "Pen.hpp"
#include "Shape.hpp"
#include <iostream>
void
PenToPNG::draw(Rectangle * rectangle)
{
std::cout << "Rectangle: PNG" << std::endl;
}
void
PenToPNG::draw(Circle * rectangle)
{
std::cout << "Circle: PNG" << std::endl;
}
void
PenToSVG::draw(Rectangle * rectangle)
{
std::cout << "Rectangle: SVG" << std::endl;
}
void
PenToSVG::draw(Circle * rectangle)
{
std::cout << "Circle: SVG" << std::endl;
}
見ればわかるが, Shape クラスは多少簡単にすることができる.
// Shape.hpp
class Pen;
class Shape
{
public:
virtual void
drawWith(Pen * pen) = 0;
};
class Rectangle
: public Shape
{
public:
virtual void
drawWith(Pen * pen) final override;
};
class Circle
: public Shape
{
public:
virtual void
drawWith(Pen * pen) final override;
};
// Shape.cpp
#include "Shape.hpp"
#include "Pen.hpp"
void
Rectangle::drawWith(Pen * pen)
{
pen->draw(this);
}
void
Circle::drawWith(Pen * pen)
{
pen->draw(this);
}
使うときはこんな感じ.
Shape * rectangle = new Rectangle();
Shape * circle = new Circle();
Pen * pen_svg = new PenToSVG();
Pen * pen_png = new PenToPNG();
rectangle->drawWith(pen_svg);
circle->drawWith(pen_svg);
rectangle->drawWith(pen_png);
circle->drawWith(pen_png);
これで Shape::drawWith の呼び出しで自動的にそれぞれの図形やペンに対応した処理がなされる.これを条件分岐で実装すると if 文がネストして大変なことになるが,クラス間をたらいまわしにするこの手のテクを使えば美しく実装することができる.これを「ダブルディスパッチ」と呼ぶ.
つづく.
&trackback()
----
**参考
-[[C++でMix-inもどき>http://sato-www.cs.titech.ac.jp/isobe/mix-in.html]]
-[[template で Mix-in>http://blogs.wankuma.com/episteme/archive/2006/10/08/40928.aspx]]
----
**コメント
#comment_num2(size=40,vsize=10,num=20,logpage=コメントログ)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(mix-in)
2013-09-04T00:29:59+09:00
1378222199
-
トップページ
https://w.atwiki.jp/squirrel_code/pages/1.html
*squirrel_code
#right{last update: &update(format=Y/m/d (D))}
**何?
プログラミングのメモ、Tipsなどなど。
**目次
***C++ 関連
-[[pImpl × 継承]]
-[[続 pImpl × 継承]]
-[[続続 pImpl × 継承]]
-[[template × mix-in]]
-[[続 template × mix-in]]
-[[続続 template × mix-in]]
-[[続続続 template × mix-in]]
-[[singleton × thread]]
-[[C++ × 定理証明]]
-[[Double dispatch がめんどくさい]]
***C++0x 関連
-[[shared_lock]]
-[[無限リスト]]
-[[続 無限リスト]]
-[[続続 無限リスト]]
***その他
-[[ようこそ]] : @wiki デフォルトのトップページ
2013-09-03T23:11:16+09:00
1378217476
-
続続 無限リスト
https://w.atwiki.jp/squirrel_code/pages/29.html
*続続 無限リスト
#right{last update: &update(format=Y/m/d (D))}
[[前回>続 無限リスト]],C++0x の新機能である lambda 式を活用し,関数型言語における list を構築した.今回はまず,このクラスを使っていろいろなプログラムを書いてみる.まず,list の空判定と出力を実装する.
template <typename T>
class list
{
// 省略
// リストが空であるかどうか
bool
is_null() const { return !_cell; }
};
// 出力
// #include <ostream>
template <typename T>
std::ostream &
operator << (std::ostream & stream, list<T> const & xs)
{
return xs.is_null() ? stream << "()"
: stream << "(" << xs.car() << " , " << std::flush << xs.cdr() << ")";
}
次に,いくつかのリスト操作関数を実装する.
template <typename T>
class list
{
// 省略
// xs.take(n) で xs の先頭 n 要素を切り出す.
list<T>
take(unsigned int n) const
{
list xs = *this;
return n == 0 ? list()
: list(car(), [n, xs](){ return xs.cdr(), take(n - 1); });
}
// xs.zipWith(f, ys) で f(x, y) のリストを計算.
template <typename F, typename T2>
list<typename F::result_type>
zipWith(F const & f, list<T2> const & ys) const
{
list xs = *this;
return list<typename F::result_type>(
f(car(), ys.car()), [&f, xs, ys](){
return xs.cdr().zipWith(f, ys.cdr()); });
}
};
カンのいい Haskeller なら気付かれたかもしれないが,zipWith を選んだのは次のプログラムを書くためだ.
// in Haskell
fib = 1 : 1 : zipWith (+) fib (tail fib)
この1行でフィボナッチ数列が計算できる.これを我らが C++ に翻訳すると
// #include <functional>
list<int> fib = list<int>(1, list<int>(1,
[&fib](){ return fib.zipWith(std::plus<int>(), fib.cdr()); }));
std::cout << fib.take(6) << std::endl;
// => (1, (1, (2, (3, (5, (8, ()))))))
すばらしい!が,喜ぶのはまだ早い. fib.take(30) あたりで計算が急激に遅くなってくる.Haskell と比べてどこに違いがあるのだろうか.
** グラフ簡約
フィボナッチプログラムの動作を追ってみよう.まず operator << 内で fib.car() され
(1, ...
が評価される.次に cdr() されて再び operator << に渡され car() されて
(1, (1, ...
となる.次の cdr() は遅延されているため,
(1, (1, { zipWith (+) fib fib.cdr() }
となり, operator << で car() されて
(1, (1, { fib.car() + fib.cdr().car() , ... }
=> (1, (1, ({ 1 + 1 }, ...
=> (1, (1, (2, ...
となる.さてこの次であるが,評価前は
(1, (1, (2, { zipWith (+) fib.cdr().car(), fib.cdr().cdr().car(), ... }
となっている.これを評価しようとすると,fib.cdr().car(), fib.cdr().cdr().car() などが計算される.
ところで,fib の 4 項目を計算するとき,fib の 3 項目以下は既に計算されているはずである.しかしながら,現在のリストの実装では,4 項目の計算時に 3 項目以下を再度計算してしまう.当然 5 項目を計算するために 4 項目を再度計算し,またそのためにみたび 3 項目を...と,計算量が爆発的に増えていく.これが計算が遅くなった原因である.
Haskell は遅延評価型の関数型言語であり,その評価戦略はグラフ簡約を用いている.どういうことかというと,一度計算したリストは,再度 cdr() されたとしても前の値をキャッシュしているのである.よって 5 項目の計算時には既に計算された 4 項目の値を用い,6 項目の計算時には既に計算された 5 項目の...と,計算量は爆発しない.
この仕組を我らのリストへ導入しよう.
一度計算された値を保存して,次に呼び出されたときに利用すればよいのだから,
一度 cdr() されたとき,自動的に cell_impl_delayed から cell_impl_eager へ切り替えればよいということになる.しかし,以下のようなクラスを用いるともう少し汎用的な実装が可能だ.
template <typename S>
class promise
{
public:
typedef typename std::function<S>::result_type result_type;
private:
class cache_base
{
protected:
cache_base()
{
}
public:
virtual ~cache_base()
{
}
virtual typename std::function<S>::result_type
value() const = 0;
virtual bool
is_valued() const = 0;
};
class cache_impl_valued
: public cache_base
{
private:
typename std::function<S>::result_type _r;
public:
cache_impl_valued(typename std::function<S>::result_type const & r)
: cache_base(), _r(r)
{
}
typename std::function<S>::result_type
value() const { return _r; }
bool
is_valued() const { return true; }
};
class cache_impl_unvalued
: public cache_base
{
private:
std::function<S> _f;
public:
cache_impl_unvalued(std::function<S> const & f)
: cache_base(), _f(f)
{
}
typename std::function<S>::result_type
value() const { return _f(); }
bool
is_valued() const { return false; }
};
mutable std::shared_ptr<cache_base> _cache;
public:
promise(std::function<S> const & f)
: _cache(new cache_impl_unvalued(f))
{
}
typename std::function<S>::result_type
operator () () const
{
if (_cache->is_valued()) {
return _cache->value();
}
else {
typename std::function<S>::result_type r = _cache->value();
_cache.reset(new cache_impl_valued(r));
return r;
}
}
};
このクラステンプレートは std::function<T()> と同様に振る舞うが,一度 operator () が呼ばれると結果の値をキャッシュする.そして再度 operator () が呼ばれた時にキャッシュされた値を利用する.次にリストの実装を一箇所だけ変更する.
struct cell_impl_delayed
: public cell_base
{
promise<std::shared_ptr<cell_base>()> _cdr;
// std::function<std::shared_ptr<cell_base>()> _cdr;
cell_impl_delayed(T const & car, std::function<std::shared_ptr<cell_base>()> const & cdr)
: cell_base(car), _cdr(cdr)
{
}
std::shared_ptr<cell_base>
cdr() const { return _cdr(); }
};
これによって,次のプログラム
list<int> fib = list<int>(1, list<int>(1,
[&fib](){ return fib.zipWith(std::plus<int>(), fib.cdr()); }));
は線形時間で動作する.さらに
T
car() { return _cell->car(); }
なるメンバ関数を定義しておくと,
std::cout << fib.take(6) << std::endl;
// => (1, (1, (2, (3, (5, (8, ()))))))
fib.cdr().cdr().cdr().car() = 1;
std::cout << fib.take(6) << std::endl;
// => (1, (1, (2, (1, (5, (8, ()))))))
なんてことが可能になったりする.
さて,同じプログラムを scheme と haskell で実装してみるとわかるが,関数型言語が本領を発揮するには,「よくある」汎用アルゴリズムを用意したときである.「よくある」アルゴリズムとしては,map, zip, filter, foldl, scanl, foldr, scanr 等々,様々に存在する.このあたりは,Haskell を参考になんなりと実装しておけばよい.例えば map であれば
template <typename F>
list<typename F::result_type>
map(F const & f) const
{
list xs = *this;
return list<typename F::result_type>(
f(car()), [&f, xs](){ return xs.cdr().map(f); });
}
のようになる.map ができたら,例えば
std::function<int(int)> inc = [](int x){ return x + 1; };
list<int> seq = list<int>(1, std::function<list<int>()>(
[&seq, &inc](){ return seq.map(inc); }));
std::cout << seq.take(10) << std::endl;
// => (1, (2, (3, (4, (5, (6, (7, (8, (9, 10, ())))))))))
のように等差数列が書けたりする.
&trackback()
----
**参考
-[[Multi-threading Library for Standard C++ (Revision 1)>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.html]]
----
**コメント
#comment(title_name=name,title_msg=comment)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(C++0x)
2012-12-11T22:46:34+09:00
1355233594
-
続 無限リスト
https://w.atwiki.jp/squirrel_code/pages/28.html
*続 無限リスト
#right{last update: &update(format=Y/m/d (D))}
[[前回>無限リスト]],C++0x を用いて基本的な無限リストを作成した.とはいえ,目につく問題も残っていた.例えばリストを扱うために常に shared_ptr を経由しなければならなかったり,リストの 2 番目を参照するために ls->cdr()->car といった不自然なメソッド呼び出しをする必要があったりした.これを改善しよう.
まず,常に shared_ptr となるのを防ぐには,cons セル全体をラッパクラスで覆えばよい.例えば以下のようになる.
template<typename T>
class list
{
struct cell
{
T _car;
std::function<std::shared_ptr<cell>()> _cdr;
cell(T const & car, std::function<std::shared_ptr<cell>()> const & cdr)
: _car(car), _cdr(cdr)
{}
};
std::shared_ptr<cell> _cell;
};
std::list と名前が衝突する? そんなことは using namespace std する奴が悪いのだ(おい)それよりも問題は,この list をどうやって構築するかである.前回は cell 構造体そのものを lambda 式を用いて直接構築した.しかし今回は cell 構造体は private なので外部からは隠蔽されている.なので nat_from で「std::shared_ptr<cell> を返す lambda 式」を直接生成できない.
そこで,ユーザ側では 「list を返す lambda 式」を定義してもらって,それを「std::shared_ptr<cell> を返す lambda 式」に変換してやる必要がある.この変換をコンストラクタとして実装してやればよい.
ついでに,C++0x らしく cell 構造体はコピー・代入禁止とし,さらに car と cdr へのアクセサを実装しておく.
template <typename T>
class list
{
struct cell
{
T car;
std::function<std::shared_ptr<cell>()> cdr;
cell(T const & car, std::function<std::shared_ptr<cell>()> const & cdr)
: car(car), cdr(cdr)
{
}
cell(cell const &) = delete;
cell
operator = (cell const &) = delete;
};
std::shared_ptr<cell> _cell;
public:
list()
{
}
private:
list(std::shared_ptr<cell> const & cell)
: _cell(cell)
{
}
public:
list(T const & car, std::function<list<T>()> const & cdr)
: _cell(new cell(car, [cdr]() { return cdr()._cell; }))
{
}
T
car() const { return _cell->car; }
list
cdr() const { return list(_cell->cdr()); }
};
これを用いて nat_from を実装すると
list<int>
nat_from(int k)
{
return list<int>(k, [k]() { return nat_from(k + 1); });
}
おおー,ずっと自然になった.
** 有限リスト
これで自由自在に無限リストを使えるようになったであろうか.いや,まだ気が早い.例えば nat_from(1) で 1 から始まる増加列を生成できたとして,これの先頭に 0 を加えたい場合どうすればよいだろう.すでにある list を返す promise を改めて生成し,list 構築なんてめんどくさい.というか,有限だろうが無限だろうが,生成する方法は異なっても使うときは同じように使いたいのだ.これを実現しよう.
まず,有限リスト用の cons セルを定義する.これを無限リスト用の cons セルと混ぜて使いたいから,同じ基底クラスを持つ派生クラスとして定義すればよいだろう.ここで car 部は共通だから基底クラスが持てばよい.cdr 部は有限リストなら shared_ptr<cell> を,無限リストなら function<shared_ptr<cell>()> を保持し,アクセスされたら shared_ptr<cell> を返すようなアクセサを用意する.
template <typename T>
class list
{
class cell_base
{
T _car;
public:
cell_base(T const & car)
: _car(car)
{
}
cell_base(cell_base const &) = delete;
virtual ~cell_base()
{
}
cell_base
operator = (cell_base const &) = delete;
T const &
car() const { return _car; }
virtual std::shared_ptr<cell_base>
cdr() const = 0;
};
struct cell_impl_eager
: public cell_base
{
std::shared_ptr<cell_base> _cdr;
cell_impl_eager(T const & car, std::shared_ptr<cell_base> const & cdr)
: cell_base(car), _cdr(cdr)
{
}
std::shared_ptr<cell_base>
cdr() const { return _cdr; }
};
struct cell_impl_delayed
: public cell_base
{
std::function<std::shared_ptr<cell_base>()> _cdr;
cell_impl_delayed(T const & car, std::function<std::shared_ptr<cell_base>()> const & cdr)
: cell_base(car), _cdr(cdr)
{
}
std::shared_ptr<cell_base>
cdr() const { return _cdr(); }
};
std::shared_ptr<cell_base> _cell;
public:
list()
{
}
private:
list(std::shared_ptr<cell_base> const & cell)
: _cell(cell)
{
}
public:
list(T const & car, list<T> const & cdr)
: _cell(new cell_impl_eager(car, cdr._cell))
{
}
list(T const & car, std::function<list<T>()> const & cdr)
: _cell(new cell_impl_delayed(car, [cdr]() { return cdr()._cell; }))
{
}
T const &
car() const { return _cell->car(); }
list
cdr() const { return list(_cell->cdr()); }
};
試してみる.
list<int> ls = nat_from(1);
ls = list<int>(0, ls);
std::cout << ls.car() << std::endl; // 0
std::cout << ls.cdr().car() << std::endl; // 1
std::cout << ls.cdr().cdr().car() << std::endl; // 2
できちまったぜ.
&trackback()
----
**参考
-[[Multi-threading Library for Standard C++ (Revision 1)>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.html]]
----
**コメント
#comment(title_name=name,title_msg=comment)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(C++0x)
2012-12-08T05:27:51+09:00
1354912071
-
無限リスト
https://w.atwiki.jp/squirrel_code/pages/27.html
*無限リスト
#right{last update: &update(format=Y/m/d (D))}
Scheme や Haskell などの関数型言語では,無限の長さのリストを自然に扱うことができる.例えば Haskell では
nat_from :: Int -> [Int]
nat_from x
= x : nat_from (x + 1)
という関数を定義すると,
nat_from 1
で [1, 2, 3, ...] という無限リストを生成できる.ちゃんと head (nat_from 1) で 1 が返るし,head (tail (nat_from 1)) では 2 が返る.無限ループに陥ったりはしない.同じ事を scheme でやろうとすると
(define-syntax lazy-cons
(syntax-rules ()
((_ x y) (cons x (delay y)))))
というマクロを用意し
(define nat-from
(lambda (x) (lazy-cons x (nat-from (+ x 1)))))
(nat-from 1)
と書くことになる.lazy-cons を使わなければならなかったのは,haskell が何もしなくても遅延評価なのに対し,scheme は delay/force 機構を用いなければ遅延評価されないからである.もしこれを
(define nat-from
(lambda (x) (cons x (nat-from (+ x 1)))))
としてしまうと,例えば (car (nat-from 1)) を評価しようとしても,先に (nat-from 1) を先に完全に構築しようとして無限ループに陥ってしまう.このように,無限リストを扱うためには遅延評価が必要になる.逆に,遅延評価(と再帰関数)が使えるなら,そのプログラミング言語では無限リストが扱える.
で,C++0x の情報を眺めていたら,lambda 式が追加されているのに気がついた(遅い).lambda 式とは局所的に関数を生成する式のことで,例えば int 型同士の足し算を表す lambda 式は
[](int x, int y){ return x + y; }
のように書く.lambda 式の型を直接的に記述する手段は用意されていないが,std::function クラステンプレートを用いて
// #include <functional>
std::function<int(int,int)> plus
= [](int x, int y){ return x + y; }
のように捕捉できる.
ここで,lambda 式が表す関数は,関数を生成しただけでは実行されない.関数を実行するには,実際に引数を与えて
plus(1, 2)
のように呼んでやらなければならない.関数の生成を delay,呼び出しを force と考えると,これは scheme の delay/force 機構と同等である. C++ はもちろん再帰関数を使えるから,無限リストが作れるはずである.やってみる.
まず,scheme で言うところの cons セルを作る.通常のリストの cons セルは,具体的な値の入った car 部と,後続の cons セルである cdr 部のペアであるが,無限リストの場合は lazy-cons の定義にあるように,これは car 部と「cdr 部を生成する promise」のペアとなる.今,「cdr 部を生成する promise」は 「後続の cons セルを生成する lambda 式」に対応するから,cons セルの型は
template <typename T>
struct cell
{
T _car;
std::function<cell()> _cdr;
};
となるはずだ.とはいえ,これでは cell が値渡しになっていろいろと不自由なので, shared_ptr を用いて
template <typename T>
struct cell
{
T _car;
std::function<std::shared_ptr<cell>()> _cdr;
// コンストラクタ
cell(T car, std::function<std::shared_ptr<cell>()> cdr)
: _car(car), _cdr(cdr)
{}
};
とする(ついでにコンストラクタも定義した).これを用ると,先ほどの無限リストを生成する関数は
std::shared_ptr<cell<int>>
nat_from(int x)
{
return std::shared_ptr<cell<int>>(
new cell<int>(x,
std::function<std::shared_ptr<cell<int>>()>(
[=](){ return nat_from(x + 1); })));
}
と書ける(lambda 式の [=] はおまじない).おお,出来てしまった.試してみると
std::shared_ptr<cell<int>> ls = nat_from(1);
std::cout << ls->_car << std::endl; // 1
std::cout << ls->_cdr()->_car << std::endl; // 2
となり,正しく動作している.ぶらぼー.
&trackback()
----
**参考
-[[Multi-threading Library for Standard C++ (Revision 1)>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.html]]
----
**コメント
#comment(title_name=name,title_msg=comment)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(C++0x)
2012-12-06T02:57:05+09:00
1354730225
-
C++ × 定理証明
https://w.atwiki.jp/squirrel_code/pages/26.html
* C++ × 定理証明
#right{last update: &update(format=Y/m/d (D))}
世の中には酔狂な人がいるもので,プログラミングと定理の証明が等価だと言った人がいるそうな.これを「カリー・ハワード対応」と言うらしい.カリー・ハワードさんが言ったのか,カリーさんとハワードさんが共同で発表したのか,カリーさんとハワードさんが独立に発見したけどたまたま時期が同じだったのか,詳しいことは知らないので調べておくように.
で,気になった.我等が C++ も定理証明と等価なのだろうか.等価だとしたら,我々がプログラムを作るとき,それはどのような定理を証明していることになるのだろうか.
やってみる.
まず,定理証明といったとき,これは論理学で言う形式的証明のことである.形式的証明にはいくつかの流儀があるが,そのうちの1つにヒルベルト流の形式化というのがある.ヒルベルト流の形式化では,全ての命題を「A ならば B」(これを「含意」と言う)と「A でない」(これを「否定」と言う)で表す.これを以下のように書こう.
A -> B // A ならば B
!A // A でない
さて,これに対応する C++ プログラムを考える.カリー・ハワード対応では,命題は型と対応する. 「含意」は A という型と B という型から新たな型「A -> B」を作り出す操作となる.これは C++ では template で実装できる.同様に「否定」も template で実装できる.
template <typename A, typename B>
struct then {};
template <typename A>
struct negate {};
「ならば」は英語で「then」である. 「A ならば B」は英語で「A then B」であり,C++言語で「then<A, B>」である.「でない」は英語で「not」だが,not は識別子としていろいろ問題があるので「negate」を使った.次に,ヒルベルト流の定式化では,推論規則として「A -> B であり,かつ A ならば B」を用いる.俗に言う(正しくは言わない)3段論法,ちょっとかっこよく言うと「モーダスポネンス」である.ちなみに推論規則には他に「モーダストレンス」とか「モーダスポネンストレンス」とかがある.興味があれば Go Wikipedia.
さて,カリー・ハワード対応において推論規則はプログラムに対応する.簡単には関数だと思えばよい.引数が前提条件で,返り値が結論である.つまり,モーダスポネンスは,then<A, B> と A を引数に取り,B を返す関数である.
template <typename A, typename B>
B mp(then<A, B> t, A) {}
ここで3段論法は以下のプログラムで表現される.
struct X{}
struct Y{}
int main(int argc, char * argv[])
{
X
p1; // 前提 X
then<X, Y>
p2; // 前提 X -> Y
Y
p3 = mp(p2, p1); // 結論 Y
}
ここでは前提を無理やり書いたため,何を証明したのか分かり難くなっているが,最後の行の p3 = ... がすなわち p3 の証明である.この証明が正しいかどうかは,コンパイルしてみればよい.コンパイラの型チェッカを通過すれば,証明は妥当である.
形式的証明では,前提をいろいろ書けばなんでも証明できてしまうが,一応「正しい」命題を「全て」生成することができる「前提」が知られている.「正しい」と「全て」を括弧でくくったあたりはいろいろとややこしいので説明は省略する.いずれにせよ,これらの前提は「公理」と呼ばれる.
// 公理1
// A -> (B -> A)
template <typename A, typename B>
then<A, then<B, A> > axiom1() {}
// 公理2
// (A -> (B -> C)) -> (A -> B) -> (A -> C)
template <typename A, typename B, typename C>
then<then<A, then<B, C> >, then<then<A, B>, then<A, C> > > axiom2() {}
// 公理3
// (!A -> !B) -> (B -> A)
template <typename A, typename B>
then<then<negate<A>, negate<B> >, then<B, A> > axiom3() {}
さて以上を使えば,世の中のいろいろな定理が証明できるはずである.手始めとして,自明な命題「A ならば A である」を証明しよう.
struct X {};
int main(int argc, char * argv[])
{
// P1: (X -> ((X -> X) -> X)) -> ((X -> (X -> X)) -> (X -> X))
// なぜなら公理2で A = X, B = (X -> X), C = X
then<then<X, then<then<X, X>, X> >, then<then<X, then<X, X> >, then<X, X> > >
p1 = axiom2<X, then<X, X>, X>();
// P2: X -> ((X -> X) -> X)
// なぜなら公理1で A = X, B = (X -> X)
then<X, then<then<X, X>, X> >
p2 = axiom1<X, then<X, X> >();
// P3: (X -> (X -> X)) -> (X -> X)
// なぜなら P1, P2 から MP
then<then<X, then<X, X> >, then<X, X> >
p3 = mp(p1, p2);
// P4: X -> (X -> X)
// なぜなら公理1で A = X, B = X
then<X, then<X, X> >
p4 = axiom1<X, X>();
// P5: X -> X
// なぜなら P3, P4 から MP (結論)
then<X, X>
p5 = mp(p3, p4);
}
コンパイルちゃんと通るぜ.
&trackback()
----
**参考
-[[C++でMix-inもどき>http://sato-www.cs.titech.ac.jp/isobe/mix-in.html]]
----
**コメント
#comment_num2(size=40,vsize=10,num=20,logpage=コメントログ)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(mix-in)
2012-09-09T01:47:59+09:00
1347122879
-
コメントログ
https://w.atwiki.jp/squirrel_code/pages/25.html
- コメントの投稿テスト -- (tossy_squirrel) &size(80%){2010-12-29 03:35:18}
2010-12-29T03:35:18+09:00
1293561318
-
続続続 template × mix-in
https://w.atwiki.jp/squirrel_code/pages/23.html
* 続続続 template × mix-in
#right{last update: &update(format=Y/m/d (D))}
さて, [[前回>続続 template × mix-in]] でテンプレートを用いた Mix-in についてはほぼカタがついたはずだ.なに?ソースが長い? Mix-in を定義するたびに IMix だの Mix だのを定義するのがめんどい? なんとかならんのか,だって?
というわけで,なんとかしてみる^^.
** その6
まず, template を使ったテクニックではある程度長くなるのは仕方がない.というか,前回のコードでも裏で結構頑張って短くしているのだ.なので,これを越えるためにはいろいろと禁断の秘儀^^を用いる必要がある.
こういう場面で C のプログラマが真っ先に思いつくのはプリプロセッサマクロだろう.私もそう思う.だが,今回のコードを展開するマクロを作るには,マクロが Mix-in するメソッド名のリスト(add と remove)を取り扱える必要がある.
そこでまたまたいろいろ調べていたら, boost/preprocessor という秘法を見つけた(たぶんインドの奥地かどっかで)
書いてみる.
// mixin.hpp
#include <list> // for std::list
#include <algorithm> // for std::find
#include <boost/mpl/list.hpp>
#include <boost/mpl/iter_fold.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/type.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
// === ここから汎用 Mix-in フレームワーク
#define _DEF_DUMMYBASE_METHOD(r, data, elem) \
void elem();
#define _DEF_INTERFACE_USING(r, data, elem) \
using _BASE::elem; using _MIXIN::elem;
#define MIXIN(ns, seq) \
namespace mixin { \
namespace ns { \
class idummy {}; \
template <typename _BASE> \
class ibase : public _BASE \
{ \
public: \
BOOST_PP_SEQ_FOR_EACH(_DEF_DUMMYBASE_METHOD, _, seq) \
}; \
template <typename _MITER, typename _BASE> \
class _IMIX1 \
: public _BASE, public boost::mpl::deref<_MITER>::type \
{ \
public: \
typedef typename boost::mpl::deref<_MITER>::type _MIXIN; \
BOOST_PP_SEQ_FOR_EACH(_DEF_INTERFACE_USING, _, seq) \
}; \
template <typename _TLIST, typename _BASE = idummy> \
class imix \
: public boost::mpl::iter_fold \
<_TLIST, ibase<_BASE>, \
_IMIX1<boost::mpl::_2, boost::mpl::_1> >::type \
{}; \
}}
#define _DEF_CLASS_USING(r, data, elem) \
using _BASE::elem;
#define DEF_MIXIN_CALL_BEGIN(ns, tname, seq) \
namespace mixin { \
namespace ns { \
class dummy {}; \
template <typename _BASE> \
class base : public _BASE \
{ \
public: \
BOOST_PP_SEQ_FOR_EACH(_DEF_DUMMYBASE_METHOD, _, seq) \
}; \
template <typename _MITER, typename _BASE> class _MIX1; \
template <typename _TLIST, typename _BASE = dummy> \
class mix \
: public boost::mpl::iter_fold \
<_TLIST, base<_BASE>, \
_MIX1<boost::mpl::_2, boost::mpl::_1> >::type \
{}; \
template <typename _MITER, typename _BASE> \
class mix1 \
: public _BASE, private boost::mpl::deref<_MITER>::type \
{ \
public: \
typedef typename boost::mpl::deref<_MITER>::type tname; \
BOOST_PP_SEQ_FOR_EACH(_DEF_CLASS_USING, _, seq)
#define DEF_MIXIN_CALL_END \
}; \
}}
// === 汎用 Mix-in フレームワークここまで ===
class A { /* 省略 */ };
class B { /* 省略 */ };
/* Mix-in インターフェイスの定義.
* 最初に MIXIN(名前空間名, メソッド名リスト) を宣言する.
* 名前空間名は,同名メソッドを持つ Mix-in の系列に対し,一意に決める.
*/
MIXIN(c, (add) (remove))
template <typename T>
class MI
{
public:
virtual ~MI() {}
virtual void add(T* t) = 0;
virtual void remove(T* t) = 0;
};
/** Mix-in の実装の定義.
* 最初に MIXIN_CALL_BEGIN(名前空間名, 型名, メソッド名リスト)を宣言
* 次に private 継承されている Mix-in のメソッドを
* public スコープに輸出するコードを書く.
* Mix-in した実装は型名で参照できる.
* 輸出コードの終わりに MIXIN_CALL_END を宣言
*/
DEF_MIXIN_CALL_BEGIN(c, mixin_t, (add) (remove))
void add(typename mixin_t::t_type* t) { mixin_t::add(t); }
void remove(typename mixin_t::t_type* t) { mixin_t::remove(t); }
DEF_MIXIN_CALL_END
template <typename T>
class MixIn
{
std::list<T*> ts;
public:
typedef T t_type;
MixIn() {}
~MixIn() {}
void add(T* t);
void remove(T* t);
};
template <typename T>
void MixIn<T>::add(T* t)
{ ts.push_back(t); }
template <typename T>
void MixIn<T>::remove(T* t)
{
typedef typename std::list<T*>::iterator iterator;
iterator iter = std::find(ts.begin(), ts.end(), t);
if (iter != ts.end()) ts.erase(iter++);
}
/** Mix-in フレームワークにより,
* mixin::名前空間名::imix と mixin::名前空間名::mix が定義されている.
* これに Mix-in の mpl::list を渡して public 継承すればよい.
*/
using namespace mixin::c;
namespace mpl = boost::mpl;
class Interface
: public imix<mpl::list<MI<A>, MI<B> > >
{
public:
virtual ~Interface() {}
};
class Container
: public mix<mpl::list<MixIn<A>, MixIn<B> >, Interface>
{
public:
Container() {}
~Container() {}
};
// mixin.cpp
#include "mixin.h"
いきなりコードの変態度が上がってしまった^^.上のコードが何言ってるのかわからねーと思うが,俺も何書いてるのかわからねー.頭がどうかなりそうなんだが,使うだけなら「汎用 Mix-in フレームワーク」 としてあるところはそのままコピってヘッダファイルか何かに放り込んでしまえば良い.やるべきことは,それ以外の部分をがりがり書くだけ.
BOOST.PP により,任意個数のメソッドを持つ Mix-in に対応している.インターフェイスが別のインターフェイスを継承する場合は,mixin::c::imix の第2引数に基底インターフェイスを指定すればよい.
インターフェイスがいらなければ MIXIN の宣言もインターフェイスの定義も必要ない. mixin::c::mix の第2引数はデフォルトで mixin::c::dummy なので,単に第2引数を省略してやればよい.
注意点としては, mixin::c 名前空間の中にクラス名 idummy, dummy, 及びクラステンプレート名 ibase, imix, base, mix が展開される.ここで名前の輸出コードは mixin::c 中に展開されるので,この中で他で定義した idummy とかを使っているとおかしなことになる.問題になる場合はよそで定義したほうのクラスを名前空間に入れて,修飾名で指定してほしい.
また引数を取らないメソッドには対応していない.mix の中で各 Mix-in の完全修飾名を typedef することも考えたんだが,ちょっとめんどそうなので,ダミーの引数を取るとかで回避してくれ.
ついでに, Mix-in が protected なメソッドを提供する場合のフレームワークも提供しておこうか(あまり使わないと思うが).
#define DEF_MIXIN_CALL_BEGIN_WITH_PROTECTED(ns, tname, seq, pseq) \
namespace mixin { \
namespace ns { \
class dummy {}; \
template <typename _BASE> \
class base : public _BASE \
{ \
public: \
BOOST_PP_SEQ_FOR_EACH(_DEF_DUMMYBASE_METHOD, _, seq) \
protected: \
BOOST_PP_SEQ_FOR_EACH(_DEF_DUMMYBASE_METHOD, _, pseq) \
}; \
template <typename _MITER, typename _BASE> class _MIX1; \
template <typename _TLIST, typename _BASE = dummy> \
class mix \
: public boost::mpl::iter_fold \
<_TLIST, base<_BASE>, \
_MIX1<boost::mpl::_2, boost::mpl::_1> >::type \
{}; \
template <typename _MITER, typename _BASE> \
class _MIX1 \
: public _BASE, \
private boost::mpl::deref<_MITER>::type \
{ \
public: \
typedef typename boost::mpl::deref<_MITER>::type tname; \
protected: \
BOOST_PP_SEQ_FOR_EACH(_DEF_CLASS_USING, _, pseq) \
public : \
BOOST_PP_SEQ_FOR_EACH(_DEF_CLASS_USING, _, seq)
メソッド void pmethod(T* t) が protected なメソッドとして,使い方は
DEF_MIXIN_CALL_BEGIN_WITH_PROTECTED(c, mixin_t, (add) (remove), (pmethod))
void add(typename mixin_t::t_type* t) { mixin_t::add(t); }
void remove(typename mixin_t::t_type* t) { mixin_t::remove(t); }
protected:
void pmethod(typename mixin_t::t_type* t) { mixin_t::pmethod(t); }
DEF_MIXIN_CALL_END
だ. Container クラスで
template <typename T>
void method(T* t) { pmethod(t); }
みたいに使ってくれ.
もう問題は残ってない・・・よな?
&trackback()
----
**参考
-[[C++でMix-inもどき>http://sato-www.cs.titech.ac.jp/isobe/mix-in.html]]
-[[template で Mix-in>http://blogs.wankuma.com/episteme/archive/2006/10/08/40928.aspx]]
----
**コメント
#comment_num2(size=40,vsize=10,num=20,logpage=コメントログ)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(mix-in)
2010-12-28T18:31:33+09:00
1293528693
-
singleton × thread
https://w.atwiki.jp/squirrel_code/pages/24.html
* singleton × thread
#right{last update: &update(format=Y/m/d (D))}
さて,この記事のテーマとは singleton と thread でどちらが攻めでどちらが○けか,''ではない!''
なんてタイトルをつけやがる….期待させて申し訳ないが(ありえない) Singleton とは,プログラム内でインスタンスが 1 つしか存在しないことを保証するクラスのこと. 要はグローバル変数の代わり.簡単なコードは以下の通り.
// singleton.h
#include <iostream>
#include <boost/thread.hpp>
class Singleton
{
private:
Singleton() {
std::cout << "Singleton : created" << std::endl;
}
Singleton(const Singleton&); // コピー禁止
Singleton& operator=(const Singleton&); // 代入禁止
public:
~Singleton() {
std::cout << "Singleton : deleted" << std::endl;
}
// インスタンス取得関数
static Singleton* getInstance();
};
// singleton.cpp
#include "singleton.h"
Singleton* Singleton::getInstance()
{
static Singleton instance;
return &instance;
}
さてここで疑問に思った.このインスタンス取得関数ってマルチスレッド安全なの?
調べると,「C では静的自動変数の初期化は,実行フローが最初に変数の宣言・初期化行を通過した時に行われる」とある.ならば Singleton のコンストラクタが何やら時間のかかる処理をやるとして,あるスレッドが Singleton のコンストラクタにいる間に他のスレッドがインスタンス取得関数を呼んだらどうなるんだろう.
てことで実験.
// singleton.h
#include <iostream>
#include <boost/thread.hpp>
class Singleton
{
private:
Singleton() {
// 1000 ミリ秒かかる処理
boost::thread::sleep(
boost::get_system_time()
+ boost::posix_time::milliseconds(1000));
std::cout << "Singleton : created" << std::endl;
}
Singleton(const Singleton&); // コピー禁止
Singleton& operator=(const Singleton&); // 代入禁止
public:
~Singleton() {
std::cout << "Singleton : deleted" << std::endl;
}
// インスタンス取得関数
static Singleton* getInstance();
};
// singleton.cpp
#include "singleton.h"
#include <cstdlib>
Singleton* Singleton::getInstance()
{
static Singleton instance;
return &instance;
}
void func()
{
std::cout << "func : begin" << std::endl;
// インスタンス取得関数の呼び出し
Singleton* singleton = Singleton::getInstance();
std::cout << "func : end" << std::endl;
}
int main(int argc, char* argv[])
{
boost::thread th1( &func );
boost::thread th2( &func );
boost::thread th3( &func );
th1.join();
th2.join();
th3.join();
return EXIT_SUCCESS;
}
結果.
func : begin
func : begin
func : begin
Singleton : created # この直前で1秒固まる
func : end
func : end
func : end
Singleton : deleted
マルチスレッド安全でした&footnote(あくまで gcc では.他の処理系では知りません.規格上はどうなってるんだろう…).なんだつまらん(笑)
&trackback()
----
**参考
-[[デザインパターン編第9章 Singleton パターン - Programming Place Plus>http://www.geocities.jp/ky_webid/design_pattern/009.html]]
-[[letsboost::thread>http://www.kmonos.net/alang/boost/classes/thread.html]]
----
**コメント
#comment_num2(size=40,vsize=10,num=20,logpage=コメントログ)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(singleton)
2010-12-28T17:25:22+09:00
1293524722
-
続続 template × mix-in
https://w.atwiki.jp/squirrel_code/pages/22.html
* 続続 template × mix-in
#right{last update: &update(format=Y/m/d (D))}
[[なんとかならんのか.>続 template × mix-in]]
というわけで,なんとかしてみる.
** その4
まず, Interface について考えてみる.重要な制約は, C++ では関数テンプレートが仮想にできないという点だ.よって最後に MixExport した関数テンプレートは,どう頑張っても Interface からは呼べないことになる.やはりこの方針はまずい.
で,いろいろ調べていたら using 宣言というのが目に止まった. using namespace std の using ディレクティブとは違い,メソッドやメンバを「引数」にとる.通常,基底クラスで宣言されたメソッドなりメンバなりは,派生クラスで同名のメンバやメソッドが宣言されると隠蔽される.しかし using 宣言することにより,隠蔽をはずすことができる.これだ!
書いてみる.
// mixin.h
#include <list> // for std::list
#include <algorithm> // for std::find
// コンテナに格納されるクラス
class A { /* 省略 */ }
class B { /* 省略 */ }
// コンテナ機能を付加する Mix-in のインターフェイステンプレート
template <typename T>
class MI
{
public:
virtual ~MI() {}
void add(T* t) = 0;
void remove(T* t) = 0;
};
// コンテナ機能を付加する Mix-in のテンプレート
template <typename T>
class MixIn
{
std::list<T> ts;
public:
typedef T type;
MixIn() {}
~MixIn() {}
void add(T* t);
void remove(T* t);
};
// MixIn の実装
template <typename T>
void MixIn<T>::add(T* t)
{ ts.push_back(t); }
template <typename T>
void MixIn::remove(T* t);
{
typedef typename std::list<T>::iterator iter;
iter i = std::find(ts.begin(), ts.end(), t);
if (i != ts.end()) ts.erase(i++);
}
// デフォルトの基底クラス
class DummyBase
{
public:
void add();
void remove();
};
// インターフェイスのメソッド名を輸出するテンプレート
template <typename M, typename Base = DummyBase>
class IMix : public Base, public M
{
public:
using Base::add;
using Base::remove;
using M::add;
using M::remove;
};
// 実装を Mix-in しながら名前を輸出するテンプレート
template <typename M, typename Base = DummyBase>
class Mix : public Base, private M
{
public:
using Base::add;
using Base::remove;
void add(typename M::type* t)
{ M::add(t); }
void remove(typename M::type* t)
{ M::remove(t); }
};
// Mix-in されるインタフェイス
class Interface
: public IMix<MI<A>, IMix<MI<B> > >
{
public:
virtual Interface() {}
};
// Mix-in されるクラス
class Container
: public Mix<MixIn<A>, Mix<MixIn<B>, Interface> >
{
public:
Container() {}
~Container() {}
};
// mixin.cpp
#include "mixin.h"
ブラボー.インターフェイスも実装も再利用でき,無駄な名前の輸出もなく,加えて Interface のポインタにも MI<A> や MI<B> のポインタにも代入できる.もちろんどのポインタ経由でもデストラクタがちゃんと呼ばれる.
さて,これで万事解決だろうか.IMix<MI<A>, IMix<MI<B> > > とか Mix<MixIn<A>, Mix<MixIn<B>, Interface> > とか,ちょっとややこしすぎないだろうか.
こういう再帰っぽいパターンってなんとなく関数プログラミングっぽい.そういえば boost のテンプレートメタプログラミングって関数プログラミングっぽいと聞いた覚えがある.ん,何かいいのある?・・・と調べていたら, [[そっくりなの>http://www.kmonos.net/alang/boost/classes/mpl.html]] があった.これを改造して, Mix-in に使えないだろうか.
やってみる.
** その5
// mixin.h
#include <list> // for std::list
#include <algorithm> // for std::find
#include <boost/mpl/list.hpp>
#include <boost/mpl/iter_fold.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/type.hpp>
namespace mpl = boost::mpl;
using mpl::_1;
using mpl::_2;
// コンテナに格納されるクラス
class A { /* 省略 */ }
class B { /* 省略 */ }
// コンテナ機能を付加する Mix-in のインターフェイステンプレート
template <typename T>
class MI
{
public:
virtual ~MI() {}
void add(T* t) = 0;
void remove(T* t) = 0;
};
// コンテナ機能を付加する Mix-in のテンプレート
template <typename T>
class MixIn
{
std::list<T> ts;
public:
typedef T t_type;
MixIn() {}
~MixIn() {}
void add(T* t);
void remove(T* t);
};
// MixIn の実装
template <typename T>
void MixIn<T>::add(T* t)
{ ts.push_back(t); }
template <typename T>
void MixIn::remove(T* t);
{
typedef typename std::list<T>::iterator iter;
iter i = std::find(ts.begin(), ts.end(), t);
if (i != ts.end()) ts.erase(i++);
}
// デフォルトの基底クラス
class DummyBase
{
public:
void add();
void remove();
};
// 型イテレータと基底を受け取りインターフェイスを返す
template <typename MIter, typename Base>
class IMix1 : public Base, public mpl::deref<MIter>::type
{
public:
typedef typename mpl::deref<MIter>::type M;
using Base::add;
using Base::remove;
using M::add;
using M::remove;
};
// 型リストに含まれるインターフェイスの Mix-in
template <typename TList>
class IMix
: public mpl::iter_fold<TList, DummyBase, IMix1<_2, _1> >::type
{};
// 型イテレータと基底を受け取り Mix-in されたクラスを返す
template <typename MIter, typename Base>
class Mix1 : public Base, private mpl::deref<MIter>::type
{
public:
typedef typename mpl::deref<MIter>::type M;
using Base::add;
using Base::remove;
void add(typename M::t_type* t) { M::add(t); }
void remove(typename M::t_type* t) { M::remove(t); }
};
// 型リストに含まれる MixIn の Mix-in
template <typename TList, typename Base = DummyBase>
class Mix
: public mpl::iter_fold<TList, Base, Mix1<_2, _1> >::type
{};
// インターフェイス
class Interface
: public IMix<mpl::list<MI<A>, MI<B> > >
{
public:
virtual ~Interface() {}
};
// Mix-in されるクラス
class Container
: public Mix<mpl::list<MixIn<A>, MixIn<B> >, Interface>
{
public:
Container() {}
~Container() {}
};
// mixin.cpp
#include "mixin.h"
こんなのがよくコンパイラ通るもんだとちょっと感心^^.MixIn のインターフェイス,Interface クラス,MixIn クラス,Container クラスいずれも非常にシンプル. MPL すげー.
ここで疑問.実装側も IMix 使えばいいんじゃね?
これはたぶんできる(確認するのめんどくさい^^).こうすると Mix と IMix を 2 つ書く必要がないし, [[続続続 template × mix-in]] で書くような汎用 Mix-in フレームワークで名前の輸出をわざわざコーディングする必要もなくなる.
ただし, Container クラスが MixIn<A> や MixIn<B> の公開派生クラスとなるので, Container クラスのポインタを MixIn<A> や MixIn<B> のポインタに代入できてしまう.この状態で delete されうることを考えると, MixIn のデストラクタを virtual にしないといけない.
好みの問題だと思うが,実装は public 継承しないほうが美しいと思う.小規模なプログラムを書く程度なら, IMix の使いまわしでいいかもね.
&trackback()
----
**参考
-[[C++でMix-inもどき>http://sato-www.cs.titech.ac.jp/isobe/mix-in.html]]
-[[template で Mix-in>http://blogs.wankuma.com/episteme/archive/2006/10/08/40928.aspx]]
----
**コメント
#comment_num2(size=40,vsize=10,num=20,logpage=コメントログ)
----
**関連ページ
#related()
----
**関連ブログ
#blogsearch(mix-in)
2010-12-28T17:20:54+09:00
1293524454