BookReview [A Tour of C++ Third Edition]
A Tour of C++ Third Edition
C++ 창시자가 전하는 최신 C++ 가이드
이 책은 북클럽 9회차 책이다.
C++의 창시자인 Bjarne Stroustrup가 쓴 책으로 C++의 최신 기능과 개념을 소개하는 책이다. C++11, C++14, C++17, C++20 그리고 C++23의 주요 기능들을 다루며, C++의 철학과 설계 원칙에 대해서도 설명한다.
읽기 전에는 단순하게 Effective C++을 읽은 뒤여서 당시 어렵게 생각했거나 변경된 최신 C++에 대해서 도움이 될줄 알았다. 읽으면서 느낀점은 최근에 읽은 책 중에 가장 딱딱한 책이었다. C++을 매우 능숙하게 사용하거나 이해하는 사람이라면 도움이 될 것 같다.
머리말
이 책은 내가 운영하며 참여하고 있는 북클럽에서 8회차로 선정된 책이다. 스터디원 모두 C++ 최신기술에 관심이 있었기 때문에 실제 언어 개발자가 쓴 최신가이드라는 말이 이끌려 읽기 시작했다.
비야네 스트롭스트룹은 C++의 설계자이며 최초 구현자이다. C++은 아직까지도 메이저 언어이며 게임업계에서는 빠질 수 없는 필수적인 언어로 손꼽힌다. 이름이 특이해서 외우기 쉽다는.. 빨대빨대
설명은, 간결하게
이 책은 현재 ISO C++ 표준인 C++20
정의를 기준으로 삼아 설명한다. 언어 자체가 매우 방대하고 크기 때문에 (라이브러리 조차도) 책에서는 꼭 필요한 예제와 개략적인 내용만을 다룬다. 또한, 책을 읽는 독자가 프로그래밍 경험이 풍부하다는 것을 가정한다.
책에서 나오는 대도시를 관광하는 예로 보고 들은 것이 내것이 되는 과정과 전체적인 설명을 듣고 바라보는 학습에 대해서 말해주는데 많이 공감하며, 다시 배운다.
위에서 말했지만 책에서 다루는 내용은 대략적인 설명 + 필요한 예제 뿐이기에 기술적인 궁금증이 생기면 Cppreference를 꼭 읽어봐야 한다.
1장: 기초 쌓기
일단 언어 법률가부터 전부 없애야 해.
헨리 6세, 2부
기본적으로 C++은 컴파일언어이다. 그 과정은 컴파일러가 프로그래머가 작성한 소스 텍스트를 처리해 오브젝트 파일을 만든 뒤 파일을 링커와 합쳐 실행 프로그램을 만든다.
ISO C++ 표준은 두 종류의 엔티티를 정의한다.
- 내장 타입(char, int 등)과 루프와 같은 언어 핵심 기능들
- 컨테이너(vector, mpa 등)와 I/O 연산(
<<
나 getline() 등)같은 표준 라이브러리 컴포넌트
C++은 동적 타입의 언어이다. 모든 엔티티(객체, 값, 이름, 식)의 타입을 사용 시점에 컴파일러에게 알려야 한다. 따라서 객체에 적용할 수 있는 연산 집합과 메모리 레이아웃이 달라진다.
모든 C++ 프로그램은 main()
이라는 전역 함수를 최소한/최대한 하나만 포함해야 한다. import std;
는 선언한 표준 라이브러리를 사용할 수 있도록 준비하라고 컴파일러에게 지시한다. (#include
와 다름 더 유용)
오버로딩 부분에 대한 내용은 타입검사기 부분을 참고하자. (타입으로 견고하게, 다형성으로 유연하게 책)
- 선언은 프로그램에게 엔티티를 알리고 타입을 명시하는 명령문이다. (즉, 실제 동작은 컴퓨터가 한다는 것을 알아야 함)
- 타입은 가능한 값 집합과 연산 집합을 정의한다.
- 객체는 어떤 타입의 값을 저장하는 메모리 영역이다.
- 값은 타입에 따라 다르게 해석되는 비트 집합이다.
- 변수는 명명된 객체이다
객체 초기화 리스트의 단골문제? 초기화 리스트 부분이다.
1
2
3
double d1 = 2.3;
double d2 {2.3};
double d3 = {2.3};
정보를 잃는 암묵적 축소 변환은 C 호환성에 따른 대가이다.
타입을 명시적으로 언급할 특별할 이유가 없으면 auto를 사용한다. 특별한 이유는 다음과 같다.
- 정의가 광범위해서 코드의 독자에게 타입을 분명히 알리고 싶을 때
- 초기자의 타입이 분명하지 않을 때
- 변수의 범위나 정밀도를 밝히고 싶을 때
1
2
3
4
5
6
7
8
9
10
11
12
vector<int> vec; // vec은 전역이다.
void fct(int arg) // fct는 전역이다 (전역 함수를 명명), arg는 지역이다. (정수 인수를 명명)
{
string motto {"Who"}; //motto는 지역
auto p = new Record{"Hume"}; // p는 명명되지 않은 Record를 가리킴
}
struct Record {
string name;
}
C#
에는 실제로 Record라는 구조체같은 클래스가 있다. 책에서 다루는 소멸과 생성은 C의 영역과 C++, 그리고 실제 사용되는 프로그램에 따라 달라진다. (언리얼의 경우) 또는 해당 객체가 RAII 스마터 포인터라면 또 다른 경우
C++는 두 가지 불변성 표기법을 지원한다.
- const: “이 값을 바꾸지 않겠다고 약속해”로 이해하면 된다. 인터페이스를 명시할 때 주로 사용한다.
- constexpr: “컴파일 타임에 평가된다”로 이해하면 된다.
c++에서 배열은 기본적으로 “메모리 내 인접한 객체 시퀀스”의 추상화이다.
2장: 사용자 정의 타입
당황하지 말자!
더글라스 아담스
기본 타입과 const한정자, 선언자 연산자로 만들 수 있는 타입을 내장 타입이라 부른다. C++는 풍부한 내장 타입과 연산 집합을 제공하지만 일부러 저수준에서 제공한다. 따라서 일반적인 컴퓨터 하드웨어의 능력을 직접적으로 그리고 효율적으로 가져다 쓴다. 따라서 다른 언어나 고수준 애플리케이션을 작성하는 기능들은 제공하지 않는다. 따라서 내장 타입과 연산에 추상 메커니즘을 추가하여 프로그래머가 이러한 고수준 기능을 만들 수 있도록 했다.
이름과 참조를 통해 struct 멤버에 접근할 때는 .
을 포인터를 통해 접근할 때는 ->
를 사용한다.
표현과 연산을 밀접하게 관련시켜 용법을 단일화하고 데이터의 일관성을 보장하고, 향후 표현을 개선할 수 있도록 사용자가 표현에 접근하지 못하게 하고 싶을 때가 많다._이를 위해 클래스가 등장했다. 클래스는 멤버들의 집합이며, 멤버는 데이터나 함수, 타입 멤버일 수 있다.
3장: 모듈성
C++ 프로그램된 함수, 사용자 정의 타입, 클래스 계층 구조, 템플릿 등 별도로 개발된 여러 부분으로 구성된다. 이렇게 많은 부분을 관리하는 핵심은 서로의 인터렉션을 명확하게 정의하는 것이다. 가장 중요한 첫 번째 단계로서 한 요소 내에서 인터페이스와 구현을 구분해야 한다.
C++은 사용자 코드에 사용할 타입과 함수의 선언만 보여지는 분리컴파일 개념을 지원한다.
- 헤더파일: 헤더 파일이라는 별개의 파일에 선언을 넣은 후, 선언이 필요한 곳에서 헤더 파일명으로 헤더 파일을
#include
한다.- C#과 큰 차이점이기도 하다. C언어 계열의 특성으로 헤더 파일을 통한 정의와 구현의 분리가 이뤄진다.
- 모듈: module 파일을 정의해 별도로 컴파일한 후, 필요한 곳에서 import한다. 명시적으로 export한 선언만이 그 module을 import한 곳에서 사용 가능하다.
- C#의 네임스페이스와 유사하지만, C++ 모듈은 컴파일 단위로서 더 강력한 기능을 제공한다.
헤더파일과 #include
는 모듈성을 시뮬레이션하는 아주 오래된 방법이며, 몇 가지 심각한 단점을 지닌다.
- 컴파일 시간: 101개의 변역 단위에서
header.h
를#include
하면 컴파일러는header.h
를 101번 처리한다.- 번역 단위: 독립적으로 컴파일되는
.cpp
파일(이 파일에서#include
하는h
파일 포함)
- 번역 단위: 독립적으로 컴파일되는
- 순서 종속성:
header2.h
보다header1.h
를 먼저#include
하면header1.h
에 들어 있는 선언과 매크로가header2.h
내 코드의 의미를 바꿀 수 있다. 반대로header1.h
보다header2.h
를 먼저#include
하면header2.h
가header1.h
내 코드에 영향을 미칠 수 있다. - 비일관성: 타입이나 함수 같은 엔티티를 한 파일에 정의한 후 조금 다르게 또 다른 파일에 정의하면 고장이나 알아채기 어려운 오류로 이어질 수 있다. 이러한 문제는 우연히 혹은 고의로 엔티티를 한 헤더가 아니라 두 소스 파일에 별도로 선언하거나 헤더 파일 간 순서 종속성을 통해 선언할 때 발생한다.
- 이행성: 헤더 파일 내 선언 표현에 필요한 모든 코드는 그 헤더 파일에 제시해야 한다. 이렇게 하지 않으면 헤더 파일이 다른 헤더를
#include
해 코드가 거대해지고, 이로 인해 의도했든 우연이었든 헤더 파일의 사용자는 이러한 세부 구현에 의존할 수밖에 없게 된다.
C++20부터 드디어 언어 단에서 모듈성을 직접적으로 표현하는 방법을 지원하기 시작했다.
- 모듈의 특징
- 모듈은 딱 한 번 컴파일된다.
- 두 모듈은 의미에 영향을 주지 않으면서 어떤 순서로든 임포트할 수 있다.
- 모듈로
import
하거나#include
하면 모듈의 사용자는 암묵적으로 그 모듈에 접근할 수 없다. 즉, import는 이행적이 아니다.
import std;
는 #include <iostream>
보다 10배 더 빨리 컴파일된다. std 모듈은
함수, 클래스, 열거 외에 C++는 여러 선언을 한데 묶어 서로의 이름이 충돌하지 않도록 표현하는 메커니즘인 네임스페이스를 지원한다.
함수와 정보를 주고받는 것이 너무나 중요하기에 그 방법도 다양하다. 다음을 핵심적으로 고려해야 한다.
- 객체를 복사하거나 공유하는가?
- 객체를 공유한다면 가변인가?
- “빈 객체”를 남기고 객체를 이동시키는가?
인수 전달이든 값 반환이든 기본 동작은 “복사하기”이지만 지나치게 자주 복사할 경우 암묵적으로 이동move
으로 최적화할 수 있다. 반대로 값 반환은 지역변수는 함수를 반환하는 시점에 사라지니 지역변수로의 포인터나 참조를 반환해서는 안된다.
- Item 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
- Item 20: ‘값에 의한 전달’보다는 ‘상수객체 참조자에 의한 전달’ 방식을 택하는 편이 대개 낫다
해당 내용에 대한 깊은 설명 참조
후위반환 타입을 이 책에서 처음 접한 것 같다. 람다와 비슷한 생김새를 가졌으며, 책에서도 논리적이긴 하지만 전통성이 더 강하기 때문에 책에서도 기존 방식의 반환을 사용한다.
4장: 오류 처리
오류 처리는 단순히 언어 기능을 넘어 프로그래밍 기법과 도구에 속하는 넓고 복잡한 주제로서 여러 걱정거리와 함께 다양한 영향을 미친다. 하지만 C++는 몇 가지 유용한 기능을 제공한다. 타입 시스템 자체가 주요 도구이다. (타입 검사기도 마찬가지.. 혹시 번역의 문제로 타입 검사기를 말하는걸까?)
try문을 남용하지 말자. 많은 프로그램에서 throw와 던져진 예외를 아주 적절히 처리하는 함수 사이에서 전형적으로 수십 개의 함수를 호출한다. 즉, 대부분의 함수는 단순히 예외를 호출 스택 위로 전달할 수 있어야 한다.
RAII의 취지는 생성자가 클래스를 운영하는 데 필요한 자원을 회득하게 하고, 소멸자가 모든 자원을 해제하게 해서 자원 해제를 암묵적으로 보장하는 것이다.
불변 개념은 클래스 디자인의 핵심이며 전제 조건은 함수 디자인에서 불변과 비슷한 역할을 한다.
- 불변 수립은 정확히 무엇을 하고 싶은지 명확히 해준다. (더 좁게)
- 불변을 통해 구체적으로 바뀐다. 코드를 더 정확하게 바꿀 기회를 제공한다.
오류 처리 대안 부분은 실제 예외 그리고 오류에 대한 대안을 찾을 때 다시 찾아서 읽어볼 예정..
5장: 클래스
C++의 가장 중요한 언어 기능은 클래스다. 클래스는 프로그램 코드 내 엔티티를 표현하기 위해 제공되는 사용자 정의 타입이다. 유용한 개념이나 엔티티, 데이터 컬렉션 등으로 프로그램을 디자인할 때 그 아이디어가 그저 머릿속이나 디자인 문서, 주석 대신 코드에 들어갈 수 있도록 프로그램 내 클래스로 표현한다.
구체 클래스의 기본 개념은 “내장 타입과 완전히 똑같이” 동작하는 것이다. 예를 들어 복소수 타입과 무한 정밀도 정수는 그 타입만의 시맨틱과 연산 집합이 있으나 내장 int와 매우 비슷하다. 비슷하게 vector는 string의 내장 배열과 매우 비슷하지만 더 유연하고 더 매끄럽다.
컨테이너 부분은 RAII 개념과 생성, 소멸 부분을 다룬다. vector를 중심으로 설명하지만 이 부분은 CodeReview에 따로 정리를 하였다.
뒤에서 나오는 클래스의 추상화, 상속부분도 마찬가지.
6장: 필수 연산
타입의 생성자, 소멸자, 복사와 이동 연산은 논리적으로 별개 아니다. 서로 연관된 집합으로 정의하지 않으면 논리적 문제나 성능 문제를 겪게 된다. 클래스 x가 자유 저장소 반납이나 자원 해제 같은 중대한 작업을 수행하는 소멸자를 포함하면 그 클래스를에 함수 구성 전체를 넣어야 할 것이다.
1
2
3
4
5
6
7
8
9
class X {
public:
X() { /* 생성자 */ }
~X() { /* 소멸자 */ }
X(const X&) { /* 복사 생성자 */ }
X(X&&) { /* 이동 생성자 */ }
X& operator=(const X&) { /* 복사 대입 연산자 */ return *this; }
X& operator=(X&&) { /* 이동 대입 연산자 */ return *this; }
};
7장: 템플릿
- 매개변수화 타입
- 매개변수화 연산
- 템플릿 메커니즘
템플릿은 타입 또는 값 집합으로 매개변수화한 클래스나 함수를 말한다. 책에서 말하는 제한된 템플릿 인수는 C#에서 다루는 where
키워드의 기능인 것 같다. 템플릿의 특성상 추론 타입도 제공하나 초기화 리스트가 통일되지 않는 경우에는 오류가 발생한다. 템플릿 인수 추론을 줄여서 CTAD라고 부른다.
매개변수화 연산은 3가지 방법으롤 연산을 표현한다.
- 함수 템플릿
- 함수 객체
- 람다식
정리하다 보면 책에서 다른 챕터(절)의 내용을 같이 다루기 때문에 매번 여러 챕터를 옮겨다녀야 한다는 점이 아쉽다.
8장: 콘셉트와 제네릭 프로그래밍
- 콘셉트
- 제네릭 프로그래밍
- 가변 인자 템플릿
- 템플릿 컴파일 오류
- 조언
템플릿의 기능
- 정보 손실 없이 타입을 인수로 전달할 수 있다.
- 구체화 타임에 다양한 컨텍스트 정보를 결합할 수 있다. 즉, 최적화에 유리하다.
- 값을 템플릿 인수로 전달할 수 있다. 즉, 컴파일 타입에 계산할 수 있다.
템플릿은 컴파일 타임 계산과 타입 조작을 지원하는 강력한 메커니즘을 제공해 코드를 매우 간결하고 효율적으로 만는다. 템플릿의 대표적인 사용 용도는 제네릭 프로그래밍이다. 제네릭 프로그래밍이란 일반적인 알고리즘의 디자인, 구현, 사용에 중점을 둔 프로그래밍(내가 느끼기엔 난이도가 있다.)이다. 여기서 일반적이라는 말은 알고리즘에서 요구하는 기본사항만 충족한다면 다양한 타입을 허용하도록 디자인할 수 있는 알고리즘이라는 뜻이다.
콘셉트는 결국 제한된 템플릿이어야 하고 이를 강력하게 제한하면 할수록 범용성은 줄어들지만 오류의 가능성도 같이 줄어든다. 인수의 부합성과 모호성을 잘 이해하고 다루기 위해선 경험이 필수적인 것 같다.
9장: 라이브러리 훑어보기
기초 프로그래밍만으론 쓸모 있는 프로그램을 만들기 어렵디. 따라서 라이브러리부터 개발해야 한다. 대부분의 개발자는 라이브러리 사용자가 아닐까?
실제로 표준으로 사용하는 라이브러리가 있으며 그것을 사용한 파생도 존재한다.
표준 라이브러리가 제공하는 기능은 다음과 같이 분류된다.
- 런타임 언어 지원
- C 표준 라이브러리
- 국제 문자셋, 지역화, 부분 문자열 읽기 전용 뷰를 지원하는 문자열
- 정규식 매핑 지원
- I/O 스트림
- 파일 시스템 조작 라이브러리
- 컨테이너
- 뷰, 제네레이터, 파이프를 포함하는 범위
- 기초 타입과 범위에 쓰이는 콘셉트
- 표준 수학 함수, 복소수, 난수 생성
- thread와 락과 같은 동시 프로그래밍
- 동기식과 비동기식 코루틴
- 알고리즘 병렬 버전
- 메타 프로그래밍
- 스마터 포인터
- array, bitset, tuple과 같은 특수 목적 컨테이너
- time 관련
- 캘린터 지원
- 단위 접미사
- view, string_view, span 원소 시퀀스
이와 같은 std(표준 라이브러리)의 포함 기준은 다음과 같다.
- 대부분의 C++프로그래머에게 유용했다.
- 같은 기능을 제공하는 단순한 버전에 비해 오버헤드가 크지 않은 일반적 형태로 제공할 수 있다.
- 간단한 사용법을 베울 수 있다.
실제로 C++개발자가 표준 라이브러리에 대해서 다루며 포함 시킨 이유를 설명하니 신기한 느낌이다.
10장: 문자열과 정규식
string 처리는 언어마다 차이가 조금씩 있지만 매우 중요한 부분이다. C++에선 표준 라이브러리를 사용하여 사용자가 C방식으로 포인터를 통해 문자 배열을 처리하지 않도록 string
타입을 제공한다.
예제와 같이 C++ string은 이동 생성자를 포함하므로 값으로 긴 문자열을 반환해도 효율적이다.
과거 학부때나 스터디를 할때 실제로 string 클래스를 만드는 공부를 한적이 있지만 실제 string의 동작 과정이나 최적화는 매우 복잡하다. 개인이 만들어서 활용하기엔 부적합
string이 지원하는 짧은 문자열은 최적화(Small String Optimization, SSO)로 구현한다. 대략 14개
string_view는 C++20부터 추가된 타입으로, 문자열을 복사하지 않고도 문자열을 참조할 수 있게 해준다. 이는 성능을 향상시키고 불필요한 메모리 할당을 줄이는 데 유용하다.
정규식 부분은 실제로 계산하기보단 최근엔 ai로 뽑아내는 경우가 더 많지 않을까?
11장: 입력과 출력
I/O 스트림 라이브러리는 포맷팅된 그리고 포맷팅되지 않은 텍스트와 수 값의 버퍼링된 I/O를 제공한다. ostream
은 타입이 있는 객체를 문자 스트림으로 변환한다. istream
은 문자 스트림을 타입이 있는 객체로 변환한다. iostream
은 두 가지 모두를 제공한다. iostream
은 ostream
과 istream
의 상속 관계에 있다.
c
->ostream
-> 스트림 버퍼 -> 바이트 시퀀스(어딘가)
모든 I/O 스트림 클래스는 소유한 모든 자원을 해제하는 소멸자를 포함한다. 즉 RAII를 따른다. printf나 format등 사용하는 수의 진법에 대한 내용은 대부분 비슷한 것 같다.
- 스트림
- 표준 스트림: 시스템 표준에 붙는 I/O 스트림에 붙는 스트림
- 파일 스트림: 파일에 붙는 스트림
- 문자열 스트림: 문자열에 붙는 스트림
- 메모리 스트림: 특정 메모리 영역에 붙는 스트림
- 동기식 스트림: 데이터 경합없이 다수의 스레드에 쓰일 수 있는 스트림
12장: 컨테이너
객체 저장에 쓰이는 클래스를 일반적으로 컨테이너라고 부른다. 컨테이너의 개념에 대해서 생각하다 보니 클래스를 설계할 때도 고려해야 하는 요소로 중요하게 작용하는 것 같다. 내가 만든 클래스가 컨테이너인지 기능적인것인지 컨테이너에 포함할 인터페이스를 어디까지 고려해야 하는지 등..불변성과 캡슐화, 가변성을 고려해야 하는 설계
C++에서 유명한 말처럼 대부분은 vector를 사용하자. 관련된 캐시 히트 부분은 위 글을 참고
그 외의 컨테이너 내용도 대부분 정리된 내용과 겹친다.
13장: 알고리듬
리스트나 벡터와 같은 데이터 구조는 그 자체로는 크게 쓸모가 없다. 사용하려면 원소 추가와 삭제 같은 기초 접근 연산이 있어야 한다. 정렬하고 부분 집합을 추출하고 원소를 제거하고 객체를 찾는 일반적인 알고리즘을 제공한다.
반복자란 실제로 어떤 타입의 객체이다. 반복자가 쓰이는 컨테이너와 특수한 요구 사항이 각각 다른 만큼 반복자 타입도 다를 수 있다. list 컨테이너의 반복자는 단순히 원소로의 포인터만으로는 부족하다. 일반적으로 list의 원소는 그 list의 다음 원소가 어디에 있는지 모르기 때문이다. 즉, list의 반복자는 링크로의 포인터일 수 있다.
병렬 알고리즘 부분은 아직 사용성이 없는 것 같다.
14장: 범위
뷰에 대한 사용법도 새로 배운 부분인 것 같다. 함수 반환문에 써서 코드를 좀 더 명확하게 하며 RAII도 준수하는 방법이나 뷰를 사용하여 코드의 가독성을 높이는 방법에 대해 고민해볼 필요가 있다.
생성자에 범위 부분을 람다로 사용하여 범위의 원소를 초기화하는 방법도 있다. 이 부분은 아직 익숙하지 않아서 자주 사용하지는 못할 것 같다.
파이프라인은 과거 shell코딩에서 자주 사용하던 방식인데 filter_view와 같은 곳에서 유용하게 사용하는 것 같다.
15장: 포인터와 컨테이너
C++은 데이터를 저장하고 참조하는 간단한 내장 저수준 타입을 제공한다. 객체와 배열은 데이터를 저장하고 포인터와 배열은 이러한 데이터를 참조한다. 하지만 보다 특수하면서 보다 일반적으로 데이터를 저장하고 사용하는 방법을 지원해야 한다. 예를 들어 표준 라이브러리 컨테이너와 반복자는 일반적으로 알고리즘을 지원하도록 디자인된다.
일반적으로 포인터란 어떤 객체를 참조하고 그 객체의 타입에 따라 접근하는 개념을 말한다. int*
같은 내장 포인터 외에도 매우 많다. 최근 가장 중요한 개념으로 스마트 포인터가 있다. 스마트 포인터는 메모리 관리와 관련된 문제를 해결하기 위해 설계된 객체이다. 스마트 포인터는 일반적으로 포인터처럼 동작하지만, 메모리 누수나 잘못된 메모리 접근을 방지하는 기능을 제공한다.
자원 관리는 모든 프로그램에서 필요한 작업이다. 우선 자원을 획득하고 이후 해제해야 한다. 메모리, 락, 소켓, 스레드 핸들, 파일 핸들 등이 모두 자원이다. 암시적이든 명시적이든 적절한 시기에 자원을 해제하지 않으면 심각한 성능 저하가 발생한다.
스마터 포인터의 사용 사례 부분이 잘 정리되어 있어서 좋다. 실제 사용이나 프로젝트 작업시에 참고할 것
16장: 유틸리티
- 표준 라이브러리는
<chrono>
을 통해 시간 처리 기능을 지원한다. - 알고리즘 시간 측정에도 사용함
- std::move 관련
- 인수 포워딩은 이동이 필요한 중요한 유스케이스다. (무엇도 바꾸지 않으면서 인수 집합을 또 다른 함수로 전송하고 싶을 때)
17장: 수
C++은 수 계산에 초점을 맞춰 디자인되지 않았다. (수학에 전문적인 언어가 따로 있다. 예를 들어, MATLAB.) 다만 전형적으로 수 계산은 과학 계산, 데이터베이스 접근, 네트워킹, 기기 제어, 그래픽스, 시뮬레이션 등과 같은 작업이 포함되므로 큰 시스템의 한 부분을 차지하는 계산의 수단으로서 C++는 매우 매력적이다.
-
헤더는 수학 함수에 대한 액세스를 제공한다. -
헤더는 수치 알고리즘에 대한 액세스를 제공한다. (병렬도 제공) -
헤더는 복소수에 대한 지원을 제공한다. -
헤더는 난수 생성에 대한 지원을 제공한다. -
헤더는 수치 한계에 대한 지원을 제공한다. -
헤더는 수학 상수에 대한 지원을 제공한다. ## 18장: 동시 실행
동시 실행(Concurrency), 즉 몇 가지 태스크를 동시에 실행하는 기술은 처리량을 늘리거나 응답성을 높이는 데 널리 쓰인다. 대부분의 언어에서 지원한다. C++에서는 적절한 메모리 모델과 원자 연산 집합으로 한 주소 공간에서 여러 쓰레드를 동시에 실행할 수 있도록 한다.
-
의 mutex, lock연산
동시 실행을 만병통치약으로 여겨서는 안되고 할 수 있다면 태스크를 순차적으로 수행하는 편이 일반적이고 더 빠르고 간편하다. 한 스레드로 정보를 전달하는 것 자체가 비용이 클 수 있다.
다른 계산과 동시에 실행될 가능성이 있는 계산을 태스크라고 부른다. 프로그램 수준에서는 스레드라 표현한다. 다른 태스크와 동시에 실행되는 태스크를 만드려면 가장 먼저 그 태스크를 인수로 넣어 thread를 생성한다.
t1.join();
은 c#에선 await t1;
와 같은 느낌일까? RAII의 일종으로 생각한다.
1
2
3
4
5
6
7
8
9
10
11
void f()
{
cout << "Hello ";
}
struct F{
void operator()() {cout << "Parallel World!\n"; }
};
jthread t1(f);
jthread t2(F{});
이와 같은 코드는 끔찍한 오류를 발생시킨다. f와 F{}는 동기화 없이 각각 객체 cout를 사용하기 때문에 두 태스크 내 각 연산의 실행 순서가 불명확하므로 출력될 결과를 예측할 수 없으며, 프로그램을 실행할 때 마다 다를 수 있다.
PaHerallllel o world!
이러한 문제를 막고자 한다면 osyncstream
을 사용하면 된다.
mutex는 운영체제까지 참조하는 중량 메커니즘이다. 데이터 경합 없이 임의의 작업량을 처리할 수 있다. (shared_lock
, unique_lock
) 하지만 소량의 작업은 atomic
변수라는 훨씬 간단하고 저렴한 메커니즘으로 가능하다.
책에서 .get에 대한 예제를 다룰 때 예외를 말하는데 역시 동시 실행 부분에선 예외 처리가 필수적일 것 같다.
19장: 역사적 배경과 호환성
C++ 역사에 관한 이야기. 2013년에 완성형이 나온 느낌인 것 같다. 왜 11 12로 올라가지 않고 14, 17 20으로 간건지..?
가상함수가 실제 객체지향에 익숙한 프로그래머가 어려워 했다는 점이 신기하고, 여기서도 C언어의 개발자 작은 부분까지 세심히 제작하는 것이 중요하다고 한다. 언어마다 개발 스타일이 달라진다는 이야기를 했는데 그 역동기 속에서 (C -> C++) 시스템 프로그래머를 설득하거나 런타임에 결정하는 가상함수의 개념도 익숙하지 않게 생각한 것 같다.
실제로 객체지향 프로그래밍 언어라는 내용이 아닌 객체지향 프로그래밍을 지원한다라는 내용이 흥미롭다.
댓글남기기