[전문가를 위한 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이 균형 이진트리로 구성되어 있지만, 이를 직접 사용하도록 인터페이스를 제공하지는 않는다.
- 파서 등을 구현하기 위해 트리나 그래프 구조가 필요하다면 직접 만들거나 다른 라이브러리를 활용한다.