クロージャデザインパターン

Moriharu Ohzu
Moriharu Ohzu日本工学院八王子専門学校
C++11ラムダ式によるデザインパターン
クロージャデザインパターン
日本工学院八王子専門学校
大圖 衛玄
自己紹介
@mozmoz1972
1992年~1997年
某ゲーム会社
プログラマ
SFC,GB,PS1,N64のゲーム開発経験
1998年~現在
日本工学院八王子専門学校
専任講師
プログラミング教育を中心に担当
CEDEC2015で講演したスライド資料に解説を加えたものです。
http://www.slideshare.net/MoriharuOhzu/ss-9224836
CEDEC2011講演。
http://www.slideshare.net/MoriharuOhzu/ss-14083300
CEDEC2012講演。
クロージャデザインパターン
https://www.youtube.com/watch?v=HooktFpS5CA
CEDEC2014ではハートランド・データ様との共同講演。スライドに文字がありませんので動画でご覧ください。
6月に執筆した書籍が発売されました!
「オブジェクト指向できていますか?」の最後のスライド。本セッションの予告のつもりでした。しかし、CEDEC2013の公募で落選。
HEY!閉包!
Closure Design Patterns
日本工学院八王子専門学校
大圖 衛玄
公募の採択を願ってインパクトのあるセッション名で再チャレンジ。しかし、わかりずらいとの理由で変更を余儀なくされました・・・
#01
Lambda
Expressions
C++11のラムダ式の基本文法をおさらいします。ちなみに写真は伝説のλシールドです。
void sample1() {
// 関数内に関数を作成
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(10, 20) << std::endl;
}
ラムダ式の概要を説明します。
void sample1() {
// 関数内に関数を作成
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(10, 20) << std::endl;
}
ラムダ式を使うと関数内に関数を作成できます。
void sample2() {
std::vector<int> = {1, 2, 3, 4, 5};
// 関数の引数内に関数を作成
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; });
}
void sample1() {
// 関数内に関数を作成
auto add = [](int a, int b) -> int { return a + b; };
std::cout << add(10, 20) << std::endl;
}
関数の引数内に関数を作ることもできます。おもに関数内で一時的に使用する小さな関数を作成する機能だと考えてください。
[]{}; 何もしないラムダ式
何もしない最小限のラムダ式です。
[]{};
[](int a) { std::cout << a; };
何もしないラムダ式
引数あり
引数がある場合は、()の中に引数を書きます。
[]{};
[](int a) { std::cout << a; };
[](int a) -> int { return a * 10; };
何もしないラムダ式
引数あり
引数あり、戻り値あり
戻り値がある場合は->の後ろに戻り値の型を書きます。
[]{};
[](int a) { std::cout << a; };
[](int a) -> int { return a * 10; };
[n](int a) -> int { return a * n; };
何もしないラムダ式
引数あり
引数あり、戻り値あり
変数nをコピーキャプチャ
変数キャプチャはラムダ式の外側にある変数を取込む機能です。コピーキャプチャは変数の内容をコピーして取込みます。
[]{};
[](int a) { std::cout << a; };
[](int a) -> int { return a * 10; };
[n](int a) -> int { return a * n; };
[=](int a) -> int { return a * n; };
何もしないラムダ式
引数あり
引数あり、戻り値あり
変数nをコピーキャプチャ
デフォルトコピーキャプチャ
[=]と書くと、キャプチャする変数はすべてコピーして取込むという意味になります。
[]{};
[](int a) { std::cout << a; };
[](int a) -> int { return a * 10; };
[n](int a) -> int { return a * n; };
[=](int a) -> int { return a * n; };
[=](int a) { return a * n; };
何もしないラムダ式
引数あり
引数あり、戻り値あり
変数nをコピーキャプチャ
デフォルトコピーキャプチャ
戻り値型の省略 (return文のみの場合)
return文だけのラムダ式であれば戻り値型の省略ができます。
[n]() { std::cout << ++n; }; コピーキャプチャの変数は変更不可
コピーキャプチャした変数は通常変更できません。この例はコンパイルエラーとなります。
[n]() mutable { std::cout << ++n; };
[n]() { std::cout << ++n; }; コピーキャプチャの変数は変更不可
mutableで変更可能
mutableキーワードを追加すれば、コピーキャプチャした変数を変更できます。(コピー元の変数は変更されません)
[&n]() { std::cout << ++n; };
[n]() { std::cout << ++n; }; コピーキャプチャの変数は変更不可
[n]() mutable { std::cout << ++n; }; mutableで変更可能
参照キャプチャ
&をつけると参照でキャプチャします。アドレスをキャプチャするので、取り込んだ元の変数の内容も変更されます。
[n]() { std::cout << ++n; };
[&n]() { std::cout << ++n; };
[&]() { std::cout << ++n; };
コピーキャプチャの変数は変更不可
参照キャプチャ
デフォルト参照キャプチャ
[n]() mutable { std::cout << ++n; }; mutableで変更可能
[&]とすると変数をすべて参照でキャプチャします。
[&]() { std::cout << ++n; };
[a, &n]() { return a * ++n; };
デフォルト参照キャプチャ
個別指定でキャプチャ可能
[n]() { std::cout << ++n; };
[&n]() { std::cout << ++n; };
コピーキャプチャの変数は変更不可
参照キャプチャ
[n]() mutable { std::cout << ++n; }; mutableで変更可能
コピーと参照は個別に指定できます。この例の場合はaはコピー、nは参照でキャプチャします。
[&]() { std::cout << ++n; };
[a, &n]() { return a * ++n; };
デフォルト参照キャプチャ
個別指定でキャプチャ可能
[a = b](auto n) { return a * n; }; 初期化キャプチャ・ジェネリックラムダ
C++14
[n]() { std::cout << ++n; };
[&n]() { std::cout << ++n; };
コピーキャプチャの変数は変更不可
参照キャプチャ
[n]() mutable { std::cout << ++n; }; mutableで変更可能
C++14では初期化キャプチャと引数の型を推論するジェネリックラムダが追加されました。
void lambda_sample(std::vector<int>& nums, int a) {
std::for_each(nums.begin(), nums.end(),
[a](int n) { std::cout << n * a << std::endl; });
}
ラムダ式の正体について説明します。このラムダ式は実際にどのような扱いになるのでしょうか?
void lambda_sample(std::vector<int>& nums, int a) {
std::for_each(nums.begin(), nums.end(),
[a](int n) { std::cout << n * a << std::endl; });
}
void lambda_sample(std::vector<int>& nums, int a) {
class lambda {
int a_; // キャプチャした変数
public:
lambda(int a) : a_(a) {}
void operator()(int n) const {
std::cout << n * a_ << std::endl;
}
};
std::for_each(nums.begin(), nums.end(), lambda(a));
}
コンパイラが
関数オブジェクトを生成
変数キャプチャをするラムダ式は、無名の関数オブジェクトに変換されると考えてください。(この例はイメージです)
void lambda_sample(std::vector<int>& nums) {
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; });
}
変数キャプチャなし
変数キャプチャをしないラムダ式はどうなるのか?
void lambda_sample(std::vector<int>& nums) {
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; });
}
void lambda_sample(std::vector<int>& nums) {
void lambda(int n) {
std::cout << n << std::endl;
}
std::for_each(nums.begin(), nums.end(), &lambda);
}
変数キャプチャなしの
ラムダ式は通常の関数と同じ扱い
変数キャプチャなし
※イメージです
変数キャプチャをしないラムダ式は単純な関数と同じ扱いになります。
void lambda_sample(std::vector<int>& nums) {
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; });
}
void lambda_sample(std::vector<int>& nums) {
void lambda(int n) {
std::cout << n << std::endl;
}
std::for_each(nums.begin(), nums.end(), &lambda);
}
auto lambda = [](int n) { std::cout << n << std::endl; };
void (*fun_ptr)(int) = lambda;
fun_ptr(10);
変数キャプチャなしの
ラムダ式は通常の関数と同じ扱い
変数キャプチャなしのラムダ式は
C言語の関数ポインタに代入可能
変数キャプチャなし
※イメージです
変数キャプチャをしないラムダ式はC言語の関数ポインタに代入できます。
void lambda_sample(std::vector<int>& nums, int a) {
int b = 10;
std::for_each(nums.begin(), nums.end(),
[a, b](int n) { std::cout << n * a * b << std::endl; });
}
続けて、クロージャの説明をします。
lambda_sample
b
anums
クロージャは
静的スコープ内にある変数を取込める
void lambda_sample(std::vector<int>& nums, int a) {
int b = 10;
std::for_each(nums.begin(), nums.end(),
[a, b](int n) { std::cout << n * a * b << std::endl; });
}
closure
a
b
n
クロージャは通常の関数と異なり、静的スコープ(構文スコープ)内にある「ローカル変数」を取込めます。
lambda_sample
b
a
for_each
begin end
nums
closure
a
b
n
クロージャは
静的スコープ内にある変数を取込める
取込んだ変数と共に他の関数内に入る
func
void lambda_sample(std::vector<int>& nums, int a) {
int b = 10;
std::for_each(nums.begin(), nums.end(),
[a, b](int n) { std::cout << n * a * b << std::endl; });
}
closure
a
b
n
クロージャは小さなクラスと同等の機能を持ちます。
#02
Higher-Order
Function
高階関数について説明します。高階関数は関数型プログラミングの重要なファクタの1つです。
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; });
for_each関数などのC++標準ライブラリの関数は昔から高階関数を利用しています。
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; }); 関数の引数に関数
高階関数とは関数の「引数として関数」を持ったり、関数の「戻り値として関数」を返す関数を指します。
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; }); 関数の引数に関数
auto result = std::count_if(nums.begin(), nums.end(),
[](int n) { return (n % 2) != 0; });
count_if関数は条件式に一致する要素を数えます。条件を調べる関数を引数として渡します。このような関数を述語と呼びます。
auto result = std::count_if(nums.begin(), nums.end(),
[](int n) { return (n % 2) != 0; });
auto result = std::accumulate(nums.begin(), nums.end(), 1,
[](int acc, int n) { return acc * n; });
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; }); 関数の引数に関数
accumulate関数は要素の集計をする関数です。集計に必要な計算部分を関数として渡します。
std::function<int (int)> compose(std::function<int (int)> f,
std::function<int (int)> g) {
return [=](int n) { return g(f(n)); };
} 関数の戻り値に関数
auto result = std::count_if(nums.begin(), nums.end(),
[](int n) { return (n % 2) != 0; });
auto result = std::accumulate(nums.begin(), nums.end(), 1,
[](int acc, int n) { return acc * n; });
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << std::endl; }); 関数の引数に関数
関数の戻り値として関数を返す例です。2つの関数を合成した関数を返す関数になります。
forEach(nums, [](int n) { std::cout << n << std::endl; });
それでは、独自の高階関数の作り方を説明します。ラムダ式などの関数を引数にとる部分をどうすればようでしょうか?
forEach(nums, [](int n) { std::cout << n << std::endl; });
template <typename Func>
void forEach(std::vector<int>& nums, Func func) {
for (int n: nums) {
func(n);
}
} templateのパラメータを利用
関数テンプレートを使う方法です。関数を引数に取る部分を関数テンプレートのパラメータにします。
template <typename Func>
void forEach(std::vector<int>& nums, Func func) {
for (int n: nums) {
func(n);
}
}
void forEach(std::vector<int>& nums, std::function<void (int)> func){
for (int n: nums) {
func(n);
}
}
forEach(nums, [](int n) { std::cout << n << std::endl; });
templateのパラメータを利用
std::functionを利用
関数テンプレートを使わない場合は、C++標準ライブラリのstd::functionを使います。
// 関数
int add(int a, int b) {
return a + b;
}
// 関数オブジェクト
class Functor {
public:
int operator() (int a, int b) {
return a * b;
}
};
void function_sample() {
std::function<int (int, int)> f;
f = &add; // 関数ポインタ
f = Functor(); // 関数オブジェクト
f = [](int a, int b) { return a / b; }); // ラムダ式
int n = f(20, 10); // functionから関数を呼び出す
}
std::function<戻り値の型(引数の型)>
std::functionの説明です。テンプレートの型指定部分に、代入したい関数の戻り値の型と引数の型を指定します。
// 関数
int add(int a, int b) {
return a + b;
}
// 関数オブジェクト
class Functor {
public:
int operator() (int a, int b) {
return a * b;
}
};
void function_sample() {
std::function<int (int, int)> f;
f = &add; // 関数ポインタ
f = Functor(); // 関数オブジェクト
f = [](int a, int b) { return a / b; }); // ラムダ式
int n = f(20, 10); // functionから関数を呼び出す
}
関数を代入するための変数と考える
std::function<戻り値の型(引数の型)>
関数の引数と戻り値の型が一致していれば、ラムダ式の他にも関数ポインタや関数オブジェクトを代入できます。
// 関数
int add(int a, int b) {
return a + b;
}
// 関数オブジェクト
class Functor {
public:
int operator() (int a, int b) {
return a * b;
}
};
void function_sample() {
std::function<int (int, int)> f;
f = &add; // 関数ポインタ
f = Functor(); // 関数オブジェクト
f = [](int a, int b) { return a / b; }); // ラムダ式
int n = f(20, 10); // functionから関数を呼び出す
}
関数を代入するための変数と考える
std::function<戻り値の型(引数の型)>
std::function型の変数から間接的に関数を呼び出すことができます。
Filter Map Fold
関数型プログラミングで最も有用な高階関数と言われるfilter・map・foldの紹介をします。
1 2 3 4 5 6 7 8 9
filter・map・foldはリストや配列などのコンテナに対する操作になります。
Filter
1 2 3 4 5 6 7 8 9
1 3 5 7 9
filterはある条件を満たすものだけを抽出する操作になります。
Filter
Map
1 2 3 4 5 6 7 8 9
1 3 5 7 9
2 6 10 14 18
mapはデータの変換を行います。
Filter
50
Fold
Map
1 2 3 4 5 6 7 8 9
1 3 5 7 9
2 6 10 14 18
foldはデータを集計して1つにまとめます。畳込み関数と言います。reduceやinjectと呼ばれることもあります。
抽 出
50
集 計
変 換
1 2 3 4 5 6 7 8 9
1 3 5 7 9
2 6 10 14 18
奇数を抽出
50
合計する
2倍に変換
1 2 3 4 5 6 7 8 9
1 3 5 7 9
2 6 10 14 18
今回の例では、奇数を抽出、2倍に変換、最後に合計するという操作を行いました。
int filter_map_fold(const std::vector<int>& nums) {
}
C++11版のfilter・map・foldを紹介します。
int filter_map_fold(const std::vector<int>& nums) {
// 奇数を抽出 (filter)
std::vector<int> odds;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(odds),
[](int n) { return (n % 2) != 0; });
}
filterに相当する関数はcopy_if関数になります。back_inserterはコピー先のコンテナにデータを追加するイテレータを作成します。
int filter_map_fold(const std::vector<int>& nums) {
// 奇数を抽出 (filter)
std::vector<int> odds;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(odds),
[](int n) { return (n % 2) != 0; });
// 2倍に変換 (map)
std::vector<int> doubles;
std::transform(odds.begin(), odds.end(),
std::back_inserter(doubles),
[](int n) { return n * 2; });
}
mapに相当する関数はtransform関数になります。
int filter_map_fold(const std::vector<int>& nums) {
// 奇数を抽出 (filter)
std::vector<int> odds;
std::copy_if(nums.begin(), nums.end(),
std::back_inserter(odds),
[](int n) { return (n % 2) != 0; });
// 2倍に変換 (map)
std::vector<int> doubles;
std::transform(odds.begin(), odds.end(),
std::back_inserter(doubles),
[](int n) { return n * 2; });
// 合計する (fold)
return std::accumulate(doubles.begin(), doubles.end(), 0,
[](int acc, int n) { return acc + n; });
}
foldに相当する関数はaccumulate関数になります。しかし、標準ライブラリ関数の組み合わせは、あまりスマートとは言えません。
LINQ for C++
そこで、C#のLINQ風に書けるC++11のライブラリ「LINQ for C++ 」を紹介します。filter・map・foldをスマートに書けます。
static int filter_map_fold(List<int> nums) {
return nums.Where(n => (n % 2) != 0)
.Select(n => n * 2)
.Aggregate(0, (acc, n) => acc + n);
} C#のLINQ
C#のLINQで先ほどのC++11の例を書いてみます。とても簡潔に書けます。
int filter_map_fold(const std::vector<int>& nums) {
return cpplinq::from(nums)
>> cpplinq::where([](int n) { return (n % 2) != 0; })
>> cpplinq::select([](int n) { return n * 2; })
>> cpplinq::aggregate(0, [](int acc, int n) { return acc + n; });
}
static int filter_map_fold(List<int> nums) {
return nums.Where(n => (n % 2) != 0)
.Select(n => n * 2)
.Aggregate(0, (acc, n) => acc + n);
} C#のLINQ
LINQ for C++
LINQ for C++との比較。C#のLINQと同様に無駄なループや中間データの作成をできるだけしないように実装されています。
int filter_map_fold(const std::vector<int>& nums) {
return cpplinq::from(nums)
>> cpplinq::where([](int n) { return (n % 2) != 0; })
>> cpplinq::select([](int n) { return n * 2; })
>> cpplinq::sum();
}
static int filter_map_fold(List<int> nums) {
return nums.Where(n => (n % 2) != 0)
.Select(n => n * 2)
.Sum();
} C#のLINQ
LINQ for C++
単純な合計であればsum関数を使います。合計・平均・最小値・最大値などを求める集計関数が、あらかじめ用意されています。
struct Person {
std::string name; // 氏名
int age; // 年齢
int salary; // 月収
};
// 人物データ
std::vector<Person> parsons = {
{"鈴木", 25, 20}, {"田中", 45, 50}, {"佐藤", 55, 60} … };
もう少し実用的なfilter・map・foldの例を紹介します。人物データの集合から40歳以上の平均月収を求めてみます。
struct Person {
std::string name; // 氏名
int age; // 年齢
int salary; // 月収
};
// 人物データ
std::vector<Person> parsons = {
{"鈴木", 25, 20}, {"田中", 45, 50}, {"佐藤", 55, 60} … };
// 40歳以上の平均月収を求める
auto avg = cpplinq::from(persons)
>> cpplinq::where([](const Person& man) { return man.age >= 40; })
>> cpplinq::select([](const Person& man) { return man.salary; })
>> cpplinq::avg();
whereで40歳以上の人物を抽出、selectで月収だけのデータに変換、avgで月収の平均を求めます。
boost range
LINQ for C++に類似するライブラリは他にも多数あります。有名ものとしてboost rangeがあります。
https://www.assetstore.unity3d.com/jp/#!/content/17276
リアクティブプログラミングは今後注目の技術になりそうです。イベント処理の流れをデータ列と見立ててLINQ風に処理します。
#03
Closure
Design Patterns
クロージャを使ったデザインパターンの紹介をします。
Pluggable
Behavior Pattern
オブジェクトの振る舞いを実行時に指定する
Pluggable Behaviorは実行時にオブジェクトの振る舞いを変化させるパターンです。
class Persons {
public:
void show() {
for (auto& person : persons_) {
if (person.age >= 20) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
struct Person {
std::string name; // 氏名
int age; // 年齢
int salary; // 月収
};
パターン適用前の状態です。20歳以上の人物の名前を表示します。show関数の振る舞いは変更できません。
class Persons {
public:
void show() {
for (auto& person : persons_) {
if (person.age >= 20) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
struct Person {
std::string name; // 氏名
int age; // 年齢
int salary; // 月収
};
年齢が20という定数になっています。この部分が変更可能になれば、他の条件の人物も表示できます。
class Persons {
public:
void show(int age) {
for (auto& person : persons_) {
if (person.age >= age) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
年齢を引数として指定できるようになれば、振る舞いの変更ができます。
class Persons {
public:
void show(int age) {
for (auto& person : persons_) {
if (person.age >= age) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
// 成人を表示
parsons.show(20);
// 年金受給者を表示
parsons.show(65);
しかし、ある年齢「以上」の判定はできますが、ある年齢「未満」の判定はできません。未成年だけ表示などには対応できません。
class Persons {
public:
void show(std::function<bool(Person&)> closure) {
for (auto& person : persons_) {
if (closure(person)) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
そこで、条件式の部分をクロージャに変更します。これにより、柔軟な振る舞いの変化を与えられます。
class Persons {
public:
void show(std::function<bool(Person&)> closure) {
for (auto& person : persons_) {
if (closure(person)) {
std::cout << person.name << std::endl;
}
}
}
private:
std::vector<Person> persons_;
};
// 未成年を表示
parsons.show([](Person& parson) { return parson.age < 20; });
// 月収100万円以上を表示
parsons.show([](Person& parson) { return parson.salary >= 100; });
年齢だけでなく、月収などを条件にすることもできます。クロージャによって条件式そのものを引数として渡せます。
Daynamical
Conditional
Execution Pattern
条件付き操作の作成と実行をする
class Persons {
public:
void show(
int age,
std::function<void (Person&)> adults,
std::function<void (Person&)> minors) {
for (auto& person : persons_) {
if (person.age >= age) {
adults(person);
} else {
minors(person);
}
}
}
private:
std::vector<Person> persons_;
};
Dynamical Conditional Execution Patternは条件式に対応する振る舞いをクロージャで変更可能にします。
Execute
Around Method Pattern
前後に実行しなければいけない操作のペアを表現する
プログラムでは初期化処理や終了処理など常に対に行うことがよくあります。
void setVec2(const char* name, float x, float y) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec2(x, y);
param->release();
}
}
void setVec3(const char* name, float x, float y, float z) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec3(x, y, z);
param->release();
}
}
名前でパラメータを検索して、値を設定する。値の設定が終わったらパラメータをreleaseする仕様とします。
void setVec2(const char* name, float x, float y) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec2(x, y);
param->release();
}
}
void setVec3(const char* name, float x, float y, float z) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec3(x, y, z);
param->release();
}
}
必ずペアで実行する定型処理
必ずペアで実行する定型処理
必ずペアで実行する定型処理があり、その部分が重複しています。
void setVec2(const char* name, float x, float y) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec2(x, y);
param->release();
}
}
void setVec3(const char* name, float x, float y, float z) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec3(x, y, z);
param->release();
}
}
必ずペアで実行する定型処理
必ずペアで実行する定型処理
値を設定する部分だけが異なります。
void setVec2(const char* name, float x, float y) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec2(x, y);
param->release();
}
}
定型処理の内部にある値を設定する部分をクロージャに変更します。
void setVec2(const char* name, float x, float y) {
Param* param = findParam(name);
if (param != nullptr) {
param->setVec2(x, y);
param->release();
}
}
void setParam(const char* name, std::function<void (Param*)> closure) {
Param* param = findParam(name);
if (param != nullptr) {
closure(param);
param->release();
}
}
クロージャを使うことにより、重複部分をまとめることができます。
void setVec2(const char* name, float x, float y) {
setParam(name, [=](Param* param) { param->setVec2(x, y); });
}
void setVec3(const char* name, float x, float y, float z) {
setParam(name, [=](Param* param) { param->setVec3(x, y, z); });
}
void setParam(const char* name, std::function<void (Param*)> closure) {
Param* param = findParam(name);
if (param != nullptr) {
closure(param);
param->release();
}
}
必ずペアで実行する処理の間に
クロージャを挟み込む
setVec2関数やsetVec3関数は、setParam関数を利用して作成します。これで定型処理がまとまり重複がなくなりました。
Lorn Patternリソースの確保・解放を確実に行う
Lorn Patternは確保したリソースを確実に解放するパターンです。Execute Around Patternと同じ発想のパターンです。
bool write_file(const char* fname, std::function<void (FILE*)> closure) {
FILE* fp;
errno_t error = fopen_s(&fp, fname, "w");
if (error != 0) {
return false;
}
closure(fp);
fclose(fp);
return true;
}
ベタな例になりますが、ファイルはオープンしたら、必ずクローズする必要があります。
bool write_file(const char* fname, std::function<void (FILE*)> closure) {
FILE* fp;
errno_t error = fopen_s(&fp, fname, "w");
if (error != 0) {
return false;
}
closure(fp);
fclose(fp);
return true;
}
クロージャの前後で
リソースの確保・解放
クロージャの前後でオープンとクローズをすれば、確実にリソースの確保と解放ができます。
bool write_file(const char* fname, std::function<void (FILE*)> closure) {
FILE* fp;
errno_t error = fopen_s(&fp, fname, "w");
if (error != 0) {
return false;
}
closure(fp);
fclose(fp);
return true;
}
write_file("test.txt", [](FILE* fp) { fputs("hello", fp); });
クロージャの前後で
リソースの確保・解放
ラムダ式の中にファイルに出力する処理を書きます。C++ではRAIIというコンストラクタとデストラクタを利用した方法もあります。
それでは、クロージャのデザインパターンのまとめをします。どのパターンでも共通する考え方を説明します。
関数
不変部分
不変部分
可変部分
例えば、関数内の一部に可変部分があって再利用しにくい場合があるとします。
関数
不変部分
不変部分
可変部分
可変部分を外側に出せば、関数内は不変部分だけとなり、再利用できるようになります。
関数
不変部分
不変部分
クロージャ
可変部分をクロージャに変更します。
高階関数
不変部分
不変部分
クロージャ クロージャ
クロージャを関数の引数にして、関数内に入り込ませます。関数を引数にもつ高階関数に変更します。
高階関数
不変部分
不変部分
クロージャ
ラムダ式
ラムダ式
ラムダ式
クロージャ
関数内の可変部分はラムダ式で作成します。これで関数の振る舞いを間接的に変更できます。
高階関数
不変部分
不変部分
クロージャ
ラムダ式
ラムダ式
ラムダ式
OPENCLOSED
クロージャ
変更に対して閉じている 拡張に対して開いている
開放・閉鎖の原則に基づいた設計となります。オブジェクト指向言語では抽象インターフェースを使って解決していました。
クロージャは「軽量な抽象インターフェース」として機能します。振る舞いの変更が簡単にできるようになります。
#04
GoF
Design Patterns
古典的なGoFのデザインパターンをクロージャで実装してみます。
Iterator Pattern
コンテナ内のオブジェクトを順番にアクセスする手段を提供する
コンテナのデータ構造を隠蔽したまま、コンテナ内のデータにアクセスできるようにするパターンです。
class Persons {
public:
typedef std::vector<Person>::iterator iter;
iter begin() {
return persons_.begin();
}
iter end() {
return persons_.end();
}
private:
std::vector<Person> persons_;
};
外部からアクセスするための
手段を提供をする
従来のIterator Patternの実装例です。外部から内部のデータにアクセスできるようにiteratorクラスを作成して返すようにします。
class Persons {
public:
typedef std::vector<Person>::iterator iter;
iter begin() {
return persons_.begin();
}
iter end() {
return persons_.end();
}
private:
std::vector<Person> persons_;
};
for (Persons::iter i = persons.begin(); i != persons.end(); ++i) {
std::cout << (*i).name << std::endl;
}
外部からアクセスするための
手段を提供をする
利用者側がループしてアクセス
利用者側がループを使って内部のデータにアクセスします。
class Persons {
public:
void each(std::function<void (Person&)> closure) {
for (auto& person : persons_) {
closure(person);
}
}
private:
std::vector<Person> persons_;
};
内部でループさせる
クロージャを使った方法に変更します。コンテナ内のデータをクロージャに渡すようにします。
class Persons {
public:
void each(std::function<void (Person&)> closure) {
for (auto& person : persons_) {
closure(person);
}
}
private:
std::vector<Person> persons_;
};
persons.each(
[](Person& person) { std::cout << person.name << std::endl; });
内部でループさせる
利用者側でのループが不要
利用者側でのループが不要になります。ループの大幅な削減ができます。
class Persons {
public:
void each(std::function<void (Person&)> closure) {
for (auto& person : persons_) {
closure(person);
}
}
private:
std::vector<Person> persons_;
};
persons.each(
[](Person& person) { std::cout << person.name << std::endl; });
for (Persons::iter i = persons.begin(); i != persons.end(); ++i) {
std::cout << (*i).name << std::endl;
} 旧スタイル
新スタイル
従来の方法とクロージャを利用した方法の比較になります。
Command Pattern
命令をオブジェクトとして表現する
Command Patternは複数の命令をクラス化してあつかうパターンです。
// コマンド抽象インターフェース
class Command {
public:
virtual ~Command() {}
virtual void action() = 0;
};
従来の方法から紹介します。まず、複数の命令を1つにまとめるための抽象インターフェースを作成します。
// コマンド抽象インターフェース
class Command {
public:
virtual ~Command() {}
virtual void action() = 0;
};
class SleepCommand : public Command {
virtual void action() {
std::cout << "寝る" << std::endl;
}
};
具体的なコマンドは抽象インターフェースを実装して作成します。各命令はaction関数をオーバーライドします。
// コマンド抽象インターフェース
class Command {
public:
virtual ~Command() {}
virtual void action() = 0;
};
class SleepCommand : public Command {
virtual void action() {
std::cout << "寝る" << std::endl;
}
};
class AwakeCommand : public Command {
virtual void action() {
std::cout << "起きる" << std::endl;
}
};
「寝るコマンド」と「起きるコマンド」を作成しました。このようにコマンドが増えるたびにクラス数が増加します。
// コマンドリストクラス
class CommandList {
public:
// コマンドの追加
void add(Command* command) {
actions_.push_back(command);
}
// コマンドの実行
void execute() {
for (iter i = actions_.begin(); i != actions_.end(); ++i) {
(*i)->action();
}
}
private:
std::vector<Command*> actions_;
};
コマンドを溜め込んで、一気に実行するコマンドリストクラスを作成します。
CommadList commands;
commands.add(new AwakeCommand());
commands.add(new StadyCommand());
commands.add(new PlayCommand());
commands.add(new SleepCommand());
commands.execute();
コマンドリストの使用例になります。
class CommandList {
public:
// コマンドの追加
void add(std::function<void()> closure) {
actions_.push_back(closure);
}
// コマンドの実行
void execute() {
for (auto& action : actions_) {
action();
}
}
private:
std::vector<std::function<void()>> actions_;
};
クロージャのコンテナ
命令をクロージャ化
クロージャを使った例を紹介します。コマンドをクラスからクロージャに変更します。クロージャをコンテナに入れることもできます。
CommadList commands;
commands.add([]{ std::cout << "起きる"<< std::endl; });
commands.add([]{ std::cout << "勉強" << std::endl; });
commands.add([]{ std::cout << "遊ぶ" << std::endl; });
commands.add([]{ std::cout << "寝る" << std::endl; });
commands.execute();
ラムダ式でコマンドを作成します。コマンドごとにクラスを作る必要はありません。コマンドが単純な場合に有効な方法です。
class CommandList {
public:
// コマンドの追加
void add(std::function<void()> next) {
const auto& prev = action_;
action_ = [=] { prev(); next(); };
}
// コマンドの実行
void execute() {
action_();
}
private:
std::function<void()> action_ = []{};
};
関数の合成
コンテナ不要
ループ不要
関数の合成を利用するとコンテナを使わずに複数のコマンドを1つにまとめることができます。
class CommandList {
public:
// コマンドの追加
void add(std::function<void()> command) {
action_ = [prev = action_, next = command] { prev(); next(); };
}
// コマンドの実行
void execute() {
action_();
}
private:
std::function<void()> action_ = []{};
};
C++14
C++14の初期化キャプチャを使うと、よりわかりやすく書けます。
class CommandList {
public:
// コマンドの追加
void operator += (std::function<void()> next) {
const auto& prev = action_;
action_ = [=]() { prev(); next(); };
}
// コマンドの実行
void execute() {
action_();
}
private:
std::function<void()> action_ = []{};
};
C#のevent風にしてみる
おまけですが、add関数を+=のオペレータに変更してみます。C#のevent風の実装ができます。
CommadList commands;
commands += []{ std::cout << "起きる"<< std::endl; };
commands += []{ std::cout << "勉強" << std::endl; };
commands += []{ std::cout << "遊ぶ" << std::endl; };
commands += []{ std::cout << "寝る" << std::endl; };
commands.execute();
CommadList commands;
commands.add(new AwakeCommand());
commands.add(new StadyCommand());
commands.add(new PlayCommand());
commands.add(new SleepCommand());
commands.execute();
旧スタイル
新スタイル
従来の方法との比較になります。
Composite Pattern
オブジェクトを再帰的な木構造で表現する
続けてコマンドリストに、一工夫加えてComposite Patternを適用します。
class CommandList {
public:
// コマンドの追加
void operator += (std::function<void()> next) {
const auto& prev = action_;
action_ = [=] { prev(); next(); };
}
// コマンドの実行
void operator()() {
action_();
}
private:
std::function<void()> action_ = []{};
};
関数オブジェクト化する
コマンドを実行するexecute関数をoperator ()に変更して、コマンドリストクラスを関数オブジェクト化します。
CommadList basic_commands;
basic_commands += []{ std::cout << "食う" << std::endl; };
basic_commands += []{ std::cout << "寝る" << std::endl; };
basic_commands += []{ std::cout << "遊ぶ" << std::endl; };
CommadList combat_commands;
combat_commands += []{ std::cout << "撃つ" << std::endl; };
combat_commands += []{ std::cout << "伏せる" << std::endl; };
combat_commands += []{ std::cout << "ジャンプ" << std::endl; };
CommadList commands;
commands += basic_commands;
commands += combat_commands;
commands();
コマンドリストにコマンドリストを追加
std::functionはラムダ式だけでなく、関数オブジェクトも代入できます。これによりコマンドリストに別のコマンドリストを追加できます。
Abstract Factory Pattern
抽象化されたオブジェクトを生成する手段を提供する
具象クラスを生成して、抽象クラスを返すクラスとファクトリクラスと呼びます。
それでは、さっそくピザ具材工場を作成してみます。
// 具材抽象インターフェース
class Ingredients {
public:
virtual ~Ingredients() {}
virtual void draw() = 0;
};
まず、Abstract Factoryクラスが作成する具材の抽象インターフェースを作成します。
// 具材抽象インターフェース
class Ingredients {
public:
virtual ~Ingredients() {}
virtual void draw() = 0;
};
class Tomato : public Ingredients {
virtual void draw() {
std::cout << "トマト" << std::endl;
}
};
ピザと言えばトマトですね。
// 具材抽象インターフェース
class Ingredients {
public:
virtual ~Ingredients() {}
virtual void draw() = 0;
};
class Cheese : public Ingredients {
virtual void draw() {
std::cout << "チーズ" << std::endl;
}
};
class Tomato : public Ingredients {
virtual void draw() {
std::cout << "トマト" << std::endl;
}
};
チーズも忘れてはいけません。
// 抽象具材工場
class Factory {
public:
virtual ~Factory() {}
virtual Ingredients* create(const std::string& name) = 0;
};
具材工場を抽象化したクラスを作成します。工場そのものも抽象化してしまうのでAbstract Factoryと呼ばれます。
// ピザ具材工場
class PizzaFactory : public Factory {
public:
virtual Ingredients* create(const std::string& name) {
if (name == "Cheese") return new Cheese();
if (name == "Tomato") return new Tomato();
if (name == "Dough" ) return new Dough();
return 0;
}
};
// 抽象具材工場
class Factory {
public:
virtual ~Factory() {}
virtual Ingredients* create(const std::string& name) = 0;
};
ピザ具材工場の簡単な実装例です。具材名を指定すると、対応する具材を作成してくれます。
// 抽象具材工場
using Factory = std::function<Ingredients* (const std::string&)>;
次はクロージャを使った実装例を紹介します。工場のクラスは作成せずにstd::functionを使います。usingで別名を付けておきます。
// ピザ工場
Ingredients* pizzaFactory(const std::string& name) {
using Creator = std::function<Ingredients* ()>;
static const std::unordered_map<std::string, Creator> creators = {
{ "Dough", [] { return new Dough(); } },
{ "Cheese", [] { return new Cheese(); } },
{ "Tomato", [] { return new Tomato(); } }
};
return creators.at(name)();
}
// 抽象具材工場
using Factory = std::function<Ingredients* (const std::string&)>;
ピザ工場の実装も単なる関数となります。具材を作成する部分にラムダ式を使っています。このような使い方もできます。
// ピザ工場を作成
Factory* factory = new PizzaFactory();
// チーズを作成
Ingredients* cheese = factory->create("Cheese");
// ピザ工場を作成
Factory factory = &pizzaFactory;
// チーズを作成
Ingredients* cheese = factory("Cheese");
旧スタイル
新スタイル
従来の方法との比較になります。
GoFデザインパターンのクロージャによる実装のまとめをします。
<<interface>>
Command
+ action()
CommandList
PlayCommand
+ action()
SleepCommand
+ action()
AwakeCommand
+ action()
Command Patternの例を使って説明します。
<<interface>>
Command
+ action()
CommandList
PlayCommand
+ action()
SleepCommand
+ action()
AwakeCommand
+ action()
コマンドが増えるたびに
新しいクラスが必要になる
クラスを使った従来の方法では、コマンドの種類が増加するたびにクラス数が増加します。
<<interface>>
Command
+ action()
CommandList
PlayCommand
+ action()
std::function<void()>
[]{cout <<"遊ぶ"; }
CommandList
SleepCommand
+ action()
AwakeCommand
+ action()
[]{cout <<"起きる"; } []{cout <<"寝る"; }
コマンドが増えるたびに
新しいクラスが必要になる
コマンドが増えても
クラス数の増加はない
std::functionとラムダ式を使うとクラスが不要になります。抽象インターフェースも不要。コマンドが増えてもクラス数は増加しません。
<<interface>>
Command
+ action()
CommandList
AwakeCommand
+ action()
std::function<void()>
[]{cout <<"起きる"; }
CommandList 抽象インターフェースの役割
実装クラスの役割
抽象インターフェース
実装クラス
std::functionによって、クラスを使わない簡易的な実装に置き換えが可能です。
<<interface>>
Command
+ action()
AwakeCommand
+ action()
std::function<void()>
[]{cout <<"起きる"; }
1つのメンバ関数しか持たない小さな抽象インターフェースは、std::functionに置き換えが可能です。
<<interface>>
SingleAbstractMethod
+ method()
Context
ConcreateClass
+ method()
このクラス図のようなパターンであれば、std::functionとラムダ式による簡易的な実装に置き換えが可能です。
<<interface>>
SingleAbstractMethod
+ method()
std::function<>
[](){ }
Context
ConcreateClass
+ method()
Context
変
換
可
能
小さな抽象インターフェースをたくさん作る必要がなくなります。
#05
Conclusion
本セッション全体のまとめです。
void lambda_sample(std::vector<int>& nums, int a) {
std::for_each(nums.begin(), nums.end(),
[a](int n) { std::cout << n * a << std::endl; });
}
void lambda_sample(std::vector<int>& nums, int a) {
class lambda {
int a_; // キャプチャした変数
public:
lambda(int a) : a_(a) {}
void operator()(int n) const {
std::cout << n * a_ << std::endl;
}
};
std::for_each(nums.begin(), nums.end(), lambda(a));
}
コンパイラが
関数オブジェクトを生成
C++11のクロージャの正体は、関数オブジェクトになります。変数キャプチャがないラムダ式は従来の関数と同じ扱いです。
struct Person {
std::string name; // 氏名
int age; // 年齢
int salary; // 月収
};
// 人物データ
std::vector<Person> parsons = {
{"鈴木", 25, 20}, {"田中", 45, 50}, {"佐藤", 55, 60} … };
// 40歳以上の平均月収を求める
auto avg = cpplinq::from(persons)
>> cpplinq::where([](const Person& man) { return man.age >= 40; })
>> cpplinq::select([](const Person& man) { return man.salary; })
>> cpplinq::avg();
ラムダ式はおもに高階関数の引数として利用します。LINQ for C++は便利なので一度試してみてください。
高階関数
不変部分
不変部分
クロージャ
ラムダ式
ラムダ式
ラムダ式
OPENCLOSED
クロージャ
変更に対して閉じている 拡張に対して開いている
関数内の可変部分をクロージャにして関心事の分離をしましょう。
<<interface>>
Command
+ action()
CommandList
PlayCommand
+ action()
std::function<void()>
[]{cout <<"遊ぶ"; }
CommandList
SleepCommand
+ action()
AwakeCommand
+ action()
[]{cout <<"起きる"; } []{cout <<"寝る"; }
コマンドが増えるたびに
新しいクラスが必要になる
コマンドが増えても
クラス数の増加はない
std::functionを使えば、クラスを使わずに簡易的な実装ができます。
http://arturoherrero.com/closure-design-patterns/
クロージャデザインターンの参考資料です。GroovyとRubyのサンプルコードがあります。
https://isocpp.org/blog/2013/10/patterns
GoFデザインパターンの参考資料です。英語の資料ですがサンプルコードが豊富でわかりやすいです。
https://www.assetstore.unity3d.com/jp/#!/content/17276
これから注目すべき技術になるかもしれません。関数型プログラミングのスタイルをゲームプログラミングで応用した例になります。
Javaによる関数型プログラミングはラムダ式を効果的に使用する例がコンパクトにまとまっています。
本書でもC++11のラムダ式の効果的な使用例があります。学生や新入社員対象の書籍になっております。
クロージャデザインパターン
本スライドはラムダ計算騎士団の皆様に、ご協力いただきました。
1 de 146

Recomendados

オブジェクト指向できていますか? por
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?Moriharu Ohzu
237.5K vistas129 diapositivas
Unity ネイティブプラグインの作成について por
Unity ネイティブプラグインの作成についてUnity ネイティブプラグインの作成について
Unity ネイティブプラグインの作成についてTatsuhiko Yamamura
47.9K vistas49 diapositivas
中3女子でもわかる constexpr por
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexprGenya Murakami
49K vistas100 diapositivas
C++ マルチスレッド 入門 por
C++ マルチスレッド 入門C++ マルチスレッド 入門
C++ マルチスレッド 入門京大 マイコンクラブ
57.8K vistas53 diapositivas
Constexpr 中3女子テクニック por
Constexpr 中3女子テクニックConstexpr 中3女子テクニック
Constexpr 中3女子テクニックGenya Murakami
33K vistas138 diapositivas
すごいConstたのしく使おう! por
すごいConstたのしく使おう!すごいConstたのしく使おう!
すごいConstたのしく使おう!Akihiro Nishimura
13.3K vistas26 diapositivas

Más contenido relacionado

La actualidad más candente

Pythonによる黒魔術入門 por
Pythonによる黒魔術入門Pythonによる黒魔術入門
Pythonによる黒魔術入門大樹 小倉
44.4K vistas35 diapositivas
高速な倍精度指数関数expの実装 por
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装MITSUNARI Shigeo
15K vistas20 diapositivas
中3女子が狂える本当に気持ちのいい constexpr por
中3女子が狂える本当に気持ちのいい constexpr中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexprGenya Murakami
30.5K vistas58 diapositivas
BoostAsioで可読性を求めるのは間違っているだろうか por
BoostAsioで可読性を求めるのは間違っているだろうかBoostAsioで可読性を求めるのは間違っているだろうか
BoostAsioで可読性を求めるのは間違っているだろうかYuki Miyatake
14.3K vistas31 diapositivas
プログラムの処方箋~健康なコードと病んだコード por
プログラムの処方箋~健康なコードと病んだコードプログラムの処方箋~健康なコードと病んだコード
プログラムの処方箋~健康なコードと病んだコードShigenori Sagawa
8.9K vistas59 diapositivas
30分で分かる!OSの作り方 por
30分で分かる!OSの作り方30分で分かる!OSの作り方
30分で分かる!OSの作り方uchan_nos
40.3K vistas38 diapositivas

La actualidad más candente(20)

Pythonによる黒魔術入門 por 大樹 小倉
Pythonによる黒魔術入門Pythonによる黒魔術入門
Pythonによる黒魔術入門
大樹 小倉44.4K vistas
高速な倍精度指数関数expの実装 por MITSUNARI Shigeo
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装
MITSUNARI Shigeo15K vistas
中3女子が狂える本当に気持ちのいい constexpr por Genya Murakami
中3女子が狂える本当に気持ちのいい constexpr中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexpr
Genya Murakami30.5K vistas
BoostAsioで可読性を求めるのは間違っているだろうか por Yuki Miyatake
BoostAsioで可読性を求めるのは間違っているだろうかBoostAsioで可読性を求めるのは間違っているだろうか
BoostAsioで可読性を求めるのは間違っているだろうか
Yuki Miyatake14.3K vistas
プログラムの処方箋~健康なコードと病んだコード por Shigenori Sagawa
プログラムの処方箋~健康なコードと病んだコードプログラムの処方箋~健康なコードと病んだコード
プログラムの処方箋~健康なコードと病んだコード
Shigenori Sagawa8.9K vistas
30分で分かる!OSの作り方 por uchan_nos
30分で分かる!OSの作り方30分で分かる!OSの作り方
30分で分かる!OSの作り方
uchan_nos40.3K vistas
Visual C++で使えるC++11 por nekko1119
Visual C++で使えるC++11Visual C++で使えるC++11
Visual C++で使えるC++11
nekko111936.8K vistas
いまさら聞けないarmを使ったNEONの基礎と活用事例 por Fixstars Corporation
いまさら聞けないarmを使ったNEONの基礎と活用事例いまさら聞けないarmを使ったNEONの基礎と活用事例
いまさら聞けないarmを使ったNEONの基礎と活用事例
Fixstars Corporation5.1K vistas
すごい constexpr たのしくレイトレ! por Genya Murakami
すごい constexpr たのしくレイトレ!すごい constexpr たのしくレイトレ!
すごい constexpr たのしくレイトレ!
Genya Murakami25.6K vistas
組み込み関数(intrinsic)によるSIMD入門 por Norishige Fukushima
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門
Norishige Fukushima47.5K vistas
C++ マルチスレッドプログラミング por Kohsuke Yuasa
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミング
Kohsuke Yuasa107.7K vistas
オブジェクト指向エクササイズのススメ por Yoji Kanno
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメ
Yoji Kanno57.2K vistas
画像処理ライブラリ OpenCV で 出来ること・出来ないこと por Norishige Fukushima
画像処理ライブラリ OpenCV で 出来ること・出来ないこと画像処理ライブラリ OpenCV で 出来ること・出来ないこと
画像処理ライブラリ OpenCV で 出来ること・出来ないこと
Norishige Fukushima221.6K vistas
組み込みでこそC++を使う10の理由 por kikairoya
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya27K vistas
Quine・難解プログラミングについて por mametter
Quine・難解プログラミングについてQuine・難解プログラミングについて
Quine・難解プログラミングについて
mametter15.3K vistas
20分くらいでわかった気分になれるC++20コルーチン por yohhoy
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
yohhoy13K vistas
コルーチンでC++でも楽々ゲーム作成! por amusementcreators
コルーチンでC++でも楽々ゲーム作成!コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!
amusementcreators7.5K vistas
TensorFlow XLAは、 中で何をやっているのか? por Mr. Vengineer
TensorFlow XLAは、 中で何をやっているのか?TensorFlow XLAは、 中で何をやっているのか?
TensorFlow XLAは、 中で何をやっているのか?
Mr. Vengineer7.1K vistas
ROS2のリアルタイム化に挑む WG初参加 por Atsushi Hasegawa
ROS2のリアルタイム化に挑む WG初参加ROS2のリアルタイム化に挑む WG初参加
ROS2のリアルタイム化に挑む WG初参加
Atsushi Hasegawa1.5K vistas

Similar a クロージャデザインパターン

ぱっと見でわかるC++11 por
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11えぴ 福田
1.6K vistas16 diapositivas
C++0x in programming competition por
C++0x in programming competitionC++0x in programming competition
C++0x in programming competitionyak1ex
1.8K vistas24 diapositivas
Ekmett勉強会発表資料 por
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料時響 逢坂
948 vistas68 diapositivas
Pfi Seminar 2010 1 7 por
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Preferred Networks
3.4K vistas60 diapositivas
Ekmett勉強会発表資料 por
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料時響 逢坂
16.7K vistas77 diapositivas
Replace Output Iterator and Extend Range JP por
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPAkira Takahashi
1.6K vistas26 diapositivas

Similar a クロージャデザインパターン(20)

ぱっと見でわかるC++11 por えぴ 福田
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11
えぴ 福田1.6K vistas
C++0x in programming competition por yak1ex
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
yak1ex1.8K vistas
Ekmett勉強会発表資料 por 時響 逢坂
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
時響 逢坂948 vistas
Ekmett勉強会発表資料 por 時響 逢坂
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
時響 逢坂16.7K vistas
Replace Output Iterator and Extend Range JP por Akira Takahashi
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JP
Akira Takahashi1.6K vistas
わんくま同盟大阪勉強会#61 por TATSUYA HAYAMIZU
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61
TATSUYA HAYAMIZU957 vistas
競技プログラミングのためのC++入門 por natrium11321
競技プログラミングのためのC++入門競技プログラミングのためのC++入門
競技プログラミングのためのC++入門
natrium11321 32.9K vistas
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„ por 和弘 井之上
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„
和弘 井之上306 vistas
C++0x in programming competition por yak1ex
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
yak1ex1K vistas
GoF デザインパターン 2009 por miwarin
GoF デザインパターン 2009GoF デザインパターン 2009
GoF デザインパターン 2009
miwarin419 vistas
メタメタプログラミングRuby por emasaka
メタメタプログラミングRubyメタメタプログラミングRuby
メタメタプログラミングRuby
emasaka992 vistas
.NET Core 2.x 時代の C# por 信之 岩永
.NET Core 2.x 時代の C#.NET Core 2.x 時代の C#
.NET Core 2.x 時代の C#
信之 岩永6.2K vistas
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~ por Fujio Kojima
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
Fujio Kojima786 vistas

Último

The Things Stack説明資料 by The Things Industries por
The Things Stack説明資料 by The Things IndustriesThe Things Stack説明資料 by The Things Industries
The Things Stack説明資料 by The Things IndustriesCRI Japan, Inc.
73 vistas29 diapositivas
SSH応用編_20231129.pdf por
SSH応用編_20231129.pdfSSH応用編_20231129.pdf
SSH応用編_20231129.pdficebreaker4
366 vistas13 diapositivas
Windows 11 information that can be used at the development site por
Windows 11 information that can be used at the development siteWindows 11 information that can be used at the development site
Windows 11 information that can be used at the development siteAtomu Hidaka
89 vistas41 diapositivas
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料) por
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)NTT DATA Technology & Innovation
23 vistas38 diapositivas
定例会スライド_キャチs 公開用.pdf por
定例会スライド_キャチs 公開用.pdf定例会スライド_キャチs 公開用.pdf
定例会スライド_キャチs 公開用.pdfKeio Robotics Association
127 vistas64 diapositivas

Último(10)

The Things Stack説明資料 by The Things Industries por CRI Japan, Inc.
The Things Stack説明資料 by The Things IndustriesThe Things Stack説明資料 by The Things Industries
The Things Stack説明資料 by The Things Industries
CRI Japan, Inc.73 vistas
SSH応用編_20231129.pdf por icebreaker4
SSH応用編_20231129.pdfSSH応用編_20231129.pdf
SSH応用編_20231129.pdf
icebreaker4366 vistas
Windows 11 information that can be used at the development site por Atomu Hidaka
Windows 11 information that can be used at the development siteWindows 11 information that can be used at the development site
Windows 11 information that can be used at the development site
Atomu Hidaka89 vistas
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料) por NTT DATA Technology & Innovation
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20... por NTT DATA Technology & Innovation
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...
光コラボは契約してはいけない por Takuya Matsunaga
光コラボは契約してはいけない光コラボは契約してはいけない
光コラボは契約してはいけない
Takuya Matsunaga24 vistas
SNMPセキュリティ超入門 por mkoda
SNMPセキュリティ超入門SNMPセキュリティ超入門
SNMPセキュリティ超入門
mkoda420 vistas

クロージャデザインパターン