c++ ラムダ式
ラムダ式とは?
無名関数。
わざわざ関数名つけるほどの処理じゃないな~って思った時とか
一時的な処理の時とかに使うと手間を省けて良いね
あとstd::functionに代入できるのが神
ラムダ式の定義
[](){};
かっこ勢揃いでおもろい
[](){}; これを普段の関数の形に戻すと
void Function() { }
これが出来上がったイメージ
小さく小さく
引数が必要なかったら[](){};の"()"も省略できる
[]{};
ラムダ式の呼び出し
[](){}();
いつも関数を呼び出してる感じで最後に (); をつければOK
ラムダ式を使わない場合
void Function()
{
cout << "Function" << endl;
}
int main()
{
Function();
}
ラムダ式を使った場合
int main()
{
[]() {cout << "Function" << endl; }(); //引数がないので()は省略可
}
引数ありバージョン
ラムダ式を使わない場合
void Function(int num)
{
cout << num << endl;
}
int main()
{
Function(5);
}
ラムダ式を使った場合
int main()
{
[](int num) {cout << num << endl; }(5);
}
戻り値を返す
ラムダ式を使わない場合
int Function(int a)
{
return a*=2;
}
int main()
{
cout << Function(5) << endl;
}
ラムダ式を使った場合
int main()
{
cout << [](int a) -> int { return a*=2; }(5) << endl;
}
戻り値の型
[](int a) -> int { return a*=2; }; //戻り値の型を定義する
[](int a) { return a*=2; }; //戻り値の型を定義しない
明示的に戻り値の型を定義しないと勝手に決めてくれる
cout << [](float a)->int { return a *= 2; }(5.2f) << endl; // 10
cout << [](float a) { return a *= 2; }(5.2f) << endl; // 10.4
かっこの意味
[] // キャプチャ
() // 引数 引数がない場合は省略可
{} // 処理
() // 関数呼び出し
;
[] キャプチャとは?
[&] 参照でキャプチャー
例えば文字列を変えるラムダ式を書いたとする
string str = "HOGE";
[](){str = "TEST"; }();
エラーE1735 外側の関数のローカル変数は、キャプチャ リストに含まれていない限り、
ラムダ本体で参照できません
これだとエラーが出る やってるイメージは
void Function()
{
str = "TEST"; // エラーE0020 識別子 "str" が定義されていません
}
int main()
{
string str = "HOGE";
Function();
return 0;
}
strないよー
ラムダ式の中で変数を使いたいときは
その変数がラムダ式のスコープ内で定義されているか
キャプチャされている必要がある
そこで[&]を使う 参照でキャプチャー
string str = "HOGE";
[&](){str = "TEST"; }();
これでエラーが消える
また[&]単体だと全ての変数を参照でキャプチャする
string str = "HOGE";
[&str](){str = "TEST"; }();
これだとstr 変数のみを参照でキャプチャする
string str = "HOGE"
int a = 0;
[&]()
{
a = 10; // OK
str = "TEST"; // OK
}();
==========================================
string str = "HOGE"
int a = 0;
[&str]()
{
a = 10; // エラーE1735
str = "TEST"; // OK
}();
==========================================
string str = "HOGE"
int a = 0;
[&a,&str]()
{
a = 10; // OK
str = "TEST"; // OK
}();
[=] コピーでキャプチャー
こっちは読み取り専用
string str = "HOGE";
[=]() {str = "TEST"; }();
エラー
コピーしてきているだけだからラムダ式内で変数を書き換えできないよー
string str = "HOGE";
[=](){ cout << str << endl; }();
string str = "HOGE";
[str](){ cout << str << endl; }(); //当然これもいける
ラムダ式の何がいいのか?
使い捨ての関数をその場で作れる
使い捨ての関数の登場シーンとは?
5秒たったら何かを実行したい
ラムダ式を使わなかった場合
//実行する関数
void CallbackFunction()
{
std::cout << "5秒たった." << std::endl;
}
//引数1 delay 引数2 callback
void DelayCallback(chrono::seconds delay, std::function<void()> callback)
{
this_thread::sleep_for(delay);
callback();
}
int main()
{
std::chrono::seconds delay(5);
DelayCallback(delay, callbackFunction);
return 0;
}
デメリットとして
①単純な処理でも関数を事前に作らないといけない
②DelayCallbackを実行する場所と実行するCallbackFunctionが別の場所に
書かれていて処理が追いにくい
これをラムダ式を使った場合
//引数1 delay 引数2 callback
void DelayCallback(chrono::seconds delay, std::function<void()> callback)
{
this_thread::sleep_for(delay);
callback();
}
int main()
{
chrono::seconds delay(5);
DelayCallback(delay,[]()
{
std::cout << "5秒たった." << std::endl;
});
return 0;
}
わーい
その他
class HogeClass
{
public:
HogeClass(){}
~HogeClass(){}
void Test()
{
cout << "HOGEクラスTest関数" << endl;
}
};
int main()
{
HogeClass* h = new HogeClass();
chrono::seconds delay(5);
DelayCallback(delay,[&h]()
{
std::cout << "5秒たった." << std::endl;
h->Test();
});
delete(h);
}
メンバ変数のキャプチャ
class HogeClass
{
int a = 0;
public:
HogeClass();
~HogeClass();
void Change();
};
==============================
void HogeClass::Change()
{
[] {a = 10; }(); //エラー
}
void HogeClass::Change()
{
[&a] {a = 10; }(); //エラー ローカル変数のaをキャプチャする
}
void HogeClass::Change()
{
[&] {a = 10; }(); //OK
}
void HogeClass::Change()
{
[this] {a = 10; }(); //OK メンバ変数をキャプチャする
}
[this]
*thisのメンバ変数を参照して、ラムダ式のなかで使用する
privateにもアクセスできる
(メンバ変数の一部だけを指定する方法はわかんない)
ラムダ式をつかった例その②
指定のシーンに遷移するコード
class Scene
{
virtual void Init() = 0;//初期化
virtual void Update() = 0;//更新
void Draw();//描画
virtual void UnInit() = 0;//後処理
}
これを継承した
タイトルシーンクラス、ステージシーンクラスを作る
class TitleScene : public Scene
{
public:
TitleScene(); //コンストラクタ
~TitleScene(); //デストラクタ
void Init()override;
void Update() override;
void UnInit()override;
};
=================================================================
class Stage_01 : public Scene
{
public:
Stage_01(); //コンストラクタ
~Stage_01(); //デストラクタ
void Init()override;
void Update() override;
void UnInit()override;
};
ゲームマネージャクラスとかシーンマネージャークラスとかに
Scene* currentScene = nullptr;
//引数に指定したsceneに変更 呼び出しはChangeScene(new Stage_01())こんな感じで呼べばいい
void ChangeScene(Scene* nextScene);
==========================================
void GameManager::ChangeScene(Scene* nextScene)
{
if (currentScene != nullptr)
{
currentScene->UnInit(); //現在のsceneの後処理呼び出し
delete currentScene; //解放処理
}
currentScene = nextScene; //新しいsceneにする
currentScene->Init(); //新しいsceneの初期化呼び出し
}
という関数を作ったとする
毎回毎回引数に実体化を書くのが変な感じがする
次のシーンに遷移するコード NextChangeScene()関数を作る!
switch文で書こう!!
case SCENE_TYPE::TITLE:
currentScene = new TitleScene();
break;
case SCENE_TYPE::STAGE_01:
currentScene = new Stage_01();
break;
??
シーンが増えたり、シーンの順番入れ替えたいときに
絶対に面倒なことになる
もうちょっといい感じに
列挙つくって、mapとかに設定して~と色々していく
//シーンの列挙
enum class SCENE_TYPE
{
NONE = -1,
TITLE = 0,
STAGE_01,
MAX,
};
//シーンのタイプと対応するシーンのマップ
const std::map<SCENE_TYPE, Scene*> sceneDictionary =
{
{SCENE_TYPE::TITLE, new TitleScene()},
{SCENE_TYPE::STAGE_01, new Stage_01()},
};
いいかんじやん
これを使って次のシーンのポインタを設定して、
NextChangeSceneを作るんやー!
void NextChangeScene()
{
//現在のシーンのタイプを取得
int currentSceneIndex = static_cast<int>(currentSceneType);
//次のシーンのタイプを計算
int nextSceneIndex = (currentSceneIndex + 1) % static_cast<int>(SCENE_TYPE::MAX);
//新しいシーンのタイプに変更
currentSceneType = static_cast<SCENE_TYPE>(nextSceneIndex);
//マップから新しいシーンのインスタンスを取得
Scene* nextScene = sceneDictionary.GetSecond(currentSceneType);
//新しいシーンに切り替える
ChangeScene(nextScene);
}
実行! イケー
例外スロー
あかん
ChangeSceneでdelete currentScene; 解放処理と
sceneDictionaryの new TitleScene()の宣言
が相性最悪や
解放したやつ使おうとしてる、、、、
毎回実体化してほしい、、、、
実体化を行うだけのラムダ式書けばいける!
もっと詳しく言うと
「実体化して新しく生成されたSceneオブジェクトへのポインタを返すラムダ式を作る」
mapを書き換える
const std::map<SCENE_TYPE, std::function<Scene*()>> sceneDictionary =
{
{SCENE_TYPE::TITLE, []{ return new TitleScene(); }},
{SCENE_TYPE::STAGE_01, []{ return new Stage_01(); }},
};
===========================================
void GameManager::NextChangeScene()
{
//次のシーンの列挙
int nextSceneIndex = (static_cast<int>(currentSceneType) + 1) % static_cast<int>(SCENE_TYPE::MAX);
//新しいシーンの列挙に変更
currentSceneType = static_cast<SCENE_TYPE>(nextSceneIndex);
//新しいシーンに切り替える
ChangeScene(sceneDictionary.GetSecond(currentSceneType)());
}
これで毎回実体化を行ってくれるようになる!!
例外がスローもおさらば
こんな変なことしなくてもいい方法あると思うけど、まぁまぁまぁ
https://www.xlsoft.com/jp/products/intel/compilers/ccl/12/ug/cref_cls/common/cppref_lambda_lambdacapt.htm
https://qiita.com/toRisouP/items/98cc4966d392b7f21c30