1 minute read

Tag Dispatching

정의

  • C++에서 함수 오버로딩과 템플릿을 활용해 타입이나 속성에 따라 다른 구현을 호출하는 메타프로그래밍 패턴
  • 함수 인자로 태그(빈 구조체나 클래스)를 전달하여 컴파일러가 오버로드 된 함수들 중에서 적합한 구현을 선택하도록 함

예시

#include <iostream>
#include <vector>
#include <list>
#include <iterator>

template <typename Iter>
void FuncImpl(Iter& it, int n, std::random_access_iterator_tag)
{
	std::cout << "O(1) Access\n";
	it += n;
}

template <typename Iter>
void FuncImpl(Iter& it, int n, std::bidirectional_iterator_tag)
{
	std::cout << "O(N) Access\n";
	for (int i = 0; i < n; ++i)
		++it;
}

template <typename Iter>
void Func(Iter& it, int n)
{
	FuncImpl(it, n, typename std::iterator_traits<Iter>::iterator_category {});
}


int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5 };
	auto vIt = v.begin();

	Func(vIt, 3);
	std::cout << "value: " << *vIt << '\n';

	std::list<int> l = { 1, 2, 3, 4, 5 };
	auto lIt = l.begin();

	Func(lIt, 3);
	std::cout << "value: " << *lIt << '\n';
}
  • FuncImpl에서 std::random_access_iterator_tag나 std::bidirectional_iterator_tag와 같은 빈 구조체를 매개변수로 받음
  • 컴파일 시점에 iterator_traits<Iter>::iterator_category{}를 통해 타입에 대한 함수가 연결됨
  • 결과적으로 if 문 없이 태그를 통해 내부적으로 적절한 함수에 연결(Dispatch)하게 됨

C++ 17

template <typename Iter>
void Func(Iter& it, int n)
{
	if constexpr (std::is_same_v<typename std::iterator_traits<Iter>::iterator_category, std::random_access_iterator_tag>)
	{
		it += n;
	}
	else
	{
		for(int i = 0; i < n; ++i)
			++it;
	}
}
  • C++ 17 이상부터는 태그 디스패칭을 직접 구현하지 않고 if constexpr을 통해 컴파일 시점에서 적절한 함수로 매핑되도록 할 수 있음
  • 태그 디스패칭처럼 함수 오버로딩으로 인한 코드 가독성을 해치는 일이 없으며 디버깅에 유리한 장점이 있다

Passkey Idiom (private tag)

사용방법

  • 스마트 포인터를 사용함과 동시에 정해진 생성 규칙을 따르도록 강제하는 방법 중 하나
class A
{
private:
	struct SecretKey {};
public:
	explicit A(SecretKey)
	{
		std::cout << "Make A by Secret Key\n";
	}

	static auto Create()
	{
		auto newA = std::make_shared<A>(SecretKey{});
		return newA;
	}

private:

};

int main()
{
	auto validA = A::Create(); // 정상 작동
	auto invalidA = new A(); // 컴파일 에러
}
  • 생성자는 Tag용 빈 구조체 SecretKey을 매개변수로 받아 외부에서 생성자를 직접 호출 할 수 없도록 함
  • 사용자는 A클래스의 Create함수만을 통해 A객체를 생성할 수 있음

주의점

  • 스마트 포인터의 make_shared, make_unique는 A 클래스에 대해 외부 함수이므로 new 호출을 통해 포인터를 생성하도록 설계되어 있음
  • 이때, A클래스의 생성자를 private영역으로 옮기게 되면 스마트포인터 생성 과정에서 접근제한 오류가 발생함

결론

  • 따라서 explicit 키워드와 빈 private tag를 통해 생성자를 외부로 노출시키되 직접 호출하지 못하도록 함

Comments