함수 템플릿과 클래스 템플릿
함수 템플릿
- 배열의 합을 구하는 예제를 봐보자
#include <iostream>
using namespace std;
int getArraySum(const int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int arr[5] = { 3, 1, 4, 1, 5 };
int sum = getArraySum(arr, 5);
cout << sum << endl;
}
int 형이 아닌 float 타입등 여러 자료형으로 이루어진 배열의 합을 구하고 싶은 경우가 있을 것이다.
- 이 때 Overloading을 통해서 float getArraySum이라는 함수를 또 만들어 줘야 한다
#include <iostream>
using namespace std;
int getArraySum(const int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
float getArraySum(const float arr[], int n) {
float sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int arr[5] = { 3, 1, 4, 1, 5 };
float farr[5] = { 3.1, 1.2, 4.3, 1.4, 5.5 };
float sum = getArraySum(farr, 5);
cout << sum << endl;
}
- float 자료형 이외에도 다른 자료형의 합을 구할 경우가 생길 수 있는데, 이 때 자료형 부분만 다르고 나머지는 공통적이므로, 자료형을 T라고 치환해서 처리하면 깔끔할 것 같다
T getArraySum(const T arr[], int n) {
T sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
- 위와 같은 기능을 해주는 것이 함수 템플릿이다.
#include <iostream>
using namespace std;
template<typename T>
T getArraySum(const T arr[], int n) {
T sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int iarr[5] = {3, 1, 4, 1, 5};
float farr[5] = {3.1, 1.2, 4.3, 1.4, 5.5};
int isum = getArraySum<int>(iarr, 5);
float fsum = getArraySum<float>(farr, 5);
cout << isum << endl;
cout << fsum << endl;
}
임의의 타입에 대한 함수를 찍어낼 수 있다.
typename T : 타입 파라미터
getArraySum
: int 버전의 getArraySum을 만들어라 템플릿은 템플릿 자체로 함수가 아니다. 함수를 생성해주는 템플릿이다.
템플릿 하나만 만들어 준다면 임의의 타입에 대해서도 함수를 생성해준다
Example) Vector 클래스
#include <iostream>
using namespace std;
class Vector2 {
public:
Vector2() : x(0), y(0) {}
Vector2(float x, float y) : x(x), y(y) {}
float GetX() const { return x; }
float GetY() const { return y; }
private:
float x, y;
};
template <typename T>
T getArraySum(const T arr[], int n) {
T sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int iarr[5] = {3, 1, 4, 1, 5};
float farr[5] = {3.1, 1.2, 4.3, 1.4, 5.5};
Vector2 varr[3] = { Vector2(1, 2), Vector2(3,4), Vector2(5, 6) };
int isum = getArraySum<int>(iarr, 5);
float fsum = getArraySum<float>(farr, 5);
Vector2 vsum = getArraySum<Vector2>(varr, 5);
cout << isum << endl;
cout << fsum << endl;
}
컴파일 타임에 에러가 발생한다
- 함수를 만들려고 할 때 T sum = 0에서 0을 Vector2 타입으로 변환하는 과정에서 오류가 발생한다
해결책 1
- T sum = arr[0];으로 하고 for문을 i = 1 부터 돌려보자
template <typename T>
T getArraySum(const T arr[], int n) {
T sum = arr[0];
for (int i = 1; i < n; i++) {
sum += arr[i];
}
return sum;
}
이렇게 해도 오류가 발생한다
Vector2 클래스의 += 연산자에 대한 정의가 되어있지 않아 오류가 발생한다.
연산자 오버로딩을 해줘야 한다
class Vector2 {
public:
Vector2() : x(0), y(0) {}
Vector2(float x, float y) : x(x), y(y) {}
float GetX() const { return x; }
float GetY() const { return y; }
Vector2 &operator+=(const Vector2 &rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
private:
float x, y;
};
int main() {
int iarr[5] = {3, 1, 4, 1, 5};
float farr[5] = {3.1, 1.2, 4.3, 1.4, 5.5};
Vector2 varr[3] = { Vector2(1, 2), Vector2(3,4), Vector2(5, 6) };
int isum = getArraySum<int>(iarr, 5);
// getArraySum(iarr, 5); 도 가능하다
float fsum = getArraySum<float>(farr, 5);
// getArraySum(farr, 5); 도 가능하다
Vector2 vsum = getArraySum<Vector2>(varr, 5);
// getArraySum(varr, 5); 도 가능하다
cout << isum << endl;
cout << fsum << endl;
cout << vsum.GetX() << ", " << vsum.GetY() << endl;
}
getArraySum
(iarr, 5)에서 굳이 를 안밝혀주고 getArraySum(iarr, 5)로 적어도 컴파일러가 알아서 타입을 추정해준다 - 즉, 타입 인수를 생략해도 된다
string 타입 배열에 대해서도 작동한다
int main() {
int iarr[5] = {3, 1, 4, 1, 5};
float farr[5] = {3.1, 1.2, 4.3, 1.4, 5.5};
Vector2 varr[3] = { Vector2(1, 2), Vector2(3,4), Vector2(5, 6) };
string sarr[3] = { "hello", "world", "luna" };
int isum = getArraySum<int>(iarr, 5);
float fsum = getArraySum<float>(farr, 5);
Vector2 vsum = getArraySum<Vector2>(varr, 5);
string ssum = getArraySum(sarr, 3);
cout << isum << endl;
cout << fsum << endl;
cout << vsum.GetX() << ", " << vsum.GetY() << endl;
cout << ssum << endl;
}
- 위에서 T sum = arr[0]로 바꿨던 해결책 1 말고도 다른 방법이 존재한다. 아래 코드를 봐보자
template <typename T>
T getArraySum(const T arr[], int n) {
T sum = new T();
// T sum;은 불가능
for (int i = 1; i < n; i++) {
sum += arr[i];
}
return sum;
}
T의 기본 생성자를 호출한다.
int나 float의 경우 0으로 초기화되고, 사용자 정의 클래스에서는 각 클래스에서 정의한 기본 생성자가 호출된다
string의 경우 빈 문자열이 생긴다
T sum; 요거는 불가능 하다
클래스 템플릿
Example) Vector 클래스
#include <iostream>
#include <string>
using namespace std;
class Vector2 {
public:
Vector2() : x(0), y(0) {}
Vector2(float x, float y) : x(x), y(y) {}
float GetX() const { return x; }
float GetY() const { return y; }
Vector2 operator+(const Vector2 &rhs) const {
return Vector2(x + rhs.x, y + rhs.y);
}
Vector2 operator-(const Vector2 &rhs) const {
return Vector2(x - rhs.x, y - rhs.y);
}
Vector2 &operator+=(const Vector2 &rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
Vector2 &operator-=(const Vector2 &rhs) {
x -= rhs.x;
y -= rhs.y;
return *this;
}
private:
float x, y;
};
int main() {
Vector2 v1(2, 3);
}
Vector2 클래스에서 float 말고도 int, double 등등의 타입을 담을 수 있게 만들고 싶다.
- Vector2d, Vector2i와 같이 별도로 클래스를 만드는 것은 매우 번거로움
클래스 템플릿이라는 것을 사용하면 쉽게 구현할 수 있다.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Vector2 {
public:
Vector2() : x(0), y(0) {}
Vector2(T x, T y) : x(x), y(y) {}
T GetX() const { return x; }
T GetY() const { return y; }
Vector2 operator+(const Vector2 &rhs) const {
return Vector2(x + rhs.x, y + rhs.y);
}
Vector2 operator-(const Vector2 &rhs) const {
return Vector2(x - rhs.x, y - rhs.y);
}
Vector2 &operator+=(const Vector2 &rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
Vector2 &operator-=(const Vector2 &rhs) {
x -= rhs.x;
y -= rhs.y;
return *this;
}
private:
T x, y;
};
int main() {
Vector2<float> v1(2, 3);
Vector2<double> v2(4, 5);
}
Tip)
Mac의 경우 vscode에서 커맨드 + Shift + L을 통해서 한 번에 바꾸기를 할 수 있다.
- 윈도우의 경우 아마 ctrl + h로 가능했던 것 같다.
Vector2
와 Vector2 은 다른 타입이다. - Vector2 자체는 클래스가 아니라 클래스를 만들어내는 템플릿이기 때문
Vector2
는 8바이트 크기를 갖는다는 것을 알 수 있고, Vector2 은 16바이트 크기를 갖는다는 것을 알 수 있다. 주의! 클래스 템플릿에서는 템플릿 인수를 생략할 수 없다.
질문) Vector2는 클래스가 아니라 클래스 템플릿인데, 연산자 오버로딩에서 클래스를 어떻게 다루는거죠?
template <typename T>
class Vector2 {
public:
/* 생략 */
Vector2 operator+(const Vector2 &rhs) const {
return Vector2(x + rhs.x, y + rhs.y);
}
Vector2 operator-(const Vector2 &rhs) const {
return Vector2(x - rhs.x, y - rhs.y);
}
Vector2 &operator+=(const Vector2 &rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
Vector2 &operator-=(const Vector2 &rhs) {
x -= rhs.x;
y -= rhs.y;
return *this;
}
private:
T x, y;
};
여기서 Vector2를 클래스 템플릿이 아니라 클래스인 것 마냥 사용하고 있다.
이게 어떻게 가능하냐?
Vector2
에서 가 생략되어 있는 것이다.
Vector2<T> operator+(const Vector2<T> &rhs) const {
return Vector2(x + rhs.x, y + rhs.y);
}
- 그리고 템플릿 인자를 2개 이상도 사용할 수 있다.
template <typename T, typename U>
void f(T a, U b) {}
참조) 두들낙서 C/C++ 강좌
'Programming Language > C++' 카테고리의 다른 글
[C++] 예외 처리(Exception handling) (0) | 2024.06.28 |
---|---|
[C++] 템플릿 특수화와 비타입 파라미터 (0) | 2024.06.28 |
[C++] 정사각형-직사각형 문제 (0) | 2024.06.28 |
[C++] 다중 상속과 다이아몬드 문제 (0) | 2024.06.28 |
[C++] 객체 지향 프로그래밍의 4대 원칙(원리) (0) | 2024.06.28 |