[전문가를 위한 C++] 16장. C++ 표준 라이브러리 둘러보기

Chapter16. 표준 라이브러리 둘러보기

코드 작성법

  • 표준 라이브러리는 C++ 템플릿과 연산자 오버로딩 기능을 상당히 많이 사용한다.

C++ 표준 라이브러리 둘러보기

  • 표준 라이브러리를 구성하는 다양한 컴포넌트들을 디자인 관점에서 살펴본다.
    • 스트링
    • 정규표현식
    • I/O Stream
    • Smart Pointer
    • Exception
    • 수학 연산 관련 유틸리티
    • 시간 관련 유틸리티
    • 무작위수
    • pair, tuple
    • optional, variant, any
    • 함수 객체
    • 파일 시스템
    • MultiThreading
    • 타입 스트레트
    • 컨테이너
    • 알고리즘

스트링

  • string, string_view 클래스 제공. 각각 <string>, <string_view> 헤더 파일에 정의되어 있다.
  • string_view는 const string &과 같다.
  • 유니코드와 현지화도 지원. 관련된 내용은 <locale>에 정의도니 Locale 클래스.

정규표현식

  • <regex>
  • 패턴 매칭을 쉽게 구현할 수 있고, 새 패턴으로 교체할 수 있다.

스마트 포인터

  • <memory>
  • 프로그램 안정화 시 걸림돌이 되는 것들 : 메모리 누수, 댕글링 포인터, 이중 해제.
  • C++에서는 이러한 문제를 방지하기 위해 스마트 포인터를 제공한다.
    • unique_ptr, shared_ptr, weak_ptr

Exception

  • <exception>, <stdexcept>, <system_error>
  • 함수나 메서드에서 발생한 다양한 에러를 상위 함수나 메서드로 전달할 수 있고, 상속 계층을 제공하여 익셉션을 코드에서 곧바로 사용하거나 커스터마이즈 할 수 있다.

수학 관련 유틸리티

  • <complex> : 복소수 클래스
  • <ratio> : 컴파일 시간 유리수 연산 ratio 클래스
  • <valarray> : vector와 유사한 valarray 클래스
  • <limits> : numeric_limit 클래스

시간 관련 유틸리티

  • <chrono> : chrono library
  • <ctime>

무작위수

  • <random>
  • 무작위수 엔진, 무작위수 엔진 어댑터, 무작위수 분포 등을 제공. 이 기능을 이용하면 정규분포, 역지수 분포 등 보다 적합한 무작위수를 생성할 수 있다.

이니셜라이즈 리스트

  • <initializer_list>
  • 인수의 개수가 다양한 함수를 쉽게 작성할 수 있다.

pair, tuple

  • <utility> : 서로 다른 타입의 두 원소를 하나로 묶어서 저장하는 pair 템플릿을 정의하고 있다.
  • <tuple> : tuple을 정의하고 있다.

함수 객체

  • <functional>
  • 특정 표준 라이브러리 알고리즘에 대한 조건식 등에 활용된다.

파일 시스템

  • <filesystem>
  • 이 헤더 파일에 정의된 것들은 모두 std::filesystem 네임스페이스에 속한다.

멀티스레딩

  • 멀티 코어를 최대한 활용하게 하려면 여러 스레드를 사용하도록 (멀티 스레딩을 지원하도록) 멀티 스레드 코드를 작성하면 된다.
  • <thread> : 이 헤더 파일에 정의된 thread 클래스를 이용하면 스레드를 하나씩 생성할 수 있다.
  • <atomic> : 이 헤더 파일에 정의된 atomic 클래스를 이용하면 데이터를 스레드에 안전하고 아토믹하게 (여러 스레드가 동시에 접근하지 않게) 만들 수 있다.
  • <conditional_variable>, <mutex> : 다양한 스레드 동기화 매커니즘을 제공한다.
  • <future> : async, future를 사용하면 여러 스레드로 뭔가 계산해서 적절한 예외 처리 방식을 통해 결과를 받을 수 있다. 스레드를 직접 다룰 때 보다 사용하기 쉽다.

타입 스트레트

  • <type_traits> : 컴파일 시간에 타입 정보를 조회할 수 있다.

표준 정수 타입

  • <cstdint> : 다양한 표준 정수 타입 (int8_t, int64_t)를 정의하고 있다. 이러한 타입의 최댓값과 최솟값을 지정하는 매크로도 제공한다.

