지난시간은 JobQueue를 관리하고 일감으로 밀어넣고 실행하는 부분까지 해봤다.
그런데 Room 클래스의 Enter, Leave, broadcast 함수 하나하나마다 EnterJob, LeaveJob, BroadCastJob 클래스를 만들어서 관리하는건 직관적이긴 하지만 너무나도 비효율적이다. 나중에 컨텐츠를 만들다보면 방대한 코드를 만들게 될것인데 클래스를 하나 하나 만드는건 그다지 좋은 방법이 아니다.
공용클래스로 만들어 Job을 관리해보자.
표준에 있는 std::function이나 람다함수를 사용하면 구현할 수 있지만, 일단 그 전의 중간단계부터 실습해보자.
함수자 Functor를 활용한 방법이다.
template<typename Ret, typename... Args>
class Job
{
public:
Ret operator()(Args... args)
{
}
private:
};
{
Job<void, uint16, uint64> job;
job(10, 60);
}
이런 식으로 리턴값과 인자를 넣는 식.
하지만 여기까지가 아니라 Job안에서 함수를 알아서 호출하도록 만들고싶다.
가장 쉽게 생각할 수 있는건 함수포인터다.
template<typename Ret, typename... Args>
class FuncJob
{
using FuncType = Ret(*)(Args...);
public:
FuncJob(FuncType func) : _func(func)
{
}
Ret operator()(Args... args)
{
_func(args...);
}
Ret Execute(Args... args)
{
_func(args...);
}
private:
FuncType _func;
};
void HealByValue(int64 target, int32 value)
{
cout << target << "한테 힐 " << value << " 만큼 줌" << endl;
}
int main()
{
FuncJob<void, int64, int32> job(HealByValue);
job(10, 60);
}
이런식으로 생성자를 통해 사용할 함수를 함수자에 넣어서 가변적인 인자 + 함수사용을 해결한다.
아직 부족한건 Job을 만들어줄 때 인자를 같이 넣어줘서 나중에는 그냥 정해진대로 실행만 시켜줘야 하는데 그부분이 없다.
즉 함수자가 어떤 값을 인자로 넣어서 함수를 실행시켜줄지 기억하고 있어야 하는 것.
그런데 아쉽게도 가변적인 숫자의 args... 을 객체에서 들고있을 문법이 따로 없다.
그래서 tuple과 (c++11) std::apply를 (c++17) 쓰면 된다.
template<typename Ret, typename... Args>
class FuncJob
{
using FuncType = Ret(*)(Args...);
public:
FuncJob(FuncType func, Args... args) : _func(func), _tuple(args...)
{
}
Ret Execute()
{
std::apply(_func, _tuple);
}
private:
FuncType _func;
std::tuple<Args...> _tuple;
};
{
FuncJob<void, int64, int32> job(HealByValue, 10, 60);
job();
}
이렇게 하면 함수자 객체가 어떤 값을 인자로 넣어야하는지 기억하기 때문에 생성자에서 값들을 올바르게 넣어주기만 하면 나중엔 그냥 호출하기만 하면 된다. (강의자는 헷갈린다고 Execute로만 호출한다 함)
만약 우리의 개발환경이 c++17이상이 아니라면 컴파일이 안되니까 프로젝트 속성 - c++ - 언어 에서 표준을 17 이상으로 설정해야한다.

