소연의_개발일지
Published 2024. 4. 9. 14:11
[C++] 템플릿 공부하기 개발공부/C++

템플릿에 대해 더 자세히 공부해보기로 함

 

참고 사이트

모드의 코드 - 씹어먹는 C++

https://modoocode.com/219

 

씹어먹는 C++ - <9 - 1. 코드를 찍어내는 틀 - C++ 템플릿(template)>

모두의 코드 씹어먹는 C++ - <9 - 1. 코드를 찍어내는 틀 - C++ 템플릿(template)> 작성일 : 2017-04-07 이 글은 79760 번 읽혔습니다. 에 대해서 배웁니다. 안녕하세요 여러분! 지난번 강좌 생각해보기는 잘

modoocode.com

 


 

템플릿이란?

우리가 함수를 사용하는 이유는 동일로직을 여러번 반복할 때, 그 반복을 줄이기 위해 생성한다.

 

함수 사용 예시)

1, 2를 입력하면 12 출력

std::string concatenateNumbers(int num1, int num2) {
    // Convert integers to strings
    std::string strNum1 = std::to_string(num1);
    std::string strNum2 = std::to_string(num2);

    // Concatenate strings
    return strNum1 + strNum2;
}

 

그런데 위 예시는 int 형 숫자 2개만 받는다. 

string 형이나 double 형을 위해서는 똑같은 함수를 만들어야 한다.

string형으로 변환 예시)

std::string concatenateStrings(string str1, string str2) {
    std::string strTemp1 = str1;
    std::string strTemp2 = str2

    // Concatenate strings
    return str1 + str2;
}

 

같은 형식인데 변수만 다르다. --> 매우 비효율적이고 낭비이다!!

만약 파라미터를 넘겨주는 부분에서 컴파일러가 원하는 타입으로 채워서 코드를 생성하면 엄청 편할 것이다.

 

그래서 템플릿(Template)이 등장했다. 

템플릿은 프로그래머가 원하는 타입을 넣어주면 알아서 코드를 찍어내는 틀이라고 생각하면 된다.

 

모두의 코드에서 나온 vector 템플릿 코드 예시이다. 

내 비쥬얼 스튜디오는 버전이낮은지 맞지 않는지 c_str()를 사용하니 문자열이 잘 출력되었다.

*c_str() : C 스타일 문자열(const char)로 변환하는 데 사용

// ConsoleStudyTemplete.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
// 템플릿 첫 활용
#include "stdafx.h"
#include <iostream>
#include <string.h>

template <typename T>
class Vector {
	T* data;
	int capacity;
	int length;

public:
	// 생성자
	Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}

	// 맨 뒤에 새로운 원소를 추가한다.
	void push_back(T s) {
		if (capacity <= length) {
			T* temp = new T[capacity * 2];
			for (int i = 0; i < length; i++) {
				temp[i] = data[i];
			}
			delete[] data;
			data = temp;
			capacity *= 2;
		}

		data[length] = s;
		length++;
	}

	// 임의의 위치의 원소에 접근한다.
	T operator[](int i) { return data[i]; }

	// x 번째 위치한 원소를 제거한다.
	void remove(int x) {
		for (int i = x + 1; i < length; i++) {
			data[i - 1] = data[i];
		}
		length--;
	}

	// 현재 벡터의 크기를 구한다.
	int size() { return length; }

	~Vector() {
		if (data) {
			delete[] data;
		}
	}
};

int main() {
	// int 를 보관하는 벡터를 만든다.
	Vector<int> int_vec;
	int_vec.push_back(3);
	int_vec.push_back(2);

	std::cout << "-------- int vector ----------" << std::endl;
	std::cout << "첫번째 원소 : " << int_vec[0] << std::endl;
	std::cout << "두번째 원소 : " << int_vec[1] << std::endl;

	Vector<std::string> str_vec;
	str_vec.push_back("hello");
	str_vec.push_back("world");
	std::cout << "-------- std::string vector -------" << std::endl;
	std::cout << "첫번째 원소 : " << str_vec[0].c_str() << std::endl;
	std::cout << "두번째 원소 : " << str_vec[1].c_str() << std::endl;
}

 

 

출력하면 아래와 같이 나온다.

-------- int vector ----------
첫번째 원소 : 3
두번째 원소 : 2
-------- std::string vector -------
첫번째 원소 : hello
두번째 원소 : world
계속하려면 아무 키나 누르십시오 . . .

 

 

