공부/C++

12. 템플릿과 표준 템플릿 라이브러리(STL)

bokob 2024. 6. 6. 17:00

12.1 클래스 템플릿

템플릿

함수 템플릿, 클래스 템플릿

  • C++의 템플릿을 이용하면 함수나 클래스를 정의할 때 특정 데이터 형을 사용하는 대신 범용형을 사용할 수 있다.
  • 함수 템플릿이나 클래스 템플릿은 여러가지 데이터 형에 대해서 함수 정의나 클래스 정의를 생성할 수 있다.

 

템플릿 장점과 제네릭 프로그래밍

템플릿 장점

  • 함수 코드의 재사용
    • 높은 소프트웨어의 생산성과 유용성

템플릿 단점

  • 포팅에 취약
    • 컴파일러에 따라 지원하지 않을 수 있음
  • 컴파일 오류 메시지 빈약, 디버깅에 많은 어려움

제네릭 프로그래밍

  • generic programming
    • 일반화 프로그래밍이라고도 부름
    • 제네릭 함수나 제네릭 클래스를 활용하는 프로그래밍 기법
    • C++에서 STL(Standard Template Library) 제공, 활용
  • 보편화 추세
    • Java, C# 등 많은 언어에서 활용

 

클래스 템플릿의 선언

제네릭 클래스 만들기

template <class 범용형이름, ...> // template <typename 범용형이름, ...>
class 클래스이름
{
    // 멤버 변수 정의
    // 멤버 함수 정의
};

 

클래스 템플릿 멤버 함수 외부 정의

template <class T=int> // 아무것도 지정 안하면 int로 된다.
class Stack{
protected:
    int m_size;
    int m_top;
    T *m_buffer;

public:
    Stack(int size = sz);
    ~Stack();
    void Push(T value);
    T Pop();
    // ...
};

template <class T>
void Stack<T>::Push(T Value){
    // ...
}
template <class T>
T Stack<T>::Pop(){
    // ...
}
// ...

int main(){
    Stack<int> iStack; // int 타입을 다루는 스택 객체 생성
    Stack<double> dStack; // double 타입을 다루는 스택 객체 생성
    
    iStack.Push(3);
    int n = iStack.Pop();
    
    dStack.Push(3.5);
    double d = dStack.Pop();
}

 

클래스 템플릿 사용 예

개발자가 만든 코드

template<class A, class B, int MAX>
class TwoArray{
    // 생략
    A arr1[MAX];
    B arr2[MAX];
};

TwoArray<char, double, 20> arr;

컴파일러에 의해 생성된 클래스

// char, double, 20 을 템플릿 파라미터로 주었을 때 가정하고 클래스 만든 것
class TwoArray_char_double_20{ 
    // 중간 생략
    char arr1[20];
    char arr2[20];
};

 

클래스 템플릿의 인스턴스화

항상 명시적으로 지정

  • 객체를 생성할 때, 템플릿의 파라미터 지정
Stack<int> s1(10);    // T는 int 형
Stack<string> s2(5);  // T는 string 형
  • 객체를 생성하지는 않지만 객체에 대한 포인터나 레퍼런스를 정의하면서 템플릿의 파라미터 지정
Stack<char> *pStack = NULL; // T는 char 형
  • typedef 문으로 클래스 템플릿의 별명을 정의하면서 템플릿의 파라미터 지정
typedef Stack<Point> PointStack; // T는 Point 형
PointStack s4(20);               // Stack<Point> 클래스의 객체 생성

 

클래스 템플릿의 사용

어디에서든지 사용 가능

void f1(Stack<int>& s);                      // 함수 인자로 사용
Stack<double>* f2();                         // 함수 리턴형으로 사용

class X{
protected:
    Stack<string> m_strStack;                // 멤버 객체의 데이터 형으로 사용
    // ...
};

