템플릿은 함수나 클래스 코드를 찍어내듯이 생산할 수 있도록 일반화 시키는 도구이다. template
키워드를 사용하면 중복 함수둘을 일반화시킨 특별한 함수를 만들 수 있는데 이 함수를 제네릭 함수
혹은 템플릿 함수
라고 부른다.
#include <iostream>
using namespace std;
class Circle {
int radius;
public:
Circle() {} //템플릿 함수에서 T tmp; 를 위해 기본생성자가 필요
Circle(int radius) {
this->radius = radius;
}
int getRadius() {
return radius;
}
};
template <class T>
void myswap(T & a, T & b) {
T tmp;
tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 4, b = 5;
myswap(a, b);
cout << "a = " << a << ", " << "b = " << b << endl;
double c = 1.3, d = 12.9;
myswap(c, d);
cout << "c = " << c << ", " << "d = " << d << endl;
Circle e(5), f(13);
myswap(e, f);
cout << "e의 반지름 : " << e.getRadius() << ", " << "f의 반지름 : " << f.getRadius() << endl;
}
a = 5, b = 4
c = 12.9, d = 1.3
e의 반지름 : 13, f의 반지름 : 5
이때 class 대신 typename 을 사용하여도 된다. 여기에서 T는 int, double 등 여러가지의 값이 될 수 있기 때문에 함수를 호출할 때 원하는 타입을 작성해주면 함수를 여러개 만들지 않고도 여러개의 타입으로 같은 함수를 실행할 수 있다. 또한 myswap()함수가 모두 T로 선언되어 있기 때문에 함수를 호출할 때 타입에 맞추어 호출하여야 한다.
중복 함수들을 템플릿화 하는 과정의 역과정을 구체화라고 하며 컴파일러가 함수의 호출문을 컴파일할 때, 구체화를 통해 제네릭 함수로부터 구체적인 함수의 소스코드를 만들어 낸다. 구체화를 통해 생성된 함수를 구체화된 함수라고 부른다.
템플릿 함수는 컴파일되지도 호출되지도 않는 함수의 틀이다. 템플릿의 역할은 제네릭 함수를 선언하고, 컴파일 시점에 구체화시키기 위한 틀을 만드는 것으로 템플릿 함수로부터 구체화된 버전의 함수가 컴파일되고 호출된다.
템플릿은 함수의 작성을 용이하게 하고, 함수 코드의 재사용을 가능하게 하며 소프트웨어의 생산성과 유연성을 높이지만 컴파일러에 따라 템플릿이 지원되지 않을 수 있기 때문에 포팅에 취약하다. 또한 템플릿과 관련된 컴파일 오류 메시지가 빈약하여 디버깅에 많은 어려움이 있다.
template
을 사용하여 제네릭 클래스를 만들 수 있다.
제네릭 클래스를 만들기 위해 클래스 선언부와 구현부를 모두 template
으로 선언한다. 제네릭 클래스의 멤버 함수는 자동 제네릭 함수이다.
#include <iostream>
using namespace std;
template <class T>
class MyStack {
int top;
T data[100];
public:
MyStack();
void push(T element);
T pop();
};
template <class T>
MyStack<T>::MyStack() {
top = -1;
}
template <class T>
void MyStack<T>::push(T element) {
if(top == 99) {
cout << "stack is full" << endl;
return;
}
data[++top] = element;
}
template <class T>
T MyStack<T>::pop() {
T returnData;
if(top == -1) {
cout << "stack is empty" << endl;
return 0;
}
returnData = data[top--];
return returnData;
}
int main() {
MyStack<int> iStack;
iStack.push(3);
cout << iStack.pop() << endl;
MyStack<double> dStack;
dStack.push(3.14);
cout << dStack.pop() << endl;
MyStack<char> cStack;
cStack.push('a');
cout << cStack.pop() << endl;
}
3
3.14
a
표준 템플릿 라이브러리는 템플릿으로 작성된 많은 제네릭 클래스와 함수 라이브러리로 일반화 프로그래밍 혹은 제네릭 프로그래밍이라는 새로운 프로그래밍 패러다임을 가져왔다. STL에 구현된 제네릭 함수나 클래스를 이용하면 쉽게 C++ 프로그램을 구축할 수 있다.
STL에 포함된 제네릭 클래스와 함수들은 3가지 종류로 분류된다
STL은 std namespace에 작성되었기 때문에 STL을 사용하려면 아래의 코드가 필요하다.
using namespace std;
또한 컨테이너 클래스를 사용하고 싶다면 해당 템플릿이 선언된 헤더 파일들을 include 시켜야 하며 알고리즘 함수를 사용하고 싶다면
#include <algorithm>
을 추가해 주어야 한다.
vector는 가변 길이 배열을 구현한 제네릭 클래스로 내부에 배열을 가지고 원소를 저장, 삭제, 검색하는 멤버들을 제공한다. 또한 vector는 스스로 내부 크기를 조절하므로 개발자가 크기에 대해 고민할 필요가 없다. 원소의 시작인덱스는 0부터 시작한다
#include <iostream>
#include <vector> //vector 를 사용하기 위한 헤더파일
using namespace std;
int main() {
vector<int> v; //int타입이 벡터 v 생성
v.push_back(1);
v.push_back(2);
v.push_back(3); //1, 2, 3 이 순서대로 저장
//at 함수로 접근하거나 배열처럼 []를 사용하여 접근
v.at(2) = 5; //1, 2, 5 로 값 변경
v[1] = 7; //1, 7, 5 로 값 변경
//size 함수로 개수를 구할 수 있음
cout << v.size() << endl; //3 출력
}
iterator
는 컨테이너 안에 있는 원소들을 하나씩 순차적으로 접근하기 위한 포인터이다. iterator를 생성하려면 컨테이너 템플릿에 구체적인 타입을 지정하여 원소의 타입이 드러나도록 해야 한다.
#include <iostream>
#include <vector> //vector 를 사용하기 위한 헤더파일
using namespace std;
int main() {
vector<int> v; //int타입이 벡터 v 생성
v.push_back(1);
v.push_back(2);
v.push_back(3); //1, 2, 3 이 순서대로 저장
vector<int>::iterator it; //벡터 v의 원소에 대한 포인터 선언
it = v.begin(); //it는 벡터v의 첫번째 원소인 1을 가르킴
cout << *it << endl;
it++; //it는 두번째 원소인 2를 가르킴
cout << *it << endl;
it = v.erase(it); //v의 두번째 원소 2를 지우고 값을 당겨 3의 값을 가르킴
cout << *it << endl; //1, 3 만 벡터에 존재
}
1
2
3
map은 key
와 value
의 쌍을 원소로 저장하고 키를 이용하여 값을 검색하는 제네릭 컨테이너이다. 키나 값은 기본 타입, 클래스 타입 모두 가능하지만 동일한 키를 가지는 원소가 중복 저장되면 오류가 발생한다.
#include <iostream>
#include <string>
#include <map> //map을 사용하기 위한 헤더파일
using namespace std;
int main() {
map <string, string> dic; //dic이름의 맵 생성
dic.insert(make_pair("apple", "사과")); //insert함수로 값 추가
dic.insert(make_pair("banana", "바나나"));
dic["cherry"] = "채리"; //[]연산자를 이용하여 값 추가
cout << dic.size() << endl; //맵의 크기는 3
string eng = "apple";
//[] 연산자를 이용하면 키로 검색하여 값을 찾을 수 있다.
//[] 연산자는 값을 찾을 수 없으면 빈 문자열을 리턴하지만 at 함수를 사용하면 예외를 발생시켜 예외처리가 필요하다.
cout << dic[eng] << endl;
//맵에 키의 데이커가 있는지 검사하기 위한 코드
if(dic.find(eng) == dic.end()) {
cout << "값을 찾을 수 없습니다." << endl;
}
else {
cout << dic[eng] << endl;
}
}
3
사과
사과
STL 알고리즘은 전역함수로 STL 컨테이너 클래스의 멤버 함수가 아니며 템플릿으로 작성되어 있다. STL 알고리즘 함수는 iterator와 함께 작동한다.
#include <iostream>
#include <vector> //vector 를 사용하기 위한 헤더파일
#include <algorithm> //알고리즘을 사용하기 위한 헤더파일
using namespace std;
int main() {
vector<int> v; //int타입이 벡터 v 생성
cout << "5개의 정수를 입력하세요>> ";
for(int i = 0; i < 5; i++) {
int n;
cin >> n;
v.push_back(n);
}
sort(v.begin(), v.end()); //벡터의 시작과 끝 사이의 값을 오름차순으로 정렬
vector<int>::iterator it;
for(it = v.begin(); it != v.end(); it++) { //벡터의 모든값을 iterator를 사용해 출력
cout << *it << " ";
}
cout << endl;
}
5개의 정수를 입력하세요>> 28 -12 240 8 100
-12 8 28 100 240
람다는 람다 대수에서 유래하며 람다 대수에서 람다식은 수학의 함수를 단순하게 표현하는 방법이다. 람다는 익명함수, 람다식, 람다 함수로 불리며 기본 구조는 캡쳐 리스트, 매개변수 리스트, 리턴타입, 함수 바디 4부분으로 구성된다.
#include <iostream>
using namespace std;
int main() {
[](int x, int y) {cout << "x와 y의 합은 : " << x + y << endl; } (2, 3);
}
x와 y의 합은 : 5
auto 키워드는 C++11 표준부터 의미가 수정되어, 변수 선언문으로부터 변수의 타입을 추론하여 결정하도록 지시한다. auto는 복잡한 형식의 변수 선언을 간단하게 해주며 타입선언의 번거로움과 오타를 줄일 수 있다.
#include <iostream>
using namespace std;
int main() {
auto sum = [](int a, int b) {
cout << a << " + " << b << " = " << a+b << endl;
};
sum(1, 5);
sum(3, 7);
}
1 + 5 = 6
3 + 7 = 10
람다식은 캡쳐 리스트를 이용하여 주면의 non-static 변수들에 대해 값을 복사하여 받거나 참조하여 활용할 수 있다.
#include <iostream>
using namespace std;
int main() {
double pi = 3.14;
auto calc = [pi](int r)->double { //지역변수 pi를 불러와 내부에서 사용하고 double로 리턴하는 람다식
return pi * r * r;
};
cout << "면적 : " << calc(3);
}
면적 : 28.26