1. 템플릿 정의

template <typename T>

 

이 template 아래에 정의되는 클래스에 대해 템플릿을 정의하고, 템플릿 인자로 T를 받게 되며, T는 반드시 어떠한 타입의 이름임을 명시하고 있다. 위 경우 템플릿 문장 아래 오는것이 class Vector 이므로 이 Vector 클래스에 대한 템플릿을 명시하는데, 만약에 밑에 오는 것이 함수일 경우 함수에 대한 템플릿이 된다.

-> 템플릿은 클래스, 함수 모두 사용할 수 있다. 

 

template <class T>

위의 경우도 typename T와 동일하게 이해하면 된다. 하지만 되도록이면 typename 키워드를 사용하기를 권장한다.

 

 

2. 인자 전달

정의한 템플릿의 인자에 값을 전달하기 위해서는 <> 안에 전달하려는 형식을 명시해주면 된다. 

Vector<int> int_vec;

int 면 <int>, string이면 <string>을 명시하여 타입 자체를 전달한다.

 

위와 같이 Vector의 템플릿의 인자에 타입을 전달하게 되면, 컴파일러는 이것을 보고 실제 코드를 생성하게 된다.

따라서 위 코드는 Vector의 T가 int로 치환된 클래스의 객체 int_vec를 생성하게 되는 것이다. 

이렇게 클래스 템플릿에 인자를 전달하여 실제 코드를 생성하는 것을 클래스 템플릿 인스턴스화(class template instantiation) 라고 한다.

 

 

템플릿 특수화 

템플릿에 사용자가 원하는 타입을 템플릿 인자로 전달하면, 컴파일러는 그 인자를 바탕으로 코드를 생성하여 컴파일하게 된다. 하지만, 간혹 일부 타입에 대해서는 다른 방식으로 처리해야 한다.

 

예를들어 bool 타입 보관하는 벡터)

Vector<bool> int_vec;

 

C++에서는 기본으로 처리하는 단위가 1byte이다.

다시 말해 bool 데이터형은 1개 bit 만으로 충분히 저장할 수 있지만, 8개 bit를 사용해서 1개 bool 을 저장해야 한다는 점이다. (8bit = 1byte) 

이는 메모리 낭비이기 때문에, 특별히 따로 처리가 필요하다. 

 

이와 같이 일부 경우에 대해 따로 처리하는 것을 템플릿 특수화 라고 한다.

템플릿 특수화는 다음과 같이 수행할 수 있다.

template <typename A, typename B, typename C>
class test {};

A가 int이고, C가 double 일 때 따로 처리하고 싶다면

template <typename B>
class test<int, B, double> {};

로 작성하고 위는 templat을 작성해 준다. 

template <>
class test<int, int double> {};

B도 int로 특수화 하고 싶은 경우에는 위와 같이\분에 원하는 타입을 전달하면 된다.

 

함수 템플릿

모두의 코드 함수 예시

#include <iostream>
#include <string>

template <typename T>
T max(T& a, T& b) {
	return a > b ? a : b;
}


int main() {
	int a = 1, b = 2;
	std::cout << "Max (" << a << "," << b << ") ? : " << max(a, b) << std::endl;
	
	std::string s = "hello", t = "world";
	std::cout << "Max (" << s << "," << t << ") ? : " << max(s, t) << std::endl;
}

 

 

출력

Max (1,2) ? : 2
Max (hello,world) ? : world
계속하려면 아무 키나 누르십시오 . . .

 

클래스는 호출할 때 <> 부분이 없다. 

c++ 컴파일러가 알아서 max(a, b)를 max<int>(a, b)로 인스턴스화 해 준다. 

더보기
#include "stdafx.h"
#include <iostream>
#include <string.h>

template <typename T>
class Vector {
	T* data;
	int capacity;
	int length;

public:
	// 어떤 타입을 보관하는지
	typedef T value_type;