class MyStack : public Stack<int>{ // ... }; // 다른 클래스의 기본 클래스로 사용

template<typename T>
class Array{
protected:
    T arr[3];
    // ...
};
Array<Stack<int>> arra;                      // 다른 클래스 템플릿 파라미터로 사용

 

템플릿의 특징

실제로 사용되기 전까지는 코드가 생성되지 않음

  • 클래스 템플릿을 인스턴스화 하는 코드를 작성하면, 컴파일러가 해당 문장을 컴파일할 때 클래스의 코드를 생성함.
  • 컴파일 시간이 오래 걸리는 단점이 있다. (하지만 크게 문제가 될 정도는 아님)

코드 크기를 최소화 함

  • 미리 코드를 만들어두는 것이 아니라, 프로그램에 실제로 사용되는 클래스나 함수에 대해서만 코드가 생성됨

함수 템플릿의 정의나, 클래스 템플릿의 멤버 함수의 정의는 헤더 파일에 놓여야 함

  • 컴파일 시간에 클래스나 함수를 만들어내기 위해서는 헤더 파일에 위치할 필요가 있다.(컴파일 시간에는 다른 구현 파일의 내용을 확인할 수 없다.)

 

2개의 제네릭 타입을 가진 클래스 만들기

#include <iostream>
using namespace std;

template <class T1, class T2> // 2개의 제네릭 타입 선언
class GClass{
    T1 data1;
    T2 data2;
public:
    GClass();
    void set(T1 a, T2, b);
    void get(T1 &a, T2 &b);
};

template<class T1, class T2>
GClass<T1, T2>::GClass(){
    data1 = 0, data2 = 0;
}

template<class T1, class T2>
void GClass<T1, T2>::set(T1 a, T2 b){
    data1 = a; data2 = b;
}

template<class T1, class T2>
void GClass<T1, T2>::get(T1 &a, T2 &b){
    a = data1; b = data2;
}

int main(){
    int a;
    double b;
    GClass<int, double> x;
    x.set(2, 0.5);
    x.get(a, b);
    cout<<"a="<<a<<'\t'<<"b="<<b<<endl;
    
    char c;
    float d;
    GClass<char, float> y;
    y.set('m', 12.5);
    y.get(c, d);
    cout<<"c="<<c<<'\t'<<"d="<<d<<endl;
}

/*
a=2	b=0.5
c=m	d=12.5
*/

 

12.2 STL

C++ 표준 템플릿 라이브러리, STL

STL = Standard Template Library

  • 표준 템플릿 라이브러리
    • C++ 표준 라이브러리 중 하나
    • 대부분의 C++ 컴파일러는 STL을 지원
  • 성능이 우수하고 안전성이 검증된 라이브러리
  • 많은 템플릿 클래스(제네릭 클래스)와 템플릿 함수(제네릭 함수) 포함
    • 개발자는 이들을 이용하여 쉽게 응용 프로그램 작성
    • 기간도 단축하고 최소한의 노력만으로 원하는 기능을 구현 가능

STL의 구성

STL의 구성

  • 컨테이너 - 템플릿 클래스
    • 데이터를 담아두는 자료 구조를 표현한 클래스
    • 리스트, 큐, 스택, 맵, 셋, 벡터
  • iterator - 컨테이너 원소에 대한 포인터
    • 컨테이너의 원소들을 순회하면서 접근하기 위해 만들어진 컨테이너 원소에 대한 포인터
  • 알고리즘 - 템플릿 함수
    • 컨테이너 원소에 대한 복사, 검색, 삭제, 정렬 등의 기능을 구현한 템플릿 함수
    • 컨테이너의 멤버 함수 아님

 

STL 컨테이너

컨테이너 클래스 설명 헤더 파일
vector 동적 크기의 배열을 일반화한 클래스 <vector>
deque 앞뒤 모두 입력 가능한 큐 클래스 <deque>
list 빠른 삽입/삭제 가능한 리스트 클래스 <list>
set 정렬된 순서로 값을 저장하는 집합 클래스, 값은 유일 <set>
map (key, value) 쌍으로 값을 저장하는 맵 클래스 <map>
stack 스택을 일반화한 클래스 <stack>
queue 큐를 일반화한 클래스 <queue>

 

STL과 관련된 헤더 파일고 이름 공간

헤더파일

