프로그래밍 이야기

A Tour of C++ : 6장 템플릿

원소랑 2019. 10. 20. 02:58

.

.

책 읽으면서 정리한 메모


6. 템플릿

파라미터화된 타입 : 제한된 템플릿 인자, 값 템플릿 인자, 템플릿 인자 추론

파라미터화된 연산 : 함수 템플릿, 함수 객체, 람다 표현식

템플릿 메커니즘 : 가변 템플릿, 별칭, 컴파일 시간 if

6.1 소개

템플릿은 타입이나 값의 집합을 파라미터화한 클래스나 함수.

6.2 파라미터화된 타입

template<typename T> 는 template<class T> 와 동일.

“모든 타입 T에 대해”라는 C++ 표현.

template<typename T>

class Vector {

// ...

}

Vector<char> vc(200); // 문자 200개를 포함하는 벡터

Vector<string> vs(17); // 문자열 17개

Vector<list<int>> vli(45); // 정수의 리스트 45개

템플릿+인자 = 인스턴스화 or 특수화

컴파일 후반부에 인스턴스화 시간 (instantiation time) 에 코드 생성. 타입 안전성을 보장하나, 컴파일 과정의 후반부에 수행됨.

6.2.1 제한된 템플릿 인자 (C++20)

template<Element T>

class Vector {

...

}

수학적으로 ‘Element(T) 를 만족하는 모든 T에 대해”라는 개념의 C++ 표현.

Element = Vector에서 요구하는 성질 T를 만족하는지 확인하는 술어

= 컨셉 = 제한된 인자 = 제한된 템플릿.

// Vector 가 복사 연산자를 가질 때

Vector<int> v1; // 정상 : int 는 복사할 수 있음

Vector<thread> v2; // 에러 : thread 는 복사 불가

컨셉은 C++ 20 부터 지원.

6.2.2 값 템플릿 인자

템플릿 타입 인자에 값 인자(Value arguments)도 받을수 있다.

template<typename T, int N>

struct Buffer {

//...

T a[N];

}

Buffer<char, 1024> blob;

6.2.3 템플릿 인자 추론

표준 pair 를 예로

pair<int, double> p = {1, 5.2};

일일이 타입지정 안 해도 됨

auto p = make_pair(1, 5.2);

생성자 인자로 템플릿 파라미터 추론 가능. C++17

pair p = {1, 5.2}; // p는 pair<int, double>

코드가 단순해지지만 유의 필요.

Vector<string> vs1 = { ”Hello”, “World” }; // Vector<string> 명확

Vector vs { ”Hello”, “World” }; // Vector<const char*> 로 추론. (실수일까?)

Vector vs2 { ”Hello”s, “World”s }; // Vector<string> 으로 추론

Vector vs3 { ”Hello”s, “World” }; // 에러. 타입이 다름. string, const char*

“문자열” 의 기본 타입은 const char* 이다. 접미사 s 를 붙여 string 으로 만들어야함.

인자 추론이 어렵다면, 생성자에 추론 가이드(deduction guide) 를 제공할 수 있다.

but, 추론 가이드는 미묘한 면이 있으므로 추론 가이드를 사용할 필요가 없게 클래스 템플릿을 설계하는 것이 가장 좋다.

6.3 파라미터화된 연산

타입과 알고리즘 모두 파라미터화할 때 사용.

- 함수 템플릿

- 함수 객체 : 데이터를 포함하면서 함수처럼 호출 가능한 객체

- 람다 표현식(lambda expression) : 함수 객체를 간단하게 표기하는 방법

6.3.1 함수 템플릿

for 구분으로 모든 시퀀스 합 구하기

template<typename Sequence, typename Value>

Value sum(const Sequence& s, Value v)

{

for (auto x : s)

v += x;

return v;

}

단, 함수 템플릿은 멤버가 될 수 있지만, virtual 멤버 될 수 없다. 컴파일러가 vtbl 을 만들지 못함.

6.3.2 함수 객체

함수처럼 호출할 수 있는 객체 정의.

template<typename T>

class Less_than {

const T val; // 비교 대상

public:

Less_than(const T& v) : val{v} {}

bool operator()(const T& x) const { return x<val; } // 연산자 호출

};

operator() 로 () 연산자 구현.

Less_than lti {43}; // x 를 42에 비교

Less_than lts {“Backus”s };

Less_than<string> lts2 {“Naur”};

객체를 그냥 함수처럼 호출

int n = 10;

string s = “Test”;

bool b1 = lti(n);

bool b2 = lts(s);

함수 객체는 알고리즘 인자로도 널리 사용됨. 특정 술어에 대해 true 요소 개수 알기

template<typename C, typename P> // Sequence<C>, Callable<P, Value_type<P>>

int count(const C& c, P pred)

{

int cnt = 0;

for (const auto& x : c)

if (pred(x))

++cnt;

return cnt;

}

count 에서 Less_than 을 사용한 것처럼, 알고리즘에서 핵심 동작의 의미를 지정하지 위해 사용한 함수 객체를 “정책 객체”라고 함. Policy Objects

6.3.3 람다 표현식

알고리즘과 별개로 Less_than 객체를 만들었는데, 불편할 수 있고, 암묵적으로 만들 수 있음.

cout<< count( vec, [&]( int a ){ return a<x; } )

cout<< count( lst, [&]( const string& a ){ return a<s; } )

[&]( int a ) { return a<x; } 같은 표기법을 람다 표현식이라고 함. Less_than<int>{x} 와 유사한 함수 객체를 생성. [&]는 캡처 리스트.람함다 몸체 안에서 사용하는 (x와 같은)모든 지역 이름이 참조로 접근됨을 명시. 복사는 [=]. x만 캡처하려면 [&x]. x를 복사한 객체를 만들고 싶다면 [=x]. []는 아무것도 캡처하지 않음.

