클래스 템플릿인 기반 클래스에 유도 클래스의 형식을 템플릿 인수로 전달하는 패턴을 말한다.
template <typename T>
class Base { ... };
class Derived: public Base<Derived> { ... };
가장 간단한 예시를 들어보자면 위와 같은 패턴으로 형성된다.
Derived Class는 Base Class를 상속하는데 템플릿 인수로 자기 자신인 Derived Class를 넘겨주고 있다.
간단한 사용 예시는 문제 발생 코드를 조금씩 해결하는 과정인 아래 코드들에서 확인할 수 있다.
class Animal {
public:
...
void goBark() { bark(); }
void bark() { cout << "Animal" << endl; }
};
class Dog: public Animal {
public:
...
void bark() { cout << "Bow" << endl; }
};
class Cat: public Animal {
public:
...
void bark() { cout << "Meow" << endl; }
};
int main() {
Dog dog;
Cat cat;
dog.goBark();
cat.goBark();
return 0;
}
위의 코드를 실행시켜보면 당연히 Animal의 bark 함수가 호출되어 "Animal"을 두 번 출력한다.
이 문제를 해결하기 위해 가상 함수를 사용하여 Derived Class의 bark 함수가 호출되도록 고쳐보면 다음과 같다.
class Animal {
public:
...
void goBark() { bark(); }
virtual void bark() { cout << "Animal" << endl; }
};
class Dog: public Animal {
public:
...
void bark() override { cout << "Bow" << endl; }
};
class Cat: public Animal {
public:
...
void bark() override { cout << "Meow" << endl; }
};
int main() {
Dog dog;
Cat cat;
dog.goBark();
cat.goBark();
return 0;
}
출력을 해보면 "Bow"와 "Meow"를 출력한다. 그러나 가상함수 테이블 참조로 인한 오버헤드가 생긴다.
오버헤드를 해결할 수 있는 방법이 CRTP를 사용한 아래 예시 코드다.
template <typename T>
class Animal {
public:
...
void goBark() { static_cast<T*>(this)->bark(); }
// void goBark() { static_cast<T&>(*this).bark(); }
void bark() { cout << "Animal" << endl; }
};
class Dog: public Animal<Dog> {
public:
...
void bark() { cout << "Bow" << endl; }
};
class Cat: public Animal<Cat> {
public:
...
void bark() { cout << "Meow" << endl; }
};
int main() {
Dog dog;
Cat cat;
dog.goBark();
cat.goBark();
return 0;
}
가상함수 사용과 동일한 결과를 도출하지만 static_cast를 사용하므로 가상함수 테이블 참조 오버헤드가 사라졌다.
또한, CRTP를 응용하면 어떤 Base Class를 상속하는 특정 객체가 얼마나 생성되었는지를 알 수 있다.
template <typename T>
class GameObject {
public:
static unsigned int objCount() noexcept { return GameObject<T>::mObjCount; }
...
protected:
GameObject() { ++GameObject<T>::mObjCount; }
GameObject(const GameObject<T>& obj) { ++GameObject<T>::mObjCount; }
~GameObject() noexcept { --GameObject<T>::mObjCount; }
...
private:
static unsigned int mObjCount;
...
};
template <typename T> unsigned int GameObject<T>::mObjCount = { };
class Triangle: public GameObject<Triangle> {
public:
Triangle() { ... }
~Triangle() noexcept { ... }
...
};
class Square: public GameObject<Square> {
public:
Square() { ... }
~Square() noexcept { ... }
...
};
int main() {
Triangle triangles[5];
Square squares[9];
cout << GameObject<Triangle>::objCount() << endl;
cout << GameObject<Square>::objCount() << endl;
return 0;
}
위의 코드를 실행해보면 출력으로 Triangle 객체는 5개, Square 객체는 9개가 생성된 것을 확인할 수 있다.
또 다른 CRTP 응용으로는 싱글톤 패턴을 구현할 때 사용될 수 있다.
template <typename T>
class Singleton {
public:
static T& getInstance() noexcept {
static T instance;
return instance;
}
...
protected:
Singleton() { ... }
~Singleton() noexcept { ... }
...
};
class Window: public Singleton<Window> {
public:
Window() { ... }
~Window() noexcept { ... }
...
};
class Cache: public Singleton<Cache> {
public:
Cache() { ... }
~Cache() noexcept { ... }
...
};
int main() {
Window& window = Window::getInstance();
Cache& cache = Cache::getInstance();
return 0;
}
Reference
'Programming > C++ Template' 카테고리의 다른 글
Type Selection (0) | 2024.05.21 |
---|---|
Thin Template 기법 (0) | 2024.04.28 |