SlideShare ist ein Scribd-Unternehmen logo
1 von 146
Downloaden Sie, um offline zu lesen
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のラムダ式の効果的な使用例があります。学生や新入社員対象の書籍になっております。
本スライドはラムダ計算騎士団の皆様に、ご協力いただきました。

Weitere ähnliche Inhalte

Was ist angesagt?

できる!並列・並行プログラミング
できる!並列・並行プログラミングできる!並列・並行プログラミング
できる!並列・並行プログラミングPreferred Networks
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについてkumake
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューMoriharu Ohzu
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門Norishige Fukushima
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意Yoshitaka Kawashima
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ増田 亨
 
C#でもメタプログラミングがしたい!!
C#でもメタプログラミングがしたい!!C#でもメタプログラミングがしたい!!
C#でもメタプログラミングがしたい!!TATSUYA HAYAMIZU
 
C#メタプログラミング概略 in 2021
C#メタプログラミング概略 in 2021C#メタプログラミング概略 in 2021
C#メタプログラミング概略 in 2021Atsushi Nakamura
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなKentaro Matsui
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?Yoshitaka Kawashima
 
非同期処理の基礎
非同期処理の基礎非同期処理の基礎
非同期処理の基礎信之 岩永
 
関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐりKazuyuki TAKASE
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性Hibiki Yamashiro
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
C#とILとネイティブと
C#とILとネイティブとC#とILとネイティブと
C#とILとネイティブと信之 岩永
 
TRICK 2022 Results
TRICK 2022 ResultsTRICK 2022 Results
TRICK 2022 Resultsmametter
 
DDD sample code explained in Java
DDD sample code explained in JavaDDD sample code explained in Java
DDD sample code explained in Java増田 亨
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexprGenya Murakami
 

Was ist angesagt? (20)

できる!並列・並行プログラミング
できる!並列・並行プログラミングできる!並列・並行プログラミング
できる!並列・並行プログラミング
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについて
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビュー
 
組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門組み込み関数(intrinsic)によるSIMD入門
組み込み関数(intrinsic)によるSIMD入門
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
オブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツオブジェクト指向の設計と実装の学び方のコツ
オブジェクト指向の設計と実装の学び方のコツ
 
C#でもメタプログラミングがしたい!!
C#でもメタプログラミングがしたい!!C#でもメタプログラミングがしたい!!
C#でもメタプログラミングがしたい!!
 
C#メタプログラミング概略 in 2021
C#メタプログラミング概略 in 2021C#メタプログラミング概略 in 2021
C#メタプログラミング概略 in 2021
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?
 
非同期処理の基礎
非同期処理の基礎非同期処理の基礎
非同期処理の基礎
 
関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり
 
競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性競技プログラミングにおけるコードの書き方とその利便性
競技プログラミングにおけるコードの書き方とその利便性
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
プログラムを高速化する話
プログラムを高速化する話プログラムを高速化する話
プログラムを高速化する話
 
C#とILとネイティブと
C#とILとネイティブとC#とILとネイティブと
C#とILとネイティブと
 
TRICK 2022 Results
TRICK 2022 ResultsTRICK 2022 Results
TRICK 2022 Results
 
DDD sample code explained in Java
DDD sample code explained in JavaDDD sample code explained in Java
DDD sample code explained in Java
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
 

Ähnlich wie クロージャデザインパターン

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

Ähnlich wie クロージャデザインパターン (20)

ぱっと見でわかるC++11
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
 
Ekmett勉強会発表資料
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
 
Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7
 
Ekmett勉強会発表資料
Ekmett勉強会発表資料Ekmett勉強会発表資料
Ekmett勉強会発表資料
 
Replace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JPReplace Output Iterator and Extend Range JP
Replace Output Iterator and Extend Range JP
 
Live Coding で学ぶ C# 7
Live Coding で学ぶ C# 7Live Coding で学ぶ C# 7
Live Coding で学ぶ C# 7
 
C++0x総復習
C++0x総復習C++0x総復習
C++0x総復習
 
わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61
 
競技プログラミングのためのC++入門
競技プログラミングのためのC++入門競技プログラミングのためのC++入門
競技プログラミングのためのC++入門
 
More C++11
More C++11More C++11
More C++11
 
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„
【C++BUILDER STARTER チュートリアルシリーズ】シーズン2 C++Builderの部 第4回 ‟関数„
 
C++0x in programming competition
C++0x in programming competitionC++0x in programming competition
C++0x in programming competition
 
GoF デザインパターン 2009
GoF デザインパターン 2009GoF デザインパターン 2009
GoF デザインパターン 2009
 
C++ tips4 cv修飾編
C++ tips4 cv修飾編C++ tips4 cv修飾編
C++ tips4 cv修飾編
 
Move semantics
Move semanticsMove semantics
Move semantics
 
メタメタプログラミングRuby
メタメタプログラミングRubyメタメタプログラミングRuby
メタメタプログラミングRuby
 
.NET Core 2.x 時代の C#
.NET Core 2.x 時代の C#.NET Core 2.x 時代の C#
.NET Core 2.x 時代の C#
 
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
C#の新機能勉強会 ~ C#7、8の新機能を活用して速く安全なプログラムを書こう~
 
Cocoa勉強会201208
Cocoa勉強会201208Cocoa勉強会201208
Cocoa勉強会201208
 

Kürzlich hochgeladen

【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)Hiroki Ichikura
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Yuma Ohgami
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdftaisei2219
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものですiPride Co., Ltd.
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A surveyToru Tamaki
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...Toru Tamaki
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNetToru Tamaki
 

Kürzlich hochgeladen (10)

【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
【早稲田AI研究会 講義資料】3DスキャンとTextTo3Dのツールを知ろう!(Vol.1)
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdf
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものです
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey
 
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet
 

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