Generic Programming(일반화 프로그래밍)과 템플릿
- 제네릭 프로그래밍이란 ? : 타입에 관계없이 동작하는 일반적인 코드를 작성하는 방법
- 예를 들어, 여러 가지 덧셈을 수행하는 함수를 정의할 때, 기존에는 덧셈에 사용되는 인자들의 타입에 따라 개별적으로 함수들을 구현해야 했다.
- add(int i1, int i2), add(double d1, double d2), add(Point p1, Point p2)
- 이를 하나의 함수로 구현할 수 있게 하는 기법이 제네릭 프로그래밍 기법이다.
- 제네릭 프로그래밍의 구현 방법
- 매크로를 사용한 제네릭 프로그래밍
#include <iostream>
#define MAX(a, b) ((a>b)?a:b)
int main() {
int x = 100;
int y = 200;
std::cout << MAX(x, y); // ((x>y)?x:y)
std::cout << ((x>y) ? x : y);
double z = 1.234;
double w = 3.456;
std::cout << MAX(z,w); // ((z,w)?z:w)
}
- 매크로를 사용한 제네릭 프로그래밍 유의사항
- 코드가 단순히 대체된다는 사실에 유의
- 매크로문을 괄호로 감싸는 것이 안전
#define SQUARE1(a) a*a
#define SQUARE2(a) (a*a)
result = 100/SQUARE1(5) // expect 4 but result 100 because 100/5*5
result = 100/SQUARE2(5) // expect 4 and result 4 because 100/(5*5)
- 매크로를 이용한 제네릭 프로그래밍을 지양하는 이유
-
- 매크로는 디버깅이 불가하다.
-
- 매크로의 확장은 사이드 이펙트를 발생시킬 수 있다.
-
- 매크로는 네임스페이스에 선언 불가하여 전역으로 잡힌다. 따라서 충돌 우려가 있다.
-
- 매크로는 미처 인식하지 못한 영역에 영향을 미칠 수 있다.
C++ 함수 템플릿
- 템플릿을 사용한 max 함수의 구현
-
- 타입명을 지정한 이름(T)으로 대체
-
- 대체한 타입명(T)을 템플릿의 템플릿 매개변수로 명시
template <typename T> or template <class T>
- 타입명을 반드시 T로 해야되는 것은 아니다.
template <typename T> T max(T a, T b)
{
return (a > b) ? a : b;
}
- 템플릿을 사용한 max 함수의 사용
- 템플릿 함수를 사용할 때는 자료형을
<>안에 명시
- 단, 컴파일러가 추론 가능할 때는 생략 가능
- 호출할 타입이 결정되면, 컴파일타임에 컴파일러가 템플릿을 기반으로 실제 함수를 생성
- 템플릿 코드만 존재할 때는 아무 함수도 생성되지 않는다. 실제 사용이 되어야만 생성된다.
int main() {
int a = 10;
int b = 20;
std::cout << max<int>(a, b) << std::endl;
double c = 1.234;
double d = 3.456;
std::cout << max<double>(c, d) << std::endl;
std::cout << max(c, d) << std::endl; // 생략 가능
}
- 템플릿이 사용 가능한 경우
- 템플릿 함수의 구현부에 사용된 연산이 실행 가능해야만 한다.
- 템플릿 매개변수에 전달된 타입 인자가 클래스인 경우 해당 클래스 안에 템플릿 함수의 구현부에 사용된 연산이 정의돼 있어야 한다
class Point {
int x;
int y;
public:
Point(int x, int y): x{x}, y{y} { }
bool operator>(const Point& p) const { // 템플릿 함수 max에 사용된 > 연산자 정의
if((x*y) > ((p.x)*(p.y)))
return true;
return false;
}
};
template <typename T> T max(T a, T b)
{
return (a > b) ? a : b;
}
int main() {
Point p1(1, 1);
Point p2(2, 4);
max<Point>(p1, p2)
}
- 템플릿의 다중 매개변수
- 서로 다른 이름을 사용하여 다중 매개변수를 정의 가능
- 매개변수가 다르다면 서로 타입이 다를 수 있음
template <typename T1, typename T2> void func(T1 a, T2 b)
{
cout << a << " " << b;
}
int main() {
func<int, double>(20, 20.5);
func('A', 12.4);
}
template <typename T1, typename T2> T1 max(T1 a, T2 b)
{
return (a > b) ? a : b;
}
int main(){
double a = 20.6;
int b = 3;
std::cout << max<double, int>(a, b) << std::endl;
}
- 템플릿의 특수화: 특정 자료형에 대해서는 템플릿 함수가 아닌 별도로 구현한 함수를 사용하도록 명시한다.
template <typename T> T max(T a, T b)
{
return (a > b) ? a : b;
}
// std::string애 대해 템플릿 함수 max를 특수화
template <> std::string max(std::string a, std::string b)
{
return (a.length >= b.length) ? a : b;
}
int main() {
std::string s1 = "abc";
std::string s2 = "defg";
std::cout << max<std::string>(s1, s2) << std::endl;
}
- 함수 템플릿 다중정의
- 함수 템플릿은 일반 함수와 마찬가지로 다중정의를 허용한다.
- 즉, 함수 템플릿과 이름이 같은 다른 함수가 존재할 수 있다는 것이다.
void swapValues(Complex &a, Complex &b)
{
...
}
template <typename T> void swapValues(T &a, T &b)
{
...
}
template <typename T> void swapValues(T a[], T b[], int n)
{
...
}
- 이름이 같은 함수가 여러 개 있을 경우에는 함수 이름과 인수들의 데이터 타입이 함수의 파라미터 타입과 정확히 일치하는 함수가 우선적으로 호출된다.
- 그러한 함수가 없을 때는 호출한 함수와 정확하게 일치시키는 변환이 가능한 템플릿 함수가 호출된다.
int main() {
Complex c1, c2;
int a, b;
int x[10], y[10];
swapValues(c1, c2);
swapValues(a, b);
swqpValues(x, y, 10);
}
C++ 클래스 템플릿
- 클래스 템플릿
- 함수 템플릿과 유사하게 템플릿을 이용한 클래스의 구현을 지원
- 컴파일러가 타입에 따라 적절한 클래스를 생성해준다.
- 클래스 템플릿 정의
template <typename T>
class Item
{
private:
string name;
T value;
public:
Item(string name, T value): name{name}, value{value} {}
string GetName() const { return name; }
T GetValue() const { return value; }
}
- 클래스 템플릿 사용
- 함수와 같이 타입을 적지 않아도 타입을 추론하는 기능이 C++17부터 추가됨
int main() {
Item<int> i{"A", 10};
std::cout << i.GetName() << std::endl;
std::cout << i.GetValue() < std::endl;
Item<double> d{"D", 20.34};
std::cout << d.GetName() << std::endl;
std::cout << d.GetValue() < std::endl;
Item i2{"A", 10}; // Item<int>로 추론
Item d2{"D", 20.34}; // Item<double>로 추론
Item s{"s", "Hello"}; // Item<std::string>으로 추론
}
template <typename T1, typename T2>
class MyPair {
private:
T1 first;
T2 second;
public:
MyPair(T1 val1, T2 val2)
: first{val1}, second{val2} {}
}
int main() {
MyPair<string, int> p1("Kim", 1);
MyPair<int, double> p2(1, 2.43);
}
template <typename T1, typename T2>
class MyPair {
private:
T1 first;
T2 second;
public:
MyPair(T1 val1, T2 val2)
: first{val1}, second{val2}
{
std::cout << "클래스 템플릿" << std::endl;
}
}
template <typename T>
class MyPair<T, double>{
private:
T first;
double second;
public:
MyPair(T1 val1, T2 val2)
: first{val1}, second{val2}
{
std::cout << "부분 특수화" << std::endl;
}
}
int main() {
MyPair<int, int> p1{32, 55}; // 클래스 템플릿
MyPair<int, double> p2{10, 20.4354}; // 부분 특수화
}
- non-type 템플릿 매개변수
- 템플릿 매개변수로 타입이 아닌 다른 매개변수를 사용할 수 있다.
template <typename T, int N>
class Array {
private:
int size = N;
T values[N];
}
int main() {
Array<int, 5> nums;
Array<double, 10> nums2;
}