  • 컨테이너 클래스를 사용하기 위한 헤더 파일
    • 해당 클래스가 선언된 헤더 파일 include 예) vector 클래스: #include <vector>
  • 알고리즘 함수를 사용하기 위한 헤더 파일
    • 알고리즘 함수에 상관 없이 #include <algorithm>

이름 공간

  • STL이 선언된 이름 공간은  std

 

iterator 사용

iterator란?

  • 반복자라고도 부름
  • 컨테이너의 원소를 가리키는 포인터

iterator 변수 선언

  • 구체적인 컨테이너를 지정하여 반복자 변수 생성

 

  • begin()과 end() 함수
    • STL 컨테이너가 공통으로 제공하는 함수
    • begin() 함수: 첫 번째 원소를 가리키는 iterator 리턴
    • end() 함수: 마지막 원소를 가리키는 iterator 리턴

 

iterator 사용 에

#include <iostream>
#include <vector>
using namespace std;

int main(){
    vector<int> v; // 정수 벡터 생성
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    
    vector<int>::iterator it; // 벡터 v의 원소에 대한 포인터 it 선언
    
    for(it = v.begin(); it!=v.end(); it++){ // iterator를 이용하여 모든 원소 탐색
        int n = *it; // it가 가리키는 원소 값 리턴
        n = n*2;
        *it = n; // it가 가리키는 원소에 값 쓰기
    }
    
    for(it = v.begin(); it!=v.end(); it++)
        cout<<*it<<' ';
    cout<<endl;
}

/*
2 4 6
*/

 

map 사용하기

특징

  • (키, 값) 쌍을 원소로 저장하는 제네릭 컨테이너
  • 동일한 키를 가진 원소가 중복 저장되면 오류 발생
  • '키'로 '값' 검색
  • 많은 응용에서 필요함
  • #include <map> 필요

맵 컨테이너 생성 예

map<string, string> dc;

// 원소 저장
dic.insert(make_pair("love", "사랑"));
dic["love"] = "사랑";

// 원소 검색
string kor = dic["love"];
string kor = dic.at("love");

 

map 클래스의 주요 멤버와 연산자

멤버와 연산자 함수 설명
insert(pair<> &element) 맵에 '키'와 '값;으로 구성된 pair 객체 element 삽입
at(key_type& key) 맵에서 '키' 값에 해당하는 '값' 리턴
begin() 맵의 첫 번째 원소에 대한 참조 리턴
end() 맵의 끝(마지막 원소 다음)을 가리키는 참조 리턴
empty() 맵이 비어 있으면 true 리턴
find(key_type& key) 맵에서 '키' 값에 해당하는 원소를 가리키는 iterator 리턴
erase(iterator it) 맵에서 it가 가리키는 원소 삭제
size() 맵에 들어 있는 원소의 개수 리턴
operator[key_type& key]() 맵에서 '키' 값에 해당하는 원소를 찾아 '값' 리턴
operator=() 맵 치환(복사)

 

STL 컨테이너의 장점

STL 컨테이너의 종류가 다르더라도 각 컨테이너가 제공하는 인터페이스는 동일

  • list 컨테이너에서 사용했던 push_back, back, begin, end 등의 멤버 함수는 list, vector, deque 등의 다른 STL 컨테이너에서도 동일하게 제공됨
#include <vector>
vector<int> list1; // vector로 변경해도 프로그램의 나머지 부분은 그대로 동작

최소한의 코드 수정으로 컨테이너를 다른 것으로 바꿀 수 있음