컨테이너

  • 정보를 원소 단위로 저장하는 컨테이너 개념.
  • 표준 라이브러리에서 제공하는 컨테이너는 모두 클래스 템플릿이다. 기본 타입, 사용자가 정의한 클래스까지 모든 타입의 데이터를 담을 수 있다.
  • 동형 컬렉션이다. 컨테이너 인스턴스마다 단 한 가지 타입 객체만 저장할 수 있다.
  • 구현이 아닌 인터페이스만 정의해 두었기 때문에 벤더마다 다르게 구현할 수 있다.
  • vector
    • <vector>
    • 동적 배열. vector에 저장된 원소 수에 따라 크기가 자동으로 늘어나거나 줄어든다.
    • 마지막 항목을 추가하거나 삭제하는 연산을 매우 빠르게 처리한다. 하지만 원소를 추가할 때 마다 크기도 늘어나야 한다.
      • 시간 복잡도 O(1), 공간 복잡도 O(N)
    • vector에서 끝부분이 아닌 다른 지점에서 원소를 추가하거나 삭제하는 연산은 그보다 느린 선형 시간이 걸린다.
  • list
    • 양방향(이중)연결 리스트, <list> 헤더 파일에 정의 되어 있다.
    • 배열이나 vector와 마찬가지로 일련의 원소를 저장하지만, 연속된 메모리 공간에 저장되지 않을 수 있다는 점이 다르다.
    • 추가와 삭제 연산을 수행할 때 정확한 위치를 안다면 상수 시간에 빠르게 처리
    • 그럼에도 대체로 vector가 더 빠르다.
  • forward_list
    • <forward_list>
    • 단방향 연결 리스트이다. 정방향(forward) 반복만 지원하기 때문에 list보다 메모리를 적게 쓴다.
    • 임의 접근 (랜덤 액세스)는 지원하지 않는다.
  • deque
    • <deque>
    • 양방향 큐 (double queue)
    • 상수 시간에 원소 접근이 가능하고, 양쪽 끝에 원소 추가/삭제 연산을 지원한다.
    • 연속된 메모리 공간에 저장하지 않는다.
  • array
    • <array>
    • 주어진 컨테이너에 담긴 원소 개수를 정확히 알고 있고, 공간을 동적으로 늘리는 유연성이 필요 없다면 크기가 고정된 array를 사용하는 것이 좋다.
      • vector에 비해 오버헤드가 적다.
  • vector, list, forward_list, deque_array 컨테이너는 원소를 순차적으로 나열된 형태로 저장하기 때문에 순차 컨테이너라 부른다.
  • queue
    • <queue>
    • 추가/삭제 빠르다.
  • priority_queue
    • <queue>
    • 추가/삭제 연산은 일반 queue보다 대체로 느리다. 우선순위에 따라 원소를 정렬해야 하기 때문.
  • stack
    • <stack>
  • queue, priority_queue, stack은 컨테이너가 아닌 컨테이너 어댑터이다. 즉, vector, list, queue와 같은 표준 순차 컨테이너 위에 인터페이스만 추가한 것이다.
  • set과 multimap
  • set, map과 같은 컨테이너를 연관 컨테이너라고 부른다. set은 키가 곧 값이다. set, map 모두 원소를 정렬한 상태로 유지한다.
  • 비정렬 연관 컨테이너와 해시 테이블
    • 표준 라이브러리는 다음 네 가지 종류의 해시 테이블도 제공한다.
      • unordered_map
      • unordered_multimap
      • unordered_set
      • unordered_multiset
    • <unordered_map>, <unordered_set> 헤더 파일에 정의 되어 있다.
    • 원소를 추가/삭제/조회하는 연산 속도는 대체로 상수 시간이다. 특히 map이나 set보다 원소를 조회하는 속도가 빠르다.
  • bitset
    • <bitset>
    • 비트 단위 작업을 추상화한 클래스
    • 원소를 추가하거나 삭제할 수 있는 데이터 구조가 아니다.
    • 크기는 고정 되어 있으며 반복자를 제공할 수 없다. 마치 부울값을 읽고 쓸 수 있도록 한 줄로 나열한 것과 같다.
    • 원소 데이터 타입에 크기 제한이 없다.
      • bitset이 40 비트로 구성될 수도 있고, 213 비트로 구성될 수도 있다. bitset<N>

알고리즘

  • 함수 템플릿으로 구현되어 있어 다양한 타입의 컨테이너에 적용할 수 있다.
  • 표준 라이브러리는 데이터(컨테이너)와 기능(알고리즘)을 엄격히 구분한다.
    • 이론상 구분되어 있지만 성능상 컨테이너에서 메서드 형태로 알고리즘을 제공하기도 한다. (set.find()는 제네릭 버전의 find()보다 빠르다!)
  • 반복자는 알고리즘과 컨테이너를 연결한다. 컨테이너에 담긴 원소를 순차적으로 탐색하기 위한 표준 인터페이스를 제공하기 때문에 알고리즘과 컨테이너의 종류에 관계 없이 똑같은 방식으로 코드를 작성할 수 있다.
  • <iterator> 헤더파일은 헬퍼 함수를 제공하여, 컨테이너에 맞는 반복자를 리턴한다.
    • begin(), end()
    • cbegin(), cend()
    • rbegin(), rend()
    • crbegin(), crend()
  • 표준 라이브러리 알고리즘은 <algorithm> 헤더파일에 정의되어 있다.
  • 알고리즘 종류
    • 불변형 순차 알고리즘
      • 원소를 순차적으로 조회하여 각 원소에 대한 정보를 리턴하는 알고리즘이다. 여기에 나온 알고리즘을 사용하면 반복문으로 원소를 순차적으로 탐색할 때 for문을 작성할 일이 거의 없다.
      • 탐색 알고리즘
      • 비교 알고리즘
      • 집계 알고리즘
    • 가변형 순차 알고리즘
      • 시퀀스의 모든 원소나 일부 원소를 수정하는 알고리즘이다.
    • 작업 알고리즘
      • 시퀀스의 원소마다 함수를 실행한다.
    • 교환 알고리즘
    • 분할 알고리즘
    • 정렬 알고리즘
    • 이진 탐색 알고리즘
    • 집합 알고리즘
    • 힙 알고리즘
    • 최대/최소 알고리즘
    • 수치 연산 알고리즘
    • 순열 알고리즘

표준 라이브러리에서 제공하지 않는 기능

  • 여러 스레드가 컨테이너에 동시에 접근할 때 스레드 안정성을 보장하지 않는다.
  • 제네릭 트리나 그래프 구조를 제공하지 않는다. map과 set이 균형 이진트리로 구성되어 있지만, 이를 직접 사용하도록 인터페이스를 제공하지는 않는다.
  • 파서 등을 구현하기 위해 트리나 그래프 구조가 필요하다면 직접 만들거나 다른 라이브러리를 활용한다.