	// 생성자
	Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}

	// 맨 뒤에 새로운 원소를 추가한다.
	void push_back(int s) {
		if (capacity <= length) {
			int* temp = new T[capacity * 2];
			for (int i = 0; i < length; i++) {
				temp[i] = data[i];
			}
			delete[] data;
			data = temp;
			capacity *= 2;
		}

		data[length] = s;
		length++;
	}

	// 임의의 위치의 원소에 접근한다.
	T operator[](int i) { return data[i]; }

	// x 번째 위치한 원소를 제거한다.
	void remove(int x) {
		for (int i = x + 1; i < length; i++) {
			data[i - 1] = data[i];
		}
		length--;
	}

	// 현재 벡터의 크기를 구한다.
	int size() { return length; }

	// i 번째 원소와 j 번째 원소 위치를 바꾼다.
	void swap(int i, int j) {
		T temp = data[i];
		data[i] = data[j];
		data[j] = temp;
	}

	~Vector() {
		if (data) {
			delete[] data;
		}
	}
};

template <typename Cont>
void bubble_sort(Cont& cont) {
	for (int i = 0; i < cont.size(); i++) {
		for (int j = i + 1; j < cont.size(); j++) {
			if (cont[i] > cont[j]) {
				cont.swap(i, j);
			}
		}
	}
}

template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp) {
	for (int i = 0; i < cont.size(); i++) {
		for (int j = i + 1; j < cont.size(); j++) {
			if (!comp(cont[i], cont[j])) {
				cont.swap(i, j);
			}
		}
	}
}

struct Comp1 {
	bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
	bool operator()(int a, int b) { return a < b; }
};

int main() {
	Vector<int> int_vec;
	int_vec.push_back(3);
	int_vec.push_back(1);
	int_vec.push_back(2);
	int_vec.push_back(8);
	int_vec.push_back(5);
	int_vec.push_back(3);

	std::cout << "정렬 이전 ---- " << std::endl;
	for (int i = 0; i < int_vec.size(); i++) {
		std::cout << int_vec[i] << " ";
	}

	Comp1 comp1;
	bubble_sort(int_vec, comp1);

	std::cout << std::endl << std::endl << "내림차순 정렬 이후 ---- " << std::endl;
	for (int i = 0; i < int_vec.size(); i++) {
		std::cout << int_vec[i] << " ";
	}
	std::cout << std::endl;

	Comp2 comp2;
	bubble_sort(int_vec, comp2);

	std::cout << std::endl << "오름차순 정렬 이후 ---- " << std::endl;
	for (int i = 0; i < int_vec.size(); i++) {
		std::cout << int_vec[i] << " ";
	}
	std::cout << std::endl;
}

 

타입이 아닌 템플릿 인자

 

#include "stdafx.h"
#include <iostream>
#include <string.h>

template <typename T, int num>
T add_num(T t) {
	return t + num
}

int main() {
	int x = 3;
	std::cout << "x : " << add_num<int, 5>(x) << std::endl;
}

 템플릿 인자로 타입만 받을 수 있는게 아니라  위 코드처럼 int형을 받을 수 있다.

<> 안에 인자를 정의해주어야 한다.

 

템플릿 안에 전달할 수 있는 타입
- 정수 타입들(bool, char, int, long...) 
- 포인터 타입
- enum 타입
- std::nullptr_t (널포인터)

 

 

std:: arrary를 사용하면 함수로 전달할 때 배열의 크기를 잃어버리지 않고 전달할 수 있다.

#include <array>

template <typename T, int num>
T add_num(T t) {
	return t + num
}

int main() {
	// C에서의 배열처럼 {}을 통해 배열을 정의할 수 있다.
	std::array<int, 5> arr = { 1, 2, 3, 4, 5 };
	// int arr[5] = {1, 2, 3, 4, 5};와 동일

	for (int i = 0; i < arr.size(); i++) {
		std::cout << arr[i] << "";
	}
	std::cout << std::endl;
}

{} 는 유니폼 초기화라 불리고, c++11에서 추가된 개념이다.

 

arr은 런타임에서 동적으로 크기가 할당되지 않고 배열처럼 int 5개를 가져가는 스택에 할당된다.

 

디폴트 템플릿 인자

함수에 디폴트 인자를 지정할 수 있는 것처럼 템플릿도 디폴트 인자를 지정할 수 있다.

아래와 같이 int num = 5 를 해 주면 

num을 작성하지 않아도 기본값으로 5가 전달된다.

#include <iostream>

template <typename T, int num = 5>
T add_num(T t) {
  return t + num;
}

int main() {
  int x = 3;
  std::cout << "x : " << add_num(x) << std::endl;
}
profile

소연의_개발일지

@ssoyxon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!