{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[C++] The C++ Programming Language: 5. A Tour of C++: Small Utility Components

 5.4 Small Utility Components

    모든 표준 라이브러리 컴포넌트들들이 명백한 이름의 기능, 예를 들어 containers, I/O와 같은 부분만 제공하는 것은 아닙니다. 이번 절에서는 작지만 널리 사용되는 컴포넌트들에 대해서 알아볼 것입니다:

  • 시간을 측정하기 위한 clock과 duration
  • iterator_traits와 is_arithmetic과 같이 자료형에 대한 정보를 주는 자료형 함수들
  • 다차원적 값의 집합을 간단히 표현하기 위한 pair와 tuple

  이 절의 요점은, 이 함수 혹은 자료형들은 복잡하지도 않고, 다른 함수 혹은 자료형과 강력하게 결합되어 사용되어야 하는 것도 아니라는 점입니다. 그럼에도 유용하죠. 이러한 라이브러리 컴포넌트는 좀 더 강력한 라이브러리 기능을 제공하기 위해 사용되는 빌딩 블록들이 됩니다.


5.4.1 Time

  시간을 다루는 기능을 제공하는 표준 라이브러리입니다. 다음은 시간을 다루는 간단한 예제입니다:

1
2
3
4
5
6
using namespace std::chrono; // see §35.2

auto t0 = high_resolution_clock::now();
do_work();
auto t1 = high_resolution_clock::now();
cout << duration_cast<milliseconds>(t1−t0).count() << "msec\n";

  clock은 time_point(시간의 한 점)을 반환합니다. 두 time_point를 빼는 것은 duration(시간의 한 구간)을 반환하죠. 다양한 clock은 그들의 다양한 시간 단위로 결과를 반환합니다. 이 예제의 경우 nanoseconds입니다. duration을 익숙한 다누이로 변환하는 것은 좋은 생각입니다. duration_cast가 하는 일이죠.

  시간을 다루는 표준 라이브러리는 <chrono>에서 서브 네임스페이스인 std::chrono로 확인할 수 있습니다.

  시간을 재보지 않고 성능에 대해 논하는 것은 이상한 일이겠죠.


5.4.2 Type Functions

  type function은 인자 혹은 반환값으로서 자료형을 다루는, 컴파일 시간에 평가되는 함수입니다. 표준 라이브러리는 다양한 라이브러리 구현자와 프로그래머들을 언어, 표준 라이브러리, 혹은 일반적인 코드 수준에서 이점을 얻을 수 있도록 돕기 위해 type function을 제공합니다.

  숫자 타입의 경우, <limits>의 numeric_limist는 유용한 정보들을 다수 제공합니다:

1
constexpr float min = numeric_limits<float>::min();
cs

  비슷하게, 객체의 크기는 내장된 sizeof 연산자를 이용해 찾아낼 수 있죠:

1
constexpr float szi = sizeof(int);
cs

  이러한 type function들은 엄밀한 타입 체킹과 더 좋은 성능을 얻게 해주는 컴파일 시간 계산을 위한 C++ 메커니즘의 일부입니다. 이러한 기능의 사용을 종종 메타 프로그래밍(metaprogramming) 혹은 템플릿 메타 프로그래밍(template metaprogramming)이라고도 말합니다. 표준 라이브러리로부터 제공되는 두 가지 기능에 대해서 알아보죠: iterator_traits와 type predicates입니다.


5.4.2.1 iterator_traits

  표준 라이브러리 sort()는 순서를 정의하는 한 쌍의 iterator를 받습니다. 더 나아가, 이러한 iterator들은 해당 순서에 임의적으로 접근할 수 있어야 하며, 곧 random access iterator여야 합니다. forward_list와 같은 몇몇 컨테이너는 이를 지원하지 않습니다. 특히, forward_list는 singly-linked list이므로 임의 접근은 비용이 비싸며 이전의 요소를 접근할 합리적인 방법이 존재하지 않습니다. 하지만 대부분의 컨테이너와 마찬가지로 forward_list는 정방향 순회를 할 수 있는 알고리즘을 제공합니다.

  표준 라이브러리는 어떤 종류의 iterator인지 확인할 수 있는 iterator_traits 메커니즘을 제공합니다. 이를 이용해서, sort()의 범위를 vector 혹은 forward_list 또한 받아들일 수 있도록 향상시킬 수 있죠. 예를 들어:

1
2
3
4
5
void test(vector<string>& v, forward_list<int>& lst)
{
    sort(v); // sort the vector
    sort(lst); // sort the singly-linked list
}
cs

  이 기법은 해당 작업을 좀 더 일반적으로 유용하게 만들죠.

  먼저, 저는 두 개의 헬퍼 함수를 작성하였습니다. 추가적인 인자를 받아 random-access iterator 혹은 forward iterator 둘을 다루는 각각의 함수를 만드는 것이죠. random-access iterator를 인자로 받는 경우 쉽습니다:

1
2
3
4
5
template<typename Ran> // for random-access iterators
void sort_helper(Ran beg, Ran end, random_access_iterator_tag) // we can subscript into [beg:end)
{
    sort(beg,end); // just sort it
}
cs

  forward iterator를 받는 경우도 거의 단순합니다. vector의 리스트를 복사하고, 정렬한 뒤, 다시 복사하는 것이죠:

1
2
3
4
5
6
7
template<typename For> // for forward iterators
void sort_helper(For beg, For end, forward_iterator_tag) // we can traverse [beg:end)
{
    vector<decltype(∗beg)> v {beg,end}; // initialize a vector from [beg:end)
    sort(v.begin(),v.end());
    copy(v.begin(),v.end(),beg); // copy the elements back
}
cs

  decltype()은 인자의 자료형을 알려주는 내장된 type function입니다.

  진짜 "type magic"은 헬퍼 함수의 선택에 있죠:

1
2
3
4
5
6
template<typname C>
void sort(C& c)
{
    using Iter = Iterator_type<C>;
    sort_helper(c.begin(),c.end(),Iterator_category<Iter>{});
}
cs

  여기서 저는 두 type function을 사용했습니다: C의 iterator 타입을 반환해주는 Iterator_type<C>와 제공되는 iterator의 종류를 알려주는 "tag" 값을 생성해주는Iterator_category<iter>{}입니다:

만약 C의 iterator가 임의 접근을 지원한다면, std::random_access_iterator_tag

만약 C의 iterator가 정방향 접근을 지원한다면, std::forward_iterator_tag

  이를 이용해서 우리는 두 개의 정렬 알고리즘을 컴파일 시간에 선택할 수 있습니다. tag dispatch라고 불리는 이 기법은 유연성과 성능을 향상시키는 표준 라이브러리 활용의 예입니다.

  iterator를 활용하기 위한 기법을 지원하는 표준 라이브러리는 간단한 클래스 템플릿 형태인 <iterator>의 iterator_traits로부터 시작해볼 수 있습니다. 이는 sort()에 활용된 type function의 간단한 정의를 할 수 있게 해주죠:

1
2
3
4
5
template<typename C>
    using Iterator_type = typename C::iterator; // C’s iterator type

template<typename Iter>
    using Iterator_category = typename std::iterator_traits<Iter>::iterator_category; // Iter’s categor y
cs

  만약 여러분들이 "compile-time type magic"을 알고 싶지 않다면 이러한 기능들을 무시하면 됩니다. 하지만 그것은 곧 여러분들의 코드를 향상시킬 수 없다는 이야기겠죠.


5.4.2.2 Type Predicates

(생략)


5.4.3 pair and tuple

  종종 어떤 데이터들은 정말 단순히 데이터로서 다룰 필요가 있습니다. 의미적으로 잘 정의된 클래스의 객체보다는, 단순히 값의 집합으로 말이죠. 이러한 경우에 우리는 단순한 구조체를 정의하는 메커니즘을 제공합니다. 또한 표준 라이브러리가 정의를 직접 작성하도록 만들 수 있습니다. 예를 들어, 표준 라이브러리 알고리즘인 equal_range는 조건을 만족하는 pair의 iterator를 반환합니다.

1
2
3
template<typename Forward_iterator, typename T, typename Compare>
    pair<Forward_iterator,Forward_iterator>
    equal_range(Forward_iterator first, Forward_iterator last, const T& val, Compare cmp);
cs

  주어진 시퀀스 [first:last)에 대해, equal_range()는 cmp를 만족하는 pair 쌍들을 반환할 것입니다. 정렬된 Records의 시퀀스에서 무언가를 검색할 때 활용할 수 있죠:

1
2
3
4
5
6
7
8
9
auto rec_eq = [](const Record& r1, const Record& r2) { return r1.name<r2.name;};// compare names

void f(const vector<Record>& v) // assume that v is sorted on its "name" field
{
    auto er = equal_range(v.begin(),v.end(),Record{"Reg"},rec_eq)

    for (auto p = er.first; p!=er.second; ++p) // print all equal records
        cout << ∗p; // assume that << is defined for Record
}
c
s

  pair의 첫 번째 멤버는 first라고 불리며, 두 번째 멤버는 second라고 불립니다. 이러한 이름은 딱히 창의적으로 보이진 않습니다만, 우리가 제네릭한 코드를 작성하고 싶을 때에는 꽤 요긴합니다.

  표준 라이브러리 pair(<utility>에 있습니다)는 표준 라이브러리뿐만 아니라 어디서든 자주 사용됩니다. pair는 연산자를 지원합니다. =, ==, <와 같은 것들이죠. 물론 요소들이 지원해야만 합니다. make_pair() 함수는 pair를 손쉽게 만들 수 있게 도와주는 함수입니다. 명시적으로 그 자료형을 이야기하지 않아도 되죠:

1
2
3
4
5
void f(vector<string>& v)
{
    auto pp = make_pair(v.begin(),2); // pp is a pair<vector<string>::iterator,int>
    // ...
}
cs

  만약 여러분들이 둘 보다 더 많은 요소가 필요하다면, tuple을 활용할 수 있습니다:

1
2
3
4
5
6
7
8
tuple<string,int,double> t2("Sild",123, 3.14); // the type is explicitly specified

auto t = make_tuple(string("Herring"),10, 1.23); // the type is deduced
// t is a tuple<str ing,int,double>

string s = get<0>(t); // get first element of tuple
int x = get<1>(t);
double d = get<2>(t);

cs

  tuple의 요소들은 숫자로 접근할 수 있습니다. 0부터 시작하죠. pair처럼 first, second로 접근하지 않아도 됩니다. 컴파일-시간에 요소를 선택하려면 안타깝게도 못생긴 형태의 get<1>(t)를 사용해야 합니다. get(t,1) 혹은 t[1]은 사용할 수 없죠.

  pair처럼 tuple 역시 해당 요소들이 지원한다면 비교 등을 수행할 수 있습니다.

  pair는 우리가 하나 이상의 값을 반환할 일이 많기 때문에 흔히 사용되는 인터페이스입니다. 셋 이상의 요소들을 활용하는 것은 비교적 드물기 때문에, tuple은 제네릭 알고리즘의 구현에서 꽤 찾아볼 수 있습니다.








댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기