=> 프로그램의 개발이나 유지 보수 시간을 단축하는 데 큰 도움이 됨

 

STL 알고리즘

STL이 제공하는 범용 함수

  • 템플릿 함수
  • 전역 함수
    • STL 컨테이너 클래스의 멤버 함수가 아님
  • iterator와 함께 작동
copy merge random rotate
equal min remove search
find move replace sort
max partition reverse swap

sort() 함수 사례

  • 두 개의 매개 변수
    • 첫 번째 매개 변수: 소팅을 시작한 원소의 주소
    • 두 번째 매개 변수: 소팅 범위의 마지막 원소 다음 주소
vector<int> v;
// ...
sort(v.begin(), v.begin()+3);   // v.begin()에서 v.begin()+2까지, 처음 3개 원소 정렬
sort(v.begin()+2, v.begin()+5); // 벡터 3번째 원소에서 v.begin()+4까지, 3개 원소 정렬
sort(v.begin(), v.end());       // 벡터 전체 정렬

 

auto를 이용하여 쉬운 변수 선언

C++에서 auto

  • 기능
    • C++ 11 부터 auto 선언의 의미 수정: 컴파일러에게 변수선언문에서 추론하여 타입을 자동 선언하도록 지시
    • C++ 11 이전까지는 스택에 할당되는 지역 변수를 선언하는 키워드
  • 장점
    • 복잡한 변수 선언을 간소하게, 긴 타입 선언 시 오타 줄임

auto의 기본 사용 사례

auto pi = 3.14; // 3.14가 실수이므로 pi는 double 타입으로 선언됨
auto n = 3;     // 3이 정수이므로 n을 int 타입으로
auto *p = &n;   // 변수 p는 int* 타입으로 추론

int n = 10;
int& ref = n;    // ref는 int에 대한 참조 변수
auto ref2 = ref; // ref2는 int& 변수로 자동 선언

 

auto의 다른 활용 사례

다른 활용 사례

  • 함수의 리턴 타입으로부터 추론하여 변수 타입 선언
int square(int x) {return x*x;}
// ...
auto ret = square(3); // 변수 ret는 int 타입으로 추론
  • STL 템플릿에 활용
    • vector<int> iterator 타입의 변수 it를 auto를 이용하여 간단히 선언
vector<int>::iterator it;
for(it = v.begin(); it!=v.end(); it++)
    cout<<*it<<endl;

for(auto it = v.begin(); it!=v.end(); it++)
    cout<<*it<<endl;

 

 

제네릭(generic) 프로그래밍

제네릭 프로그래밍

  • 정보의 타입과 정보를 처리하는 알고리즘을 분리하여 서로 독립적으로 관리할 수 있는 프로그래밍 방식
    • 컨테이너: '정보의 타입'
    • sort() 함수를 비롯한 알고리즘 함수: '정보를 처리하는 알고리즘'

제네릭 프로그래밍의 목표

  • 정보의 타입과 관계없이 동일한 방법으로 처리할 수 있는 알고리즘을 구현하는 것

 

제네릭 프로그래밍의 장점

타입과 알고리즘간의 불필요한 연관성 제거(decoupling)

  • vector 클래스의 세부 구현을 변경했다고 해도  sort() 함수는 영향을 받지 않음

재사용성(reusability) 증가

확장의 용이성

  • 공통적인 인터페이스: begin(), end() 등
  • 새로운 컨테이너 클래스를 만들 때 이 공통의 인터페이스를 유지한다면 STL의 모든 알고리즘을 사용할 수 있다.

 

-- -종강 ---

'공부 > C++' 카테고리의 다른 글

11. 가상함수와 추상클래스  (0) 2024.06.02
10. 상속  (0) 2024.05.27
9. friend와 연산자 중복  (0) 2024.05.16
8. 함수와 참조, 복사생성자  (1) 2024.05.14
7. 여러가지 객체의 생성방법  (0) 2024.05.14