[전문가를 위한 C++] C++와 표준 라이브러리
‘전문가를 위한 C++ - Marc Gregoire 지음, 남기혁 옮김’ 책을 참고하여 작성한 포스트입니다.
C++의 기초
전처리 지시자
빌드(build)
- C++로 작성된 소스 코드를 프로그램으로 만드는 작업으로, 세 단계를 거친다.
- 전처리(preprocess) 단계
- 소스 코드에 담긴 메타 정보를 처리한다.
- 컴파일(compile) 단계
- 소스 코드를 머신이 읽을 수 있는 객체 파일로 변환
- 링크(link) 단계
- 앞서 변환한 여러 객체 파일을 애플리케이션으로 엮음
- 전처리(preprocess) 단계
지시자(directive)
- 전처리기에 전달할 사항을 표현함
#
문자로 시작함.#include [file]
- 지정한 ‘파일’의 내용을 지시자 위치에 넣음.
- 다른 곳에 정의된 함수를 사용할 목적으로 해당 함수의 선언문이 담긴 헤더 파일을 가져온다.
#define [key] [value]
- 코드 내에 key 들을 value 로 모두 바꿈.
#ifdef [key] #endif
,#ifndef [key] #endif
- key가
#define
문으로 정의되어 있으면 (후자는 그러지 않은 경우) 묶인 코드 블럭을 포함 시킴.
- key가
#pragma [xyz]
#pragma once
의 경우 중복되는 include를 막는다.
헤더 파일의 주 용도는 소스 파일에서 정의할 함수를 선언(declare)하는 것이다.
함수의 호출 방식, 매개변수의 개수와 타입, 리턴 타입 등을 컴파일러에 알려 준다.
함수 정의(definition, 구현)는 실제로 수행할 동작을 나타낸다.
네임스페이스(namespace)
- 코드에서 이름이 서로 충돌하는 문제를 해결하는 방법 중 하나이다.
- namespace를 적용한 함수 등을 호출하려면 스코프 지정 연산자(scope resolution operator,
::
) 를 이용하여, 이름 앞에 namespace를 붙인다.
std::cout << "Hello, World! ";
// 네임스페이스 블록 안에서 접근할 때는
// 네임스페이스 접두어를 붙이지 않아도 된다.
// 혹은 using 지시자로 생략 가능하다.
using namespace std;
int main()
{
cout << "Hello, World!" << endl;
}
- 중첩(nested) namespace 는
xxx::yyy::zzz
, - namespace alias를 사용하면 네임스페이스의 이름을 변경하거나 더 짧게 할 수 있다.
namespace Short = XXX::YYY::ZZZ
헤더 파일 안에서는 절대로 using 문을 작성하면 안된다!
그러면 해당 헤더 파일을 include하는 모든 파일에서 using 문으로 지정한 방식으로 호출해야 하기 때문이다.
리터럴(literal)
- 코드에 표시한 숫자나 스트링과 같은 값.
- 직접 정의도 가능하다. (15장)
123 // 10 base
0173 // 8 base
0x7B // 16 base
0b1110 // 2 base
3.14f // floating-point
3.14 // double floating-point
0x3.ABCp-10, 0xb.cp12l // 16 base floating-point ??
'a' // char
"abc" // character array 끝은 \0
23'345'344.234'222f // 자리수 구분자(digits separator
변수(variable)
int initializedInt { 7 }
과int initializedInt = 7
은 같다. 전자를 균일 초기화(uniform initialization) 이라고 한다.
숫자 경곗값
<limits>
에서 제공 하는std::numeric_limits
클래스 템플릿을 사용하면 된다.
int MaxValue = numeric_limits<int>::max();
int MinValue = numeric_limits<int>::min();
int LowestValue = numeric_limits<int>::lowest();
- 부동 소수점 에서는 min의 경우 가장 작은 ‘양’의 값이고, 최젓값은 가장 작은 ‘음수’ 값이다!
영 초기화(zero initialization)
- 기본 정수 타입은 0, 기본 부도소수점 타입은 0.0, 포인터 타입은 nullptr,
- 객체는 디폴트 생성자로 초기화한다.
float MyFloat {};
캐스트(cast)
- 프로그램 실행 중에 변수의 타입을 바꾸는 기능
- 자세한 건 10장에서 더 본다
float MyFloat { 3.14f };
int I1 { (int)MyFloat };
int I2 { int(MyFloat) };
int I3 { static_cast<int>(MyFloat) };
연산자
비트 연산자
&
&=
AND 연산|
|=
OR 연산<<
>>
<<=
>>=
shift 연산^
,^=
XOR 연산
열거 타입(enumerated type, enum)
강타입 열거 타입(strongly typed enumeration type)
- 변수에 지정할 수 있는 값의 범위를 제한한다.
- 기본적으로 int타입으로 멤버에 값이 내부적으로 할당되며,
- 별도 지정이 없다면 첫 멤버의 값은 0, 따라오는 멤버들은 이전 멤버의 값에 1을 더한 값으로 할당된다.
- 내부적으로 정수로 표현된다고 해서 자동으로 정수로 변환되는 것은 아니다!
- 열거 타입의 값을 다른 타입으로 바꿀 수 있다.
enum class Types : unsigned long { ... }
- 강타입 정의한 열거 타입 값은 스코프(scope)가 자동으로 확장되지 않는다. 즉, 상위 스코프에 똑같은 이름이 있더라도 충돌되지 않는다.
- 예전 방식인
enum
보다는enum class
방식으로 선언하자!
구조체(struct)
- 선언한 구조체를 타입으로 선언한 변수는 해당 구조체에 있는 모든 필드(field)를 가진다.
- 각 필드는
.
연산자로 접근한다.
조건문(conditional statement)
if의 초기자
if (<초기자>; <조건문>) ...
처럼 초기자를 넣을 수도 있다.- 이 초기자는 조건문과, if, else if, else 본문에서만 사용 가능하다.
switch 문
- 표현식의 결괏값이 반드시 정수 타입이거나, 열거 타입이거나, 강타입 열거 타입이어야 하며, 상수와 비교 가능해야 한다.
- 표현식의 결과에 해당하는 상숫값마다 특정한 경우를
case
문으로 표현한다. - 각 case에
break
문이 없다면 이어지는 case를 계속 수행해 나간다. 이것을 fallthrough 라고 한다. [[fallthrough]]
어트리뷰트를 넣어주면 좋겠다
...
case One:
DoSomething();
[[fallthrough]];
case Two:
...
- switch 문도 초기자 지정이 가능하다.
switch (<초기자>; <표현식>) {<본문>}
조건 연산자(conditional operator)
<조건> ? <참인 경우 동작> : <거짓인 경우 동작>
- 인수 세개를 받는 유일한 삼항 연산자(ternary operator) 이다.
논리 연산자(logical evaluation operator)
- 논리 표현식 평가할 때 단락 논리(short-circuit logic, 축약 논리)에 따른다.
- 즉, 표현식 평가 중 최종 결과가 나오면 나머지 부분은 평가하지 않는다.
3방향 비교 연산자(three-way comparison operator)
<=>
가 연산기호이다. 우주선 연산자라고도 불린다.- C++20 기능.
함수(function)
- 함수 사용하려면 먼저 선언해야 한다.
- 특정 파일 안에서만 사용할 함수는 선언과 구현(정의)을 모두 소스 파일 안에 작성한다.
- 다른 모듈이나 파일에서도 사용한다면, 선언은 모듈 인터페이스 파일로부터 익스포트하고, 정의는 모듈 인터페이스 파일이나 모듈 구현 파일에 작성한다.
- 함수를 선언하는 문장을 function prototype 혹은 function header 라고 부른다.
- 함수의 리턴 타입을 제외한 함수 이름과 매개변수 목록을 function signature 라고 부른다.
함수 이름
- 모든 함수는 내부적으로
__func__
라는 로컬 변수가 저장되어 있는데, - 이 변수의 값이 함수의 이름이다.
함수 오버로딩(overloading)
- 리턴 타입만 달라서는 안된다.
- 매개변수 구성(타입이나 개수)이 달라야 한다.
- 주어진 인수를 기반으로 컴파일러가 적합한 버전을 선택해 실행한다.
어트리뷰트(attribute)
- 소스코드에 벤더에서 제공하는 정보나 옵션을 추가하는 메커니즘
[[어트리뷰트]]
와 같이 사용한다.
C스타일 배열(array)
- 배열은 같은 타입의 값을 연달아 저장하며, 각 값은 배열에서 해당 위치를 이용해 접근한다.
- 배열 선언 시 반드시 배열의 크기를 지정해야 하며,
- 변수로 지정할 수 없고, 상수 또는 상수 표현식(constant expression, constexpr)으로 지정해야 한다.
// 아래 세가지 다 같은 배열의 초기화이다.
int MyArray[3] = {0};
int MyArray[3] = {};
int MyArray[3] {};
// c++20
int MyArray[] {1, 2, 3, 4};
int MyArray[3] { 2 }; // 첫 번째 원소만 2. 나머지는 0
char TicTacToeBoard[3][3]; // 2차원 배열
<array>
헤더의std::size()
함수로 배열의 크기를 구할 수 있다.
std::array
- c++ 의
<array>
헤더 파일에 정의된 배열이다. array<int, 3> arr { 9, 8, 7 };
와 같이 초기화한다.- 첫 매개변수가 배열에 담길 원소의 타입, 두번째 매개변수가 배열의 크기다.
std::vector
<vector>
헤더 파일에 선언된 동적 배열이다.
std::pair
<utility>
헤더 파일에 정의된 클래스 템플릿이다.- 두 값을 하나로 묶고, public 데이터 멤버인
first
와second
로 접근 가능하다.
std::optional
<optional>
헤더 파일에 정의된 클래스 템플릿이다.- 특정한 타입의 값을 가질 수도 있고, 아무 값도 가지지 않을 수도 있다.
- 함수 매개변수에 전달된 값이 없을 수도 있는 상황에 사용되기도 하고,
- 값을 리턴 할수도 있고 그러지 않을 수도 있는 함수의 리턴 타입으로도 사용한다.
optional<int> GetData(bool GiveIt)
{
if (GiveIt)
{
return 42;
}
else
{
return nullopt;
}
}
int main()
{
optional<int> Data1 { GetData(true) };
// 값이 있는 지 확인하려면 아래 두 가지 방법
if(Data1.has_value()) {}
else if(Data1) {}
// 값 get은 아래 두가지 방법
cout << Data1.value() <<endl;
cout << *Data1 << endl;
// 값이 있으면 값 리턴, 없으면 다른 값 리턴
cout << Data1.value_or(0) << endl;
}
- 값이 없는 optional 에 대해 value() 를 호출하면
std::bad_optional_access
예외가 발생한다. - 레퍼런스는 optional에 담을 수 없다. 포인터는 가능
구조적 바인딩(structured binding)
- 여러 변수 선언 시 변수값을 한꺼번에 초기화 하는 기능
array values { 1, 2, 3};
auto [x, y, z] { values };
- x, y, z 가 배열내의 각 원소로 초기화 된다.
- 반드시
auto
키워드를 사용해야 한다.!! - 개수도 맞아야 함
- 비 static 멤버가 모두 public 으로 선언된 구조체도 사용 가능
- pair 도 가능
반복문
범위 기반(rang-based) for 문
- 컨테이너에 담긴 원소에 대해 반복문을 실행하는데 좋은 기능.
- 반복자를 리턴하는 begin(), end() 메서드가 정의된 모든 타입에 사용 가능
- 컨테이너 내의 모든 원소에 대한 복제본을 가져온다.
- 복제하지 않고 싶다면 레퍼런스 변수를 사용하면 된다.
C++20
- for문에 초기자 지정이 가능하다.
초기자 리스트(std::initializer_list
)
<initializer_list>
헤더 파일에 정의되어 있는 클래스 템플릿이다.
#include <initializer_list>
int MakeSum(initializer_list<int> Values)
{
int Total {};
for (int Value : Values)
{
Total += Value;
}
return Total;
}
int main()
{
int A { MakeSum({ 1, 2, 3})}; // 6
int B { MakeSum({ 1, 2, 3.0})} // error
}
- 초기자 리스트는 타입에 안전(type safe)하다.
- 두번째 함수 호출과 같이 다른 타입이 지정되면 컴파일 에러 또는 경고 메시지가 출력됨
c++의 string
- 2장에서..
c++의 객체지향 언어 특성
클래스(class)
- 객체의 특성을 정의한 것
- 클래스를 정의하는 코드는 주로 모듈 인터페이스 파일에 작성하고,
- 구현하는 코드는 함께 적거나, 소스파일에 작성한다. 모듈의 자세한 내용은 11장
- 데이터 멤버(data member, 속성) 와 메서드(method, 동작)를 선언한다.
- public, private, protected 등으로 접근 수준을 지정하는데, 중복되거나 순서 상관 없다.
- 보통 데이터 멤버는 private으로 설정하고, 이 데이터에 대한 접근과 수정은 public으로 지정한다.
- 클래스와 이름이 같고 리턴 타입이 없는 메서드를 생성자(constructor) 라고 하고, 틸드(~)가 붙은 메서드를 소멸자(destructor)라고 한다.
- 생성자로 데이터 멤버를 초기화 하는 방법은
- 생성자 초기자(constructor initializer)
- 생성자 이름 뒤에 콜론을 붙여 표현
- 생성자의 본문에서 초기화
- 생성자 초기자(constructor initializer)
스코프 지정
- 변수나 함수, 클래스 등에 접근할 때 가장 안쪽 스코프의 이름ㅂ터 검색하고, 없으면 바로 다음 바깥의 스코프를 검색하며, 글로벌 스코프(global scope) 에 이르기까지 찾아나간다.
- 글로벌 스코프에도 없으면 undefined symbol error 를 발생시킨다.
- 접두어 없이 스코프 지정 연산자만 사용하면 글로벌 스코프에 직접 접근 가능하다!
균일 초기화
- c++11 이후로는 구조체, 클래스 둘다 중괄호를 이용한 균일 초기화(uniform initialization, 중괄호 초기화, 유니폼 초기화)로 통일되었다.
// 등호 생략 가능
CircleStruct MyCircle1 = { 10, 10, 2.5 };
CircleClass MyCircle2 = { 10, 10, 5.0 };
int X = 3.14;
의 경우 축소 변환(narrowing)이 일어나는데,- 균일 초기화를 사용하면 이 경우 에러 메시지를 출력한다.
- 클래스의 ‘생성자 초기자’도 균일 초기화가 가능하다.
MyClass() : MyArray { 1, 2, 3} {}
지정 초기자(designated initializer)
- c++20
포인터와 동적 메모리
- 동적 메모리를 이용하면 컴파일 시간에 크기를 확정할 수 없는 데이터를 다루 수 있다.
스택(stack)과 프리스토어(free store)
- 스택
- 스택의 top은 프로그램의 현재 스코프를 표현하며, 주로 현재 실행 중인 함수를 가리키며, 이 함수에 선언된 변수는 모두 최상단 스택 프레임의 메모리 공간에 담긴다.
- 함수의 실행이 끝나면 해당 스택 프레임이 삭제되면서 함수 안의 변수들도 같이 삭제되어 메모리 공간을 차지하지 않아 프로그래머가 직접 할당 해제(deallocate)할 필요가 없다.
- 프리스토어
- 함수가 끝난 후에도 그 안에서 사용하던 변수를 유지하고 싶을 때 저장하는 곳이다.
- 여기에 할당된 메모리 공간은 직접 할당 해제 해야 한다.
포인터 사용법
- 메모리 공간을 적당히 할당하기만 하면 어떠한 값이라도 프리스토어에 저장이 가능하다.
- 포인터(pointer)를 선언하면 된다.
*
가 특정 타입에 대한 메모리 공간을 가리킨다는 것을 의미한다.- 포인터는 동적으로 할당된 프리스토어 메모리를 가리킨다.
- 포인터는 변수를 초기화하지 않으면 어느 메모리를 가리키는 지 알 수 없기 때문에 반드시 초기화해야 한다.
- 그러지 않고 사용하면 거의 대부분 프로그램이 크래시(crash)로 뻗어버린다.
- 당장 포인터 변수에 메모리를 할당하지 않을 때는 널 포인터(null pointer, nullptr)로 초기화 해주어야 한다.
int* MyIntegerPointer { nullptr };
- nullptr은 bool 표현식에서는
false
로 취급한다! - 포인터 변수에 메모리를 동적으로 할당할 때는
new
연산자를 사용한다.MyIntegerPointer = new int;
- 동적으로 할당한 메모리를 다 쓰고 나면 반드시
delete
로 공간을 해제해 주어야 한다.delete MyIntegerPointer; MyIntegerPointer = nullptr;
- 메모리를 해제한 포인터를 다시 사용하지 않도록 바로 nullptr로 초기화 해주면 좋다.
- 역참조(dereference, 참조 해제)를 통해 포인터가 가리키는 값에 접근이 가능하다.
- 프리스토어 뿐 아니라 스택과 같은 다른 종류의 메모리도 포인터로 가리킬 수 있다.
- 원하는 변수의 포인터 값을 얻으려면
&(레퍼런스)
연산자를 사용하면 된다.int i { 8 }; int* MyIntergerPointer { &i };
- 구조체에 접근할 때는 역참조로 구조체 자체(시작 지점)에 접근한 뒤
.
연산자를 통해 필드에 접근한다.// 아래 두 표현은 같다 cout<<(*AnEmployee).salary; cout<<AnEmployee->salary;
동적으로 배열 할당
new[]
연산자를 통해 프리스토어에 배열을 할당한다.- 포인터 변수는 스택 안에 있다.
- 메모리 할당 후에는 배열을 일반 스택 기반 배열처럼 다룰 수 있다.
- 작업이 끝나면
delete[]
연산자로 할당 해제 해주자
널 포인터 상수
NULL
은 포인터가 아니라 정수 0 에 해당한다!- 그래서 매개변수의 타입이 정수인 함수에
NULL
이 들어가는 경우 그대로 실행될 수 있다. - 널 포인터 상수인
nullptr
을 쓰자!
const
의 다양한 용도
- 변경되면 안 될 대상을 선언할 때 사용한다.
const 상수
- 컴파일러가 값이 변경되지 않도록 보장하는 작업을 한다.
// Ip가 가리키는 값이 수정되지 않게 하는 선언 const int* Ip; int const* Ip; // Ip 자체의 값이 변경되지 않도록 하는 선언 // 값 변경이 안되니까 변수 선언과 동시에 초기화해주어야 한다! int* const Ip { nullptr }; // Ip가 가리키는 값, Ip의 값 둘 다 수정되지 않도록 하려면 const int* const Ip { nullptr }; int const* const Ip { nullptr };
- 함수에서 const 매개변수를 받는 다면, 비 const 변수도 const 변수로 캐스드 될 수 있다.
const 메서드
- 클래스 메서드에 const 지정을 하면, 해당 클래스의 데이터 멤버를 수정할 수 없게 만든다.
- const 멤버 함수를 인스펙터(inspector), 비 const 멤버 함수를 뮤테이터(mutator)라고 한다.
constexpr 키워드
consteval 키워드
- c++20
레퍼런스(reference, 참조)
- 레퍼런스는 변수에 대한 앨리어스(alias)다.
- 레퍼런스에 대해 수정한 내용은 그 레퍼런스가 가리키는 변수의 값에 그대로 반영된다.
- 즉, 변수의 주소를 가져오거나 변수에 대한 역참조 연산을 수행하는 작업을 자동으로 처리해주는 특수한 포인터라 볼 수 있다.
레퍼런스 변수
- 레퍼런스 변수는 반드시 생성하자마자 초기화해야 한다.
int X = 3; int& XRef = x; XRef = 10; // X 의 값도 10으로 바뀜
- 레퍼런스는 한 번 생성되고 나면 가리키는 대상을 바꿀 수 없다!
- 한번 생성된 레퍼런스에 다른 변수를 대입하면 레퍼런스가 가리키는 변수의 값에 다른 변수의 값이 대입된다.
int Y { 5 }; int& YRef { Y }; XRef = YRef // X의 값이 5로 바뀜
- 레퍼런스에 다른 레퍼런스를 대입하는 경우도 위와 같다!
- 그래서 기본적으로 레퍼런스 자체는 기본적으로 const 속성을 갖는다.
- 레퍼런스에 대한 레퍼런스도 만들 수 없으므로,
- 레퍼런스에 const를 사용하는 경우는 레퍼런스가 가리키는 변수의 값을 수정하지 않고 싶을 때이다.
- 이 경우 레퍼런스가 가리키는 변수에 직접 접근해서 값을 수정하는 것까지는 막지 못한다!
- const 레퍼런스의 경우만 리터럴 처럼 이름 없는 값에 대해 레퍼런스 생성이 가능하다!
- 이름 없는 객체도 const 레퍼런스로 가리킬 수 있다!
// 아래 두 선언은 같은 선언이다 const int& UnnamedRef { 5 }; int const& UnnamedRef { 5 }; // 임시 객체를 const reference가 가리킴 const string& String1 { GetString() };
- 레퍼런스는 모든 타입에 대해 만들 수 있다. 포인터에도!
int* IntP { nullptr }; int*& PtrRef { IntP }; PtrRef = new int; *PtrRef = 5; // IntP가 5를 가리킴
- 레퍼런스가 가져온 주소는 그 레퍼런스가 가리키는 변수의 주소와 같다!
int X { 3 }; int& XRef { X }; int* XPtr = &XRef; *XPtr = 100; // X의 값이 100으로 바뀜
- 위에서
XPtr
는XRef
타입이 달라 비교연산이 되지 않는다. &XRef
와는 비교 연산 가능하고 동일하다.- 구조적 바인딩으로 여러 레퍼런스를 한번에 초기화 가능하다.
레퍼런스 데이터 멤버
- 반드시 생성자 초기자에서 초기화 해야 한다!
MyClass(int& Ref) : MyRef(Ref) {}
reference 매개변수
- 레퍼런스는 주로 함수나 메서드의 매개변수로 많이 사용된다.
- 매개변수는 값 전달(pass-by-value) 방식을 따르기 때문에 함수는 인수의 복사본을 받는다.
- 그래서 함수 내에서 인수의 복사본을 변경하더라도 인수의 원본은 변하지 않게 된다.
- C에서는 포인터를 이용해 다른 스택 프레임에 있는 변수를 수정하는데, 함수 안에서 포인터를 역참조하다보면 해당 변수가 현재 스택 프레임에 없는 경우도 메모리를 변경해버리는 문제가 생길 수 있다. 그리고 이 방식은 포인터 연산이 많아 코드가 복잡하다.
- C++에서는 레퍼런스 전달 방식(pass-by-reference)을 통해 매개변수를 레퍼런스로 전달할 수 있다.
- 주의할 점은, 레퍼런스로 전달하는 매개변수에 리터럴을 지정하면 컴파일 에러가 발생하므로, 리터럴을 지정하고 싶다면 const 레퍼런스로 전달해야 한다.
- const 레퍼런스 매개변수의 가장 큰 장점은 성능이다. 함수에 매개변수를 레퍼런스로 전달하면 원본에 대한 포인터만 전달되기 때문에 원본 전체를 복제하지 않으며, const 레퍼런스를 사용할 경우 원본 변수가 변경되지도 않는 장점까지 있다.
- const 레퍼런스 매개변수는 특히 객체를 다룰 때 유용하다.
레퍼런스 리턴값
- 함수나 메서드의 리턴값을 레퍼런스 타입으로 지정이 가능하지만, 함수 종료 후에도 계속 남아 있는 객체에 대해서만 레퍼런스로 리턴이 가능하다.
포인터와 레퍼런스의 선택 기준
- 레퍼런스가 하는 일을 포인터가 할 수 있지만, 우선 코드가 복잡해지고, 레퍼런스보다 불안전하다.
- 레퍼런스의 값은 널이 될 수 없고, 레퍼런스를 명시적으로 역참조도 할 수 없다.
- 가리키는 위치를 변경해야 하는 경우를 제외하고는 레퍼런스를 사용해 주는 것이 좋다.
- RVO, NRVO, copy elision, zero-copy pass-by-value ??
const_cast()
- 변수에 const 속성을 추가하거나 제거하는 기능을 한다.
- 서드파티 라이브러리처럼 수정할 수 없는 경우 등 부득이할때 사용한다.
// 일시적으로 String1의 const를 해제한다. void SomeFunc(const char* String1) { SomeMethod(const_cast<char*>(String1)); }
std::as_const()
메서드는<utility>
헤더에 정의되어 있으며, 레퍼런스 매개변수를 const 레퍼런스 버전으로 변환해준다.
익셉션(exception, 예외)
throw
구문으로 예외를 발생시키고, 즉시 실행을 중단시킨다.- 익셉션은 const 레퍼런스로 잡는 것이 바람직하다.
- 익셉션이 발생하는 함수는
try/catch
구문으로 잡아주면 된다. - 표준 라이브러리의 익셉션 클래스의
what()
메서드는 익셉션에 대한 간략한 설명이 담긴 스트링을 반환한다.try { ... } catch (const invalid_argument& Exception) { cout << "{ } "<< Exception.what(); }
- 위 코드에서 예외 발생 전까지는 try내 구문이 실행 된다.
타입 앨리어스(type alias)
- 기존에 선언된 타입에 다른 이름을 붙이는 것.
using IntPtr = int*;
의 경우 int*라는 타입 선언을 IntPtr이란 새 이름으로 부르게 된다.int* P1; IntPtr P2;
- P1과 P2의 타입은 똑같다.
- 표준라이브러리의 string은
using string = basic_string<char>;
의 결과다.
typedef
- 타입 앨리어스는 C++11에 도입되었는데, 이전까지 사용되던 애다.
- 선언하는 순서가 반대라서 헷갈린다.
typedef int* IntPtr;
- 타입 앨리어스를 쓰자.
타입 추론(type inference)
- 표현식의 타입을 컴파일러가 스스로 알아내는 기능
auto
와decltype
이 있다.
auto 키워드를
- 사용 처
- 함수의 리턴 타입 추론
- 구조적 바인딩에 사용
- 표현식의 타입 추론
- non-type 템플릿 매개변수의 타입을 추론하는 데 사용
- 축약 함수 템플릿(abbreviated function template)에 사용
- decltype(auto)에 사용
- 함수에 대한 또 다른 문법으로 사용
- 제네릭 람다 표현식에서 사용
- auto를 표현식 추론에 사용하면 레퍼런스와 const가 제거되어 값이 복제되므로,
- 그러길 원하지 않으면 const와 &를 붙여주자.
const auto& F1 { Foo() };
- 포인터를 다룰 때는
auto*
를 사용하는 것이 좋다.- 포인터임을 명확히 드러내 주고,
- auto, const, 포인터를 함께 쓸 때 발생하는 이상한 동작도 방지 가능하다.
```cpp // 둘 모두 int* const 이다. // 포인터가 가리키는 대상이 아닌 포인터를 보호한다. const auto P1 { &I }; auto const P2 { &I };
// 포인터가 가리키는 값을 보호한다. const auto* P3 { &I }; auto* const P4 { &I };
- 초기화 구문
- 복제 리스트 초기화(copy list initialization)
- ```T Obj = { Arg1, Arg2, ... };```
- 직접 리스트 초기화(direct list initialization)
- ```T Obj { Arg1, Arg2, ... };```
### decltype 키워드
- 인수로 전달한 표현식의 타입을 알아낸다.
- 레퍼런스나 const를 제거하지 않는다.
- 템플릿 사용할 때 효과적이다.
```cpp
int X { 123 };
decltype(X) Y { 456 };
- 컴파일러는 Y의 타입이 X의 타입인 int라고 추론한다.
Leave a comment