사용은 간편하나 모호함 유발. 표현식이 간단하지 않으면 연산에 이름을 부여해서 목적을 명확히 하고 여러 곳에서 사용할 수 있도록.

draw_all(), rorate_all() 처럼 포인터나 unique_ptr 을 포함하는 vector(Container) 요소마다 어떤 연산 적용하는 함수를 여러 벌 만들기 귀찮을 때, 이럴 때 함수 객체(특히 람다)를 사용, 컨테이너 순회 작업과 각 요소로 어떤 일을 할지 분리할 수 있다.

먼저, 포인터를 포함하는 컨테이너의 각 요소가 가리키는 객체에 특정 연산을 적용하는 함수가 필요.

template<typename C, typename Oper>

void for_all( C& c, Oper op )

{

for (auto& x : c)

op( x );

}

이제 일련의 _all() 시리즈 대신 위 함수를 활용.

void user2()

{

vector<unique_ptr<Shape>> v;

while (cin)

v.push_back( read_shape(cin) );

for_all( v, [](unique_ptr<Shape>& ps){ ps->draw(); }); // draw_all()

for_all( v, [](unique_ptr<Shape>& ps){ ps->rotate(45); }); // rotate_all(45)

}

람다도 함수처럼 제네릭할 수 있다.

template<class S>

void rotate_end_draw( vector<S>& v, int r)

{

for_all( v, [](auto& s){ s->rotate(r); s->draw(); } );

}

auto 파라미터를 이용하면 람다를 템플릿으로 만들 수 있고, 이를 일컬어 제네릭 람다(Generic lambda)

표준위원회의 정치적 이유 때문인지 함수 인자에서 auto 사용은 아직 허용되지 않는다.

람다를 이용하면 어떤 구문이든 표현식을 만들 수 있는데, 주로 인자값을 바탕으로 어떤 값을 계산하는 연산 제공하는 데 주로 쓰임. 용도는 광범위.

복잡한 초기화도.

switch ( mode )

case : v = val; break;

case : v = val; break;

자료 구조를 초기화하기 위해 여러 대안 중 하나를 선택, 각 대안마다 연산을 해야하는 경우. ‘효율성’이란 미명하에 지저분한 코드와 버그의 원인을 만들어냄.

- 의도된 값을 얻기 전에 변수가 사용될 수 있다.

- 초기화 코드가 다른 코드와 섞이면 이해하기 어렵다.

- 초기화 코드가 다른 코드와 섞이면 각 대안에 대한 case를 잊어버리기 쉽다.

- 초기화가 아닌 대입에 가깝다.

이 코드 대신 람다를 이용할 수 있다.

vector<int> v = [&] {

switch( m )

case zero:

return vector<int>(n); // n개 요소를 0으로 초기화

case seq:

return vector<int>{p,q}; // 시퀀스 [p:q]에서 복사

case cpy:

return arg;

}

};

6.4 템플릿 메커니즘

좋은 템플릿 정의를 위해 필요한 언어 기능

- 타입에 의존적인 값 : 가변 템플릿 variable templates

- 타입과 템플릿의 별칭 : 별칭 템플릿 alias templates

- 컴파일 시간 선택 메커니즘 : if constexpr

- 타입과 표현식의 속성을 질의할 수 있는 컴파일 시간 메커니즘 : requires 표현식

constexpr함수, static_asserts도 템플릿 설계와 활용에 종종 쓰임. 이런 기본 메커니즘들은 일반적, 기본적 추상화 도구로 사용

6.4.1 가변 템플릿

특정 타입 사용할 때 상수, 변수가 필요하듯 템플릿도 마찬가지.

template <class T>

constexpr T viscosity = 0.4; // 템플릿 상수

template <class T>

constexpr space_vector<T> external_acceleration = { T{}, T{-9.8}, T{} }; // space_vector 는 3차원 벡터

auto vis2 = 2*viscosity<double>;

auto acc = external_acceleration<float>; // float 3개 벡터.

6.4.2 별칭

타입, 템플릿의 동의어를 만들면 유용한 경우가 많음.

표준 헤더 <cstddef> 의 별칭 size_t 정의

using size_t = unsigned int;

size_t 라는 이름의 실제 타입은 구현마다 다름. size_t 가 unsigned long 일 수 있다. 이식성을 높이기 위해 사용하기 좋음.

일반적인 타입 파라미터화

template<typename T>

class Vector {

public:

using value_type = T;

}

모든 표준 라이브러리 컨테이너는 값 타입이 value_type

template<typename C>

using Value_type = typename C::value_type; // 템플릿C의 요소 타입을 Value_type 으로

template<typename Container>

void algo(Container& c)

{

Vector<Value_type<Container>> vec;

// ...

}

새로운 템플릿 타입도 재정의 가능

template<typename Value>

using String_map = Map<string, Vaule>;

String_map<int> m; // m 타입은 Map<string, int>

6.5 조언

[1] 다양한 인자 타입에 적용 가능한 알고리즘은 템플릿으로

[4] 템플릿은 타입 안전성을 보장하지만, 타입 확인이 컴파일 시간상 늦음

[6] 알고리즘 인자로 함수 객체 사용

[7] 간단한 함수 객체를 한 곳에서만 쓴다면 람다로.

[8] 가상 함수 멤버는 템플릿 멤버 함수가 될 수 없다.

[9] 표기 방식은 간단히, 상세 구현을 숨기기 위해 템플릿 별칭 사용 6.4.2

.

.

.

728x90
반응형