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


この記事が気に入ったらサポートをしてみませんか?