그러면 c++17 이전에는 어떻게 이런 문제를 해결했을까?
c++11에서의 해결법을 알아보자. 알면 탬플릿 이해도가 많이 높아지기에 배우면 좋다.
// C++11 apply
template<int... Remains>
struct seq
{ };
template<int N, int... Remains>
struct gen_seq : gen_seq<N-1, N-1, Remains...>
{ };
template<int... Remains>
struct gen_seq<0, Remains...> : seq<Remains...>
{ };
template<typename Ret, typename... Args>
void xapply(Ret(*func)(Args...), std::tuple<Args...>& tup)
{
xapply_helper(func, gen_seq<sizeof...(Args)>(), tup);
}
template<typename F, typename... Args, int... ls>
void xapply_helper(F func, seq<ls...>, std::tuple<Args...>& tup)
{
(func)(std::get<ls>(tup)...);
}
std::aplly 대신 xapply를 사용해도 똑같은 결과가 나온다.
template<typename Ret, typename... Args>
class FuncJob
{
using FuncType = Ret(*)(Args...);
public:
FuncJob(FuncType func, Args... args) : _func(func), _tuple(args...)
{
}
Ret Execute()
{
xapply(_func, _tuple);
}
private:
FuncType _func;
std::tuple<Args...> _tuple;
};
예시로 간단하게 설명하면 (지금도 잘 이해 안됨)
- xapply에 void HealByValue(int64 target, int32 value) 함수와 튜플을 넣는다. 튜플에는 인자들이 저장되어있음.
- xapply_helper(HealByValue, seq<0, 1>(), tup); 가 불러와짐
gen_seq<sizeof...(Args) = 2> 가 seq<0, 1> 로 바뀌는건 이해가 잘 안된다. - xapply_helper에서 F = HealByValue, Args = int64, int32 (tup에 의해), ls = 0, 1 (sez<0, 1>에 의해) 인 함수로 실행됨.
- std::get<ls>(tup)... 은 "ls 안의 원소마다 std::get<I>(tup) 를 하나씩 써라" 라는 뜻 이라서
func(std::get<0>(tup), std::get<1>(tup)); 이렇게 풀어진다.
이해가 안되는 부분
- std::get<ls>(tup)... 이 왜 저런식으로 풀어지는가?
-> pack extension (...) 문법의 제약때문에 저렇게 풀어지는것이라 한다(그냥 그런 문법이라는 듯?) 만약 함수 안이 아니라 그냥 밖에다 std::get(tup)...; 이렇게 쓰면 컴파일 에러.
- gen_seq<sizeof...(Args)> 가 seq<0, 1> 바뀌는 이유
- gen_seq<2> 대입. 이 때 template<int N, int... Remains> 로 불러와짐. (Remains이 빈 팩인 상태)
- 해당 탬플릿에서 gen_seq<2>은 gen_seq<1, 1, Remains....>을 상속받음. (Remains...은 빈팩)
- gen_seq<1, 1>은 template<int N, int... Remains>로 불러와짐.
- gee_seq<1, 1>은 gen_seq<0, 0, 1>을 상속받음.
- gen_seq<0, 0, 1>은 gen_seq<0, Remains...>로 불러와짐
- gen_seq<0, 0, 1>은 seq<0, 1>을 상속받음.
- 즉 gen_seq<2>의 최초(기반) 부모는 seq<0, 1> 인 것이고, xapply_helper의 두번째 인자는 seq<ls...> 를 받으므로 타입 변환을 통해 seq<0, 1>로 들어감.
일반 함수는 위와같이 사용할 수 있는데
class 안의 멤버함수는 this 포인터까지 넘겨줘야 사용이 가능하기때문에 사용할 수 없다.
이것을 사용하기 위해 새로운 클래스 MemberJob을 만들어주고, 멤버함수 전용 xapply와 helper도 오버로딩 해주자.
강의 영상과 달리 MemberJob 생성자에서 원인을 잘 모르겠는 에러가 났다. 같은 증상있는 사람도 있고, 강의자도 질문에 대답한거 보니 잘 모르는듯 했다.
그래서 gpt에게 물어보니 클래스 템플릿 인자 추론이 실패해서 그렇고, 템플릿 인자를 명시하면 해결된다 했다. MemberJob<Knight, void, uint32> job2(&k1, &Knight::HealMe, 10);
생성자 부분을 이렇게 변경하니 해결되긴 했다.
template<typename T, typename Ret, typename... Args>
void xapply(T* obj, Ret(T::*func)(Args...), std::tuple<Args...>& tup)
{ //T::* 로 쓰는건 T클래스 안의 멤버함수라는 의미
xapply_helper(obj, func, gen_seq<sizeof...(Args)>(), tup);
}
template<typename T, typename F, typename... Args, int... ls>
void xapply_helper(T* obj, F func, seq<ls...>, std::tuple<Args...>& tup)
{
(obj->*func)(std::get<ls>(tup)...);
}
template<typename T, typename Ret, typename... Args>
class MemberJob
{
using FuncType = Ret(T::*)(Args...); //T::* 로 쓰는건 T클래스 안의 멤버함수라는 의미
public:
MemberJob(T* obj, FuncType func, Args... args) : _obj(obj), _func(func), _tuple(args...)
{
}
Ret Execute()
{
xapply(_obj, _func, _tuple);
}
private:
T* _obj;
FuncType _func;
std::tuple<Args...> _tuple;
};
class Knight
{
public:
void HealMe(uint32 value) {
cout << "Heal " << value << endl;
}
};
main
{
Knight k1;
MemberJob job2(&k1, &Knight::HealMe, 10); //에러 발생 코드
//MemberJob<Knight, void, uint32> job2(&k1, &Knight::HealMe, 10); // 에러 해결 코드
job2.Execute();
}
이제 이전에 job 클래스를 이것저것 만들었던것을 탬플릿을 활용한것으로 통일시켜보자.
job 등록용 클래스를 만들어야하는 일이 없어졌다!
class Room
{
public:
// 싱글쓰레드 환경처럼 코딩
void Enter(PlayerRef player);
void Leave(PlayerRef player);
void Broadcast(SendBufferRef sendBuffer);
public:
// 멀티쓰레드 환경에서는 일감으로 접근
void PushJob(JobRef job) { _jobs.Push(job); }
void FlushJob();
template<typename T, typename Ret, typename... Args>
void PushJob(Ret(T::* memFunc)(Args...), Args... args)
{
auto job = MakeShared<MemberJob<T, Ret, Args...>>(static_cast<T*>(this), memFunc, args...);
_jobs.Push(job);
}
private:
map<uint64, PlayerRef> _players;
JobQueue _jobs;
};
extern Room GRoom;
// handle_~~~ 함수들에서 일감 예약하는 부분
{
//GRoom.PushJob(MakeShared<EnterJob>(GRoom, player)); // 일감만 예약해줌
GRoom.PushJob(&Room::Enter, player);
}
{
//GRoom.PushJob(MakeShared<BroadcastJob>(GRoom, sendBuffer));
GRoom.PushJob(&Room::Broadcast, sendBuffer);
}'강의 수강 > 게임서버(1)' 카테고리의 다른 글
| 강의를 듣고 다시 확인할 것들 모음 (0) | 2025.08.23 |
|---|---|
| 72. JobQueue #3 (0) | 2025.08.22 |
| 70. JobQueue #1 (0) | 2025.08.19 |
| 69. 채팅 실습 (0) | 2025.08.18 |
| 68. 패킷 자동화 #2 (0) | 2025.08.18 |