팁 – 검색 표로 함수 호출하기
사실 별 내용은 없는데 주변을 보니 여전히 if
와 switch
로 해결하는 분이 많아 즐겨 쓰는 방법을 적어 본다. 초급 수준 팁이라고 하면 될까. 이 글을 단순화한 것이라고 할 수 있겠다. 이해하기 쉬운 예를 먼저 보자.
TCP/IP로 수신한 정보를 파싱하는 과정을 생각해 보자.
- 수신한 정보를 메시지 단위로 자른다.
- 메시지 종류를 확인한다.
- 메시지 종류에 맞게 준비해 둔 구조체에 담는다.
2번 과정에서 흔히 등장하는 게 if
나 switch
이다. 어떤 메시지인지 알아야 그에 맞게 처리할 테니까. 코드로 보면 대략 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
if (...) function1(); else if (...) function2(); else function3(); // ... switch (...) { case ...: function1(); break; case ...: function2(); break; case ...: function3(); break; } |
처리할 게 몇 가지 없으면 이것도 무난하다. 하지만 점점 늘어나거나 많을 때는 조건을 계속 추가해야 하니 성가시기도 하고 보기도 좋지 않다. 이럴 때 쓰면 쓰면 된다.
코드는 초기화, 호출, 처리자 세 부분으로 구분할 수 있다. 먼저 초기화는 다음과 같다.
1 2 3 4 5 6 |
void Processor::initialize() { handlers_.emplace(CallId::hello, std::bind(&Processor::hello, this)); handlers_.emplace(CallId::hi, std::bind(&Processor::hi, this)); handlers_.emplace(CallId::go, std::bind(&Processor::go, this)); } |
딱 보면 감이 오지 않는가. C에서 쓰던 함수 포인터의 재림이다. ID와 호출할 함수를 묶어 맵에 추가하면 초기화는 끝이다.
1 2 3 4 5 6 7 8 |
void Processor::process(CallId id) { auto pos{handlers_.find(id)}; if (handlers_.end() == pos) return; pos->second(); } |
맵에 추가한 덕분에 호출 부분이 간단해졌다. 처리할 게 늘어나더라도 이 부분은 손댈 필요 없이, 처리 함수를 만든 후 초기화에만 추가하면 된다.
이 걸로 끝이긴 한데…
사실 이 역시 처리할 함수가 많아지면 클래스 규모가 커져 보기 안 좋을 수 있다. 그럴 때는 처리 함수만 모은 클래스와 호출 클래스를 나누고, 처리 함수별로 소스 코드 파일을 따로 만드는 등 유지 보수하기 좋게 다듬는 것도 나쁘지 않았다.