일반화 프로그래밍(generic programming)
C++이 가지는 프로그래밍 언어로서의 특징 중 하나로 일반화 프로그래밍(generic programming)을 들 수 있습니다.
일반화 프로그래밍은 데이터를 중시하는 객체 지향 프로그래밍과는 달리 프로그램의 알고리즘에 그 중점을 둡니다.
이러한 일반화 프로그래밍을 지원하는 C++의 대표적인 기능 중 하나가 바로 템플릿(template)입니다.
템플릿(template)
템플릿(template)이란 매개변수의 타입에 따라 함수나 클래스를 생성하는 메커니즘을 의미합니다.
템플릿은 타입이 매개변수에 의해 표현되므로, 매개변수화 타입(parameterized type)이라고도 불립니다.
템플릿을 사용하면 타입마다 별도의 함수나 클래스를 만들지 않고, 여러 타입에서 동작할 수 있는 단 하나의 함수나 클래스를 작성하는 것이 가능합니다.
변수 템플릿
// variable template
template <typename T>
constexpr T pi = T(3.1415926535897932385L);
int main()
{
float f = pi<float>;
double d = pi<double>;
int i = pi<int>;
return 0;
}
JavaScript
복사
함수 템플릿(function template)
C++에서 함수 템플릿(function template)이란 함수의 일반화된 선언을 의미합니다.
함수 템플릿을 사용하면 같은 알고리즘을 기반으로 하면서, 서로 다른 타입에서 동작하는 함수를 한 번에 정의할 수 있습니다.
임의의 타입으로 작성된 함수에 특정 타입을 매개변수로 전달하면, C++ 컴파일러는 해당 타입에 맞는 함수를 생성해 줍니다.
C++에서 함수 템플릿은 다음과 같은 문법으로 정의합니다.
문법
template <typename 타입이름>
함수의 원형
{
// 함수의 본체
}
C++98에서 추가된 typename 키워드 이전에는 class 키워드를 사용했습니다.
따라서 C++ 컴파일러는 템플릿 정의 내의 typename 키워드와 class 키워드를 같은 것으로 간주합니다.
위에서 정의된 타입 이름은 함수의 원형과 본체에서 임의의 타입으로 사용할 수 있습니다.
이렇게 정의된 함수 템플릿을 호출할 때 매개변수로 int형을 전달하면, 함수의 원형과 본체에서 정의된 타입 이름은 모두 int형으로 바뀌게 됩니다.
다음 예제는 여러 타입의 변수의 값을 서로 바꿔주는 Swap() 함수를 함수 템플릿으로 작성한 예제입니다.
예제
//int Add(int a, int b)
//{
// return a + b;
//}
//
//float Add(float a, float b)
//{
// return a + b;
//}
template <typename T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
srand(time(NULL));
int a = Add<int>(1, 2);
float b = Add<float>(1.0f, 2.0f);
return 0;
}
JavaScript
복사
template <typename T>
void Swap(T& a, T& b);
int main(void)
{
int c = 2, d = 3;
cout << "c : " << c << ", d : " << d << endl;
Swap(c, d);
cout << "c : " << c << ", d : " << d << endl;
string e = "hong", f = "kim";
cout << "e : " << e << ", f : " << f << endl;
Swap(e, f);
cout << "e : " << e << ", f : " << f << endl;
return 0;
}
template <typename T>
void Swap(T& a, T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
C++
복사
실행 결과
c : 2, d : 3
c : 3, d : 2
e : hong, f : kim
e : kim, f : hong
위의 예제에서 Swap() 함수 템플릿은 정수형 숫자뿐만 아니라 문자열에 대해서도 정상적으로 동작합니다.
함수 템플릿의 인스턴스화
함수 템플릿이 각각의 타입에 대해 처음으로 호출될 때, C++ 컴파일러는 해당 타입의 인스턴스를 생성합니다.
이렇게 생성된 인스턴스는 해당 타입에 대해 특수화된 템플릿 함수입니다.
이 인스턴스는 함수 템플릿에 해당 타입이 사용될 때마다 호출됩니다.
명시적 특수화(explicit specialization)
C++의 함수 템플릿은 특정 타입에 대한 명시적 특수화를 제공하여, 해당 타입에 대해 특별한 동작을 정의할 수 있게 해줍니다.
컴파일러는 호출된 함수에 정확히 대응하는 특수화된 정의를 발견하면, 더는 템플릿을 찾지 않고 해당 정의를 사용합니다.
앞서 정의된 함수 템플릿 Swap의 double형에 대한 명시적 특수화는 다음과 같습니다.
함수 템플릿 원형
template <typename T>
void Swap(T& a, T& b);
template <typename T> void Swap(T& a, T& b);
C++
복사
double형을 위한 명시적 특수화
template <> void Swap<double>(double&, double&) { ... };
다음 예제는 Swap() 함수 템플릿에서 double형에 대한 동작만을 변경하기 위해 명시적 특수화를 사용한 예제입니다.
예제
template <> void Swap<double>(double&, double&)
{
// double형은 값을 서로 바꾸지 않음.
}
실행 결과
c : 2, d : 3
c : 3, d : 2
e : 1.234, f : 4.321
e : 1.234, f : 4.321
위의 예제에서 Swap() 함수는 double형에 대해서는 더는 값을 서로 바꾸지 않게 됩니다.
클래스 템플릿(class template)
C++에서 클래스 템플릿(class template)이란 클래스의 일반화된 선언을 의미합니다.
앞서 살펴본 함수 템플릿과 동작은 같으며, 그 대상이 함수가 아닌 클래스라는 점만 다릅니다.
클래스 템플릿을 사용하면, 타입에 따라 다르게 동작하는 클래스 집합을 만들 수 있습니다.
즉, 클래스 템플릿에 전달되는 템플릿 인수(template argument)에 따라 별도의 클래스를 만들 수 있게 됩니다.
이러한 템플릿 인수는 타입이거나 명시된 타입의 상숫값일 수 있습니다.
C++에서 클래스 템플릿은 다음과 같은 문법으로 정의할 수 있습니다.
template <typename 타입이름>
class 클래스템플릿이름
{
// 클래스 멤버의 선언
}
C++
복사
함수 템플릿과 마찬가지로 템플릿 정의 내에서 typename 키워드 대신에 class 키워드를 사용할 수 있습니다.
위에서 정의된 타입 이름은 클래스의 선언에서 임의의 타입으로 사용할 수 있습니다.
다음 예제는 클래스 템플릿을 사용하여 다양한 타입의 데이터를 저장할 수 있는 Data 클래스를 작성한 예제입니다.
template <typename T>
class Data
{
private:
T data_;
public:
Data(T dt);
data(T dt);
T get_data();
};
C++
복사
실행 결과
str_data : C++ 수업
int_data : 12
다음은 템플릿을 linked list를 만들어보자.
#include <iostream>
#include <list>
namespace ya
{
template <typename T>
class list
{
public:
struct Node
{
T data;
Node* back;
};
list() // 생성자 : 객체가 생성될떄 (메모리에 할당될때) 자동으로 호출되는 함수
{
mHead = nullptr;
mTail = nullptr;
}
~list() // 소멸자 : 객체가 사라질때 (메모리에 해제될때) 자동으로 호출되는 함수
{
}
void push_back(T data)
{
if (mHead == nullptr)
{
mHead = new Node();
mHead->data = data;
mHead->back = nullptr;
mTail = mHead;
}
else
{
mTail->back = new Node();
mTail->back->data = data;
mTail->back->back = nullptr;
mTail = mTail->back;
}
}
private:
Node* mHead;
Node* mTail;
};
}
int main()
{
std::list<int> stlList;
stlList.push_back(10);
stlList.push_back(20);
stlList.push_front(-10);
ya::list<int> yaList;
yaList.push_back(1);
yaList.push_back(2);
yaList.push_back(3);
return 0;
}
C++
복사
연산자 오버로드
operator 키워드(keyword) 클래스 인스턴스에 적용할 때 연산자 기호의 의미를 지정하는 함수를 선언합니다. 이 키워드는 연산자에게 둘 이상의 의미를 제공 즉, 오버로드합니다. 컴파일러는 피연산자의 형식을 검사하여 연산자의 여러 가지 의미 간을 구분합니다.
구문
typeoperatoroperator-symbol(parameter-list)\
설명
대부분의 기본 제공 연산자의 함수는 전역적으로 또는 클래스 단위로 다시 정의할 수 있습니다. 오버로드된 연산자는 함수로 구현됩니다.
오버로드된 연산자의 이름은 x입니다operator. 여기서 x는 다음 표에 표시된 연산자입니다. 예를 들어 더하기 연산자를 오버로드하려면 operator+라는 함수를 정의합니다. 마찬가지로 더하기/대입 연산+=자를 오버로드하려면 operator+=라는 함수를 정의합니다.
다시 정의할 수 있는 연산자
테이블 확장
연산자 | 이름 | Type |
, | Comma | 이진 |
! | 논리 NOT | 단항 |
!= | 같지 않음 | 이진 |
% | 모듈러스 | 이진 |
%= | 모듈러스 대입 | 이진 |
& | 비트 AND | 이진 |
& | Address-of | 단항 |
&& | 논리적 AND | 이진 |
&= | 비트 AND 대입 | 이진 |
( ) | 함수 호출 | — |
( ) | 캐스트 연산자 | 단항 |
* | 곱하기 | 이진 |
* | 포인터 역참조 | 단항 |
*= | 곱하기 할당 | 이진 |
+ | 더하기 | 이진 |
+ | 단항 더하기 | 단항 |
++ | 증 분 1 | 단항 |
+= | 더하기 할당 | 이진 |
- | 빼기 | 이진 |
- | 단항 부정 연산자 | 단항 |
-- | 감소 1 | 단항 |
-= | 빼기 할당 | 이진 |
-> | 멤버 선택 | 이진 |
->* | 멤버 포인터 선택 | 이진 |
/ | 나누기 | 이진 |
/= | 나누기 할당 | 이진 |
< | 보다 작음 | 이진 |
<< | 왼쪽 시프트 | 이진 |
<<= | 왼쪽 시프트 할당 | 이진 |
<= | 보다 작거나 같음 | 이진 |
= | 양도 | 이진 |
== | Equality | 이진 |
> | 보다 큼 | 이진 |
>= | 크거나 같음 | 이진 |
>> | 오른쪽 시프트 | 이진 |
>>= | 오른쪽 시프트 할당 | 이진 |
[ ] | 배열 첨자 | — |
^ | 배타적 OR | 이진 |
^= | 배타적 OR 할당 | 이진 |
| | 포괄적 비트 OR | 이진 |
|= | 포괄적 비트 OR 대입 | 이진 |
|| | 논리적 OR | 이진 |
~ | 1의 보수 | 단항 |
delete | 삭제 | — |
new | 새로 만들기 | — |
conversion operators | conversion operators | 단항 |
1 단항 증가 및 감소 연산자의 두 가지 버전이 있습니다. 즉, 사전 증가 및 사후 증가입니다.
•
•
•
다음 테이블의 연산자는 오버로드할 수 없습니다. 테이블에는 전처리기 기호 및 ###.
다시 정의할 수 없는 연산자
테이블 확장
연산자 | 이름 |
. | 멤버 선택 |
.* | 멤버 포인터 선택 |
:: | 범위 확인 |
? : | 조건부 |
# | 문자열로 전처리기 변환 |
## | 전처리기 연결 |
오버로드된 연산자는 코드에서 발견되었을 때 일반적으로 컴파일러에 의해 암시적으로 호출되지만 다음과 같이 멤버 또는 비멤버 함수가 호출될 때처럼 명시적으로 호출할 수 있습니다.
C++복사
Point pt;
pt.operator+( 3 ); // Call addition operator to add 3 to pt.
C++
복사
예시
다음 예제에서는 연산자를 + 오버로드하여 두 개의 복소수를 추가하고 결과를 반환합니다.
C++복사
// operator_overloading.cpp
// compile with: /EHsc
#include <iostream>using namespace std;
struct Complex {
Complex( double r, double i ) : re(r), im(i) {}
Complex operator+( Complex &other );
void Display( ) { cout << re << ", " << im << endl; }
private:
double re, im;
};
// Operator overloaded using a member function
Complex Complex::operator+( Complex &other ) {
return Complex( re + other.re, im + other.im );
}
int main() {
Complex a = Complex( 1.2, 3.4 );
Complex b = Complex( 5.6, 7.8 );
Complex c = Complex( 0.0, 0.0 );
c = a + b;
c.Display();
}
C++
복사
Output복사
6.8, 11.2
Plain Text
복사
#include <iostream>
#include <list>
// 수학에서의 벡터
// 크기와 방향을 가진 화살표(점)
struct Point
{
int x;
int y;
Point operator+(Point other)
{
Point ret;
ret.x = x + other.x;
ret.y = y + other.y;
return ret;
}
Point operator-(Point other)
{
Point ret;
ret.x = x - other.x;
ret.y = y - other.y;
return ret;
}
Point operator-(int value)
{
Point ret;
ret.x = x - value;
ret.y = y - value;
return ret;
}
bool operator<(Point other)
{
return (x < other.x && y < other.y);
}
};
// 연산자 오버로딩
int main()
{
Point p1;
p1.x = 1;
p1.y = 1;
Point p2;
p2.x = 3;
p2.y = 2;
Point p3;
p3.x = p1.x + p2.x;
p3.y = p1.y + p2.y;
p3 = p1 + p2; //p1.operator+(p2);
p3 = p1 - p2; //p1.operator+(p2);
if (p1 < p2)
{
int a = 0;
}
std::string test = "test";
test += " success";
//p1.operator+(p2);
return 0;
}
C++
복사
숙제
1.
ya::list 3번 따라서 쳐보고 디버깅 하면서 메모리 구조를 그려주세요. (메모리 그림도 숙제로 제출)
2.
ya::list에 push_front()함수를 구현
3.
ya::list::iterator 구현
std::list<int> list;
list.push_back(1);
list.push_back(2);
std::list<int>::iterator iter = list.begin();
iter++;
1.
Vector2 구조체에 10가지 이상의 연산자 오버로딩을 구현