やあ
DirectXでゲームを作ってるときに、UnrealEngineならデリゲートがあるのになーって思う場面があったので、作ってみた。
間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。
デリゲートとは
AはBを知っていて、BはAを知らない時にBで発生したイベントを、Aに伝えられるようにするもの。
あると何がうれしいのか?
・AとBを分離できる
デリゲートなしでAにBで発生したイベントを伝えようとすると、B.cppにA.hをインクルードしてAの処理を呼び出すことになる。
デリゲートを使うと、A.cppでB.hをインクルードするだけで済む。
・Bから不特定多数にイベントを伝えることができる。
要件?
・グローバル関数の保存
・ラムダ式の保存
- lvalueとrvalueそれぞれの保存に対応する
・メンバ関数の保存
- オブジェクトの保存
実装環境
・Windows 10
・Visual Studio 2022
・ISO C++ 20 標準
実装する
関数ポインタ : 手動
main関数の中で関数ポインタを宣言し、関数のアドレスを保存して呼び出してみる
▼見たい人だけ
グローバル関数
main.cpp
#include <iostream>
using namespace std;
void GlobalFunc(std::string tag)
{
cout << "Hello " << tag << " from global function" << endl;
}
int main()
{
void (*ptr)(std::string);
typedef void(*tdFuncPtr)(std::string);
using uFuncPtr = void(*)(std::string);
ptr = GlobalFunc;
tdFuncPtr tdfn = GlobalFunc;
uFuncPtr fn = GlobalFunc;
ptr("local");
tdfn("typedef");
fn("using");
std::invoke(ptr, "invoke");
return 0;
}
実行結果
Hello local from global function
Hello typedef from global function
Hello using from global function
Hello invoke from global function
こんな感じで関数のアドレスをポインタに保存できる!
main.cpp
int main()
{
void (*ptr)(std::string);
typedef void(*tdFuncPtr)(std::string);
using uFuncPtr = void(*)(std::string);
auto lambda = [](std::string tag)
{
cout << "Hello " << tag << " from lambda function!" << endl;
};
ptr = lambda;
tdFuncPtr tdptr = lambda;
uFuncPtr uptr = lambda;
ptr("local");
tdptr("typedef");
uptr("using");
std::invoke(ptr, "invoke");
return 0;
}
実行結果
Hello local from lambda function!
Hello typedef from lambda function!
Hello using from lambda function!
Hello invoke from lambda function!
main.cpp
class TestClass
{
public:
void ClassMemberFunction(std::string tag)
{
cout << "Hello " << tag << " from class member function" << endl;
}
};
int main()
{
void(TestClass:: * memberPtr)(std::string);
typedef void(TestClass::* tdMemberPtr)(std::string);
using uMemberPtr = void(TestClass::*)(std::string);
memberPtr = &TestClass::ClassMemberFunction;
tdMemberPtr tdmPtr = &TestClass::ClassMemberFunction;
uMemberPtr umPtr = &TestClass::ClassMemberFunction;
TestClass* pTestClass = new TestClass();
(*pTestClass.*memberPtr)("local");
(*pTestClass.*tdmPtr)("typedef");
(*pTestClass.*umPtr)("using");
std::invoke(umPtr, *pTestClass, "invoke");
TestClass testClass = TestClass();
(testClass.*memberPtr)("local2");
(testClass.*tdmPtr)("typedef2");
(testClass.*umPtr)("using2");
std::invoke(umPtr, testClass, "invoke2");
return 0;
}
実行結果
Hello local from class member function
Hello typedef from class member function
Hello using from class member function
Hello invoke from class member function
Hello local2 from class member function
Hello typedef2 from class member function
Hello using2 from class member function
Hello invoke2 from class member function
めんどくさい!!
関数ポインタ : 自動?
templateを使って、戻り値も引数もわからない状態で関数ポインタを宣言する!
Delegate.h
template<typename T>
class Delegate;
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
private:
using Function = Ret(*)(Args...);
Function mFunction;
};
これで戻り値も引数もわからない関数ポインタmFunctionができた!
このDelegateクラスで関数のアドレスを保存したり、呼び出したりできるようにする。
関数のアドレスを保存したり呼び出したりする
グローバル関数
Delegate.h
class DelegateNotBound : public std::exception
{
public:
const char* what() const noexcept override
{
return "Delegate is not bound";
}
};
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
template<auto Function>
void Bind()
{
mFunction = [](Args... args)
{
return Function(std::forward<Args>(args)...);
};
}
explicit operator bool() const { return mFunction; }
template<typename ...Args>
Ret Broadcast(Args&&... args)
{
if (!*this)
{
throw DelegateNotBound();
}
return mFunction(std::forward<Args>(args)...);
}
bool IsBound() const
{
return mFunction;
}
private:
using Function = Ret(*)(Args...);
Function mFunction;
};
実際にDelegateクラスを使ってみる!
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
void GlobalFunc(std::string tag)
{
cout << "Hello " << tag << " from global function" << endl;
}
int main()
{
Delegate<void(std::string)> ptr;
ptr.Bind<GlobalFunc>();
ptr.Broadcast("pto8193");
return 0;
}
実行結果
Hello pto8193 from global function
ラムダ式を保存する。
しかし、単純にグローバル関数のように保存してしまうと、
Delegateがラムダ式を呼び出す前にラムダ式の寿命が来る可能性がある。
そのためラムダ式自体を保存する必要がある。
様々なタイプのポインタを保存してみる
▼見たい人だけ
まずはオブジェクト
main.cpp
#include <iostream>
using namespace std;
class TestClass
{
public:
void Show()
{
cout << "Hello my ID is " << id << endl;
}
void SetID(int inID)
{
id = inID;
}
private:
int id;
};
int main()
{
std::byte mData[sizeof(void*)];
TestClass* testClass = new TestClass();
testClass->SetID(10);
new (&mData) TestClass(*testClass);
TestClass* mDataTestClass = reinterpret_cast<TestClass*>(mData);
mDataTestClass->Show();
return 0;
}
実行結果
Hello my ID is 10
templateで書いてみる
main.cpp
using Storage = std::byte(*)[sizeof(void*)];
template<typename Type>
Type* ConvertByteToType(Storage mData, Type& instance)
{
new (mData) Type(instance);
return reinterpret_cast<Type*>(mData);
}
int main()
{
std::byte mData[sizeof(void*)];
TestClass* testClass = new TestClass();
testClass->SetID(10);
TestClass* mDataTestClass = ConvertByteToType<TestClass>(&mData, *testClass);
mDataTestClass->Show();
return 0;
}
これで、
オブジェクトのポインタをmDataに保存。
mDataからオブジェクトを復元。
オブジェクトの実行ができるようになった。
次に、ラムダ式をmDataに保存してみる。
int main()
{
std::byte mData[sizeof(void*)];
auto aLambda = [](std::string tag)
{
cout << "Hello " << tag << " from lambda" << endl;
};
auto lambda = ConvertByteToLambda(&mData, aLambda);
std::invoke(*lambda, "pto8913");
(*lambda)("pto8913");
return 0;
}
様々なタイプのポインタを保存の仕方が分かった!(本当に?)
ラムダ式をDelegate.h
内に保存できるようにするため、様々なタイプのポインタを保存してみるで作ったmDataをDelegate.h
に統合する。
Delegate.h
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
template<auto Function>
void Bind()
{
new (&mData) std::nullptr_t(nullptr);
mFunction = [](Storage, Args... args)
{
return Function(std::forward<Args>(args)...);
};
}
template<typename ...Args>
Ret Broadcast(Args&& ...args)
{
return mFunction(&mData, std::forward<Args>(args)...);
}
private:
using Storage = std::byte (*)[sizeof(void*)];
using Function = Ret(*)(Storage, Args...);
alignas(void*) std::byte mData[sizeof(void*)];
Function mFunction;
};
ラムダ式を保存するときに考えなきゃいけないのが、lvalueとrvalue
例 :
auto lambda = [](){};
hogeDelegate.Bind(lambda);
hogeDelegate.Bind([](){});
この二つに対応するBind関数を作る
Delegate.h
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
template<typename Type>
void Bind(Type& funcPtr)
{
cout << "lvalue function" << endl;
new (&mData) Type* (&funcPtr);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
template<typename Type>
void Bind(Type&& funcPtr)
{
cout << "rvalue function" << endl;
new (&mData) Type(std::move(funcPtr));
mFunction = [](Storage data, Args... args)
{
Type* instance = reinterpret_cast<Type*>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
};
実際に使ってみる!
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
int main()
{
Delegate<void(std::string)> lvalueLambda;
auto aLambda = [](std::string tag)
{
cout << "lambda : " << tag << endl;
};
lvalueLambda.Bind(aLambda);
lvalueLambda.Broadcast("pto8913 lvalue ");
Delegate<void(std::string)> rvalueLambda;
rvalueLambda.Bind(
[](std::string tag)
{
cout << "lambda : " << tag << endl;
}
);
rvalueLambda.Broadcast("pto8913 rvalue ");
return 0;
}
実行結果
lvalue function
lambda : pto8913 lvalue
rvalue function
lambda : pto8913 rvalue
できた!
メンバ関数を保存するBind関数を作ってみる
Delegate.h
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
template<auto Function, typename Type>
void Bind(Type& instance)
{
cout << "member function" << endl;
new (&mData) Type* (&instance);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(Function, *instance, std::forward<Args>(args)...);
};
};
};
実際に使ってみる。
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
class TestClass
{
public:
void Show()
{
cout << "Hello my ID is " << id << endl;
}
void SetID(int inID)
{
id = inID;
}
private:
int id;
};
int main()
{
TestClass* testClass = new TestClass();
testClass->SetID(99);
Delegate<void()> memberFunction;
memberFunction.Bind<&TestClass::Show>(*testClass);
memberFunction.Broadcast();
return 0;
}
実行結果
member function
Hello my ID is 99
できた!・・・と言いたいところだけど、このままだとなんでもBindできちゃう。
例 :
Delegate<void(std::string)> invalidMemberFunction;
invalidMemberFunction.Bind<&GlobalFunc>(*testClass);
invalidMemberFunction.Broadcast("pto8913");
こういったことを回避するために事前にチェックできるようにする。
Delegate.h
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>>>
void Bind(Type& instance)
{
};
};
templateにstd::is_member_function_pointer
を追加するだけです。
typename = typename std::enable_if_t<std::is_member_function_ponter_v<decltype(Function)>>
これでメンバ関数以外の関数に対してエラーが出るようになりました。
しかし、メンバ関数ならば戻り値や引数、クラスに関係なく保存できるようになっているので、これも排除します。
Delegate.h
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance)
std::is_invocable_r_v
を追加することで、保存しようとしている関数の戻り値や引数、クラスの型を解析してエラーを発してくれます。
std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
グローバル関数のtemplateについても処理を追加する。
Delegate.h
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind()
ラムダ式については調べようがないので、そのままです。
エラー書き出してみた。
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
class TestClass
{
public:
void StrFunction(std::string tag)
{
cout << "Hello " << tag << " from class member function" << endl;
}
void VoidFunction()
{
cout << "Void Function" << endl;
};
};
void GlobalFunc() {
cout << "Global Function" << endl;
};
int main()
{
Delegate<void()> globalFunction;
globalFunction.Bind<GlobalFunc>();
globalFunction.Broadcast();
TestClass* testClass = new TestClass();
Delegate<void()> memberFunction;
memberFunction.Bind<&TestClass::VoidFunction>(*testClass);
auto aLambda = []()
{
cout << "lvalue lambda" << endl;
};
Delegate<void()> lvalueLambdaFunction;
lvalueLambdaFunction.Bind(aLambda);
lvalueLambdaFunction.Broadcast();
Delegate<void()> rvalueLambdaFunction;
rvalueLambdaFunction.Bind(
[]()
{
cout << "rvalue lambda" << endl;
}
);
rvalueLambdaFunction.Broadcast();
return 0;
}
ここまでの実装
Delegate.h
#pragma once
class DelegateNotBound : public std::exception
{
public:
const char* what() const noexcept override
{
return "Delegate is not bound";
}
};
template<typename T>
class Delegate;
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind()
{
new (&mData) std::nullptr_t(nullptr);
mFunction = [](Storage, Args... args)
{
return Function(std::forward<Args>(args)...);
};
}
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance)
{
new (&mData) Type* (&instance);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(Function, *instance, std::forward<Args>(args)...);
};
};
template<typename Type>
void Bind(Type& funcPtr)
{
new (&mData) Type* (&funcPtr);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
template<typename Type>
void Bind(Type&& funcPtr)
{
new (&mData) Type(std::move(funcPtr));
mFunction = [](Storage data, Args... args)
{
Type* instance = reinterpret_cast<Type*>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
explicit operator bool() const { return mFunction; }
template<typename ...Args>
Ret Broadcast(Args&& ...args)
{
if (!*this)
{
throw DelegateNotBound();
}
return mFunction(&mData, std::forward<Args>(args)...);
}
private:
using Storage = std::byte (*)[sizeof(void*)];
using Function = Ret(*)(Storage, Args...);
alignas(void*) std::byte mData[sizeof(void*)];
Function mFunction;
};
複数の関数を保存できるようにする
Delegate.h
#include <vector>
template<typename T>
class MulticastDelegate;
template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind()
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>();
}
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>(instance);
};
template<typename Type>
void Bind(Type& funcPtr)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
}
template<typename Type>
void Bind(Type&& funcPtr)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
}
explicit operator bool() const { return !mDelegates.empty(); }
template<typename ...Args>
void Broadcast(Args&& ...args)
{
for (auto& _delegate : mDelegates)
{
_delegate.Broadcast(std::forward<Args>(args)...);
}
}
bool IsBound() const
{
return mDelegates.size() > 0;
}
private:
using DelegateType = Delegate<Ret(Args...)>;
std::vector<DelegateType> mDelegates;
};
実際に使ってみる
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
class InventoryComponent
{
public:
void AddItem(int inItem)
{
item += inItem;
OnItemAdded.Broadcast(item);
}
MulticastDelegate<void(int)> OnItemAdded;
private:
int item;
};
void ItemAdded(int inItem)
{
cout << "Item Added : " << inItem << endl;
}
class InventoryUI
{
public:
void UpdateInventoryUI(int inItem)
{
item = inItem;
cout << "InventoryUI Updated : " << item << endl;
}
private:
int item;
};
int main()
{
InventoryComponent* inventoryComp = new InventoryComponent();
inventoryComp->OnItemAdded.Bind<ItemAdded>();
InventoryUI* inventoryUI = new InventoryUI();
inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI);
inventoryComp->AddItem(10);
return 0;
}
実行結果
Item Added : 10
InventoryUI Updated : 10
デリゲートの宣言をマクロにする
Delegate.h
#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;
#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;
使ってみる
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
DECLARE_MULTICAST_DELEGATE_OneParam(FOnItemCountChanged, int)
class InventoryComponent
{
public:
void AddItem(int inItem)
{
item += inItem;
OnItemAdded.Broadcast(item);
}
void RemoveItem(int count)
{
item -= count;
OnItemRemoved.Broadcast(item);
}
FOnItemCountChanged OnItemAdded;
FOnItemCountChanged OnItemRemoved;
private:
int item;
};
・マクロでFOnItemCountChanged
を宣言することで、FOnItemCountChanged
を複数の変数で使えるようになる。
・後から引数などを変更したくなったら、マクロの部分を変えるだけなのでとても楽。
削除(忘れてた)
うまいこと消す方法を思いつかなかったのでMulticastDelegateにタグを持たせる。
Delegate.h
template<typename T>
class Delegate;
template<typename T>
class MulticastDelegate;
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
void ClearBind()
{
new (&mData) std::nullptr_t(nullptr);
mFunction = nullptr;
}
};
template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind(std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>();
mFunctionTags.push_back(tag);
}
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>(instance);
mFunctionTags.push_back(tag);
};
template<typename Type>
void Bind(Type& funcPtr, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
mFunctionTags.push_back(tag);
}
template<typename Type>
void Bind(Type&& funcPtr, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
mFunctionTags.push_back(tag);
}
void Unbind(std::string tag)
{
cout << "TRY Unbind function : " << tag << endl;
int i = 0;
for (auto& _delegate : mDelegates)
{
if (mFunctionTags[i] == tag)
{
_delegate.ClearBind();
mFunctionTags.erase(mFunctionTags.begin() + i);
mDelegates.erase(mDelegates.begin() + i);
cout << "Unbind lambda function : " << tag << endl << endl;
break;
}
++i;
}
}
void ClearBind()
{
for (auto& _delegate : mDelegates)
{
_delegate.ClearBind();
}
mDelegates.clear();
mFunctionTags.clear();
}
private:
std::vector<std::string> mFunctionTags;
};
使ってみる
main.cpp
#include <iostream>
#include "Delegate.h"
using namespace std;
class InventoryComponent
{
public:
void AddItem(int inItem)
{
item += inItem;
OnItemAdded.Broadcast(item);
}
MulticastDelegate<void(int)> OnItemAdded;
private:
int item;
};
void ItemAdded(int inItem)
{
cout << "Item Added : " << inItem << endl;
}
class InventoryUI
{
public:
void UpdateInventoryUI(int inItem)
{
item = inItem;
cout << "InventoryUI Updated : " << item << endl;
}
private:
int item;
};
int main()
{
InventoryComponent* inventoryComp = new InventoryComponent();
inventoryComp->OnItemAdded.Bind<ItemAdded>("ItemAdded");
InventoryUI* inventoryUI = new InventoryUI();
inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI, "UpdateInventoryUI");
InventoryUI* inventoryUI2 = new InventoryUI();
inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI2, "UpdateInventoryUI2");
inventoryComp->AddItem(10);
inventoryComp->OnItemAdded.Unbind("ItemAdded");
inventoryComp->OnItemAdded.Unbind("UpdateInventoryUI");
inventoryComp->AddItem(10);
inventoryComp->OnItemAdded.Bind(
[](int i)
{
cout << "item adddeeeed to rvalue lambda" << endl << endl;
},
"OnItemAdded"
);
inventoryComp->OnItemAdded.Unbind("OnItemAdded");
inventoryComp->AddItem(10);
return 0;
}
実行結果
Item Added : 10
InventoryUI Updated : 10
InventoryUI Updated : 10
TRY Unbind function : ItemAdded
Unbind lambda function : ItemAdded
TRY Unbind function : UpdateInventoryUI
Unbind lambda function : UpdateInventoryUI
InventoryUI Updated : 20
TRY Unbind function : OnItemAdded
Unbind lambda function : OnItemAdded
InventoryUI Updated : 30
タグ付けるのめんどくさい・・・
何かいい方法あれば教えてください。
全体
Delegate.h
#pragma once
#include <vector>
#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;
#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;
class DelegateNotBound : public std::exception
{
public:
const char* what() const noexcept override
{
return "Delegate is not bound";
}
};
template<typename T>
class Delegate;
template<typename T>
class MulticastDelegate;
template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
friend class MulticastDelegate<Ret(Args...)>;
public:
Delegate()
{
ClearBind();
}
virtual ~Delegate()
{
ClearBind();
}
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind()
{
new (&mData) std::nullptr_t(nullptr);
mFunction = [](Storage, Args... args)
{
return Function(std::forward<Args>(args)...);
};
}
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance)
{
new (&mData) Type* (&instance);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(Function, *instance, std::forward<Args>(args)...);
};
};
template<typename Type>
void Bind(Type& funcPtr)
{
new (&mData) Type* (&funcPtr);
mFunction = [](Storage data, Args... args)
{
Type* instance = *reinterpret_cast<Type**>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
template<typename Type>
void Bind(Type&& funcPtr)
{
new (&mData) Type(std::move(funcPtr));
mFunction = [](Storage data, Args... args)
{
Type* instance = reinterpret_cast<Type*>(data);
return std::invoke(*instance, std::forward<Args>(args)...);
};
}
void ClearBind()
{
new (&mData) std::nullptr_t(nullptr);
mFunction = nullptr;
}
explicit operator bool() const { return mFunction; }
template<typename ...Args>
Ret Broadcast(Args&& ...args)
{
if (!*this)
{
throw DelegateNotBound();
}
return mFunction(&mData, std::forward<Args>(args)...);
}
bool IsBound() const
{
return mFunction;
}
private:
using Storage = std::byte (*)[sizeof(void*)];
using Function = Ret(*)(Storage, Args...);
alignas(void*) std::byte mData[sizeof(void*)];
Function mFunction;
};
template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
void Bind(std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>();
mFunctionTags.push_back(tag);
}
template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
void Bind(Type& instance, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Function>(instance);
mFunctionTags.push_back(tag);
};
template<typename Type>
void Bind(Type& funcPtr, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
mFunctionTags.push_back(tag);
}
template<typename Type>
void Bind(Type&& funcPtr, std::string tag)
{
DelegateType _delegate;
mDelegates.push_back(_delegate);
mDelegates.back().Bind<Type>(funcPtr);
mFunctionTags.push_back(tag);
}
void Unbind(std::string tag)
{
int i = 0;
for (auto& _delegate : mDelegates)
{
if (mFunctionTags[i] == tag)
{
_delegate.ClearBind();
mFunctionTags.erase(mFunctionTags.begin() + i);
mDelegates.erase(mDelegates.begin() + i);
break;
}
++i;
}
}
void ClearBind()
{
for (auto& _delegate : mDelegates)
{
_delegate.ClearBind();
}
mDelegates.clear();
mFunctionTags.clear();
}
explicit operator bool() const { return !mDelegates.empty(); }
template<typename ...Args>
void Broadcast(Args&& ...args)
{
for (auto& _delegate : mDelegates)
{
_delegate.Broadcast(std::forward<Args>(args)...);
}
}
bool IsBound() const
{
return mDelegates.size() > 0;
}
private:
using DelegateType = Delegate<Ret(Args...)>;
std::vector<DelegateType> mDelegates;
std::vector<std::string> mFunctionTags;
};
間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。