13 minute read

‘전문가를 위한 C++ - Marc Gregoire 지음, 남기혁 옮김’ 책을 참고하여 작성한 포스트입니다.


C++의 기초

전처리 지시자

빌드(build)

  • C++로 작성된 소스 코드를 프로그램으로 만드는 작업으로, 세 단계를 거친다.
    1. 전처리(preprocess) 단계
      • 소스 코드에 담긴 메타 정보를 처리한다.
    2. 컴파일(compile) 단계
      • 소스 코드를 머신이 읽을 수 있는 객체 파일로 변환
    3. 링크(link) 단계
      • 앞서 변환한 여러 객체 파일을 애플리케이션으로 엮음

지시자(directive)

  • 전처리기에 전달할 사항을 표현함
  • # 문자로 시작함.
    • #include [file]
      • 지정한 ‘파일’의 내용을 지시자 위치에 넣음.
      • 다른 곳에 정의된 함수를 사용할 목적으로 해당 함수의 선언문이 담긴 헤더 파일을 가져온다.
    • #define [key] [value]
      • 코드 내에 key 들을 value 로 모두 바꿈.
    • #ifdef [key] #endif, #ifndef [key] #endif
      • key가 #define문으로 정의되어 있으면 (후자는 그러지 않은 경우) 묶인 코드 블럭을 포함 시킴.
    • #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 데이터 멤버인 firstsecond로 접근 가능하다.

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)라고 한다.
  • 생성자로 데이터 멤버를 초기화 하는 방법은
    1. 생성자 초기자(constructor initializer)
      • 생성자 이름 뒤에 콜론을 붙여 표현
    2. 생성자의 본문에서 초기화

스코프 지정

  • 변수나 함수, 클래스 등에 접근할 때 가장 안쪽 스코프의 이름ㅂ터 검색하고, 없으면 바로 다음 바깥의 스코프를 검색하며, 글로벌 스코프(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으로 바뀜 
    
  • 위에서 XPtrXRef 타입이 달라 비교연산이 되지 않는다.
  • &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)

  • 표현식의 타입을 컴파일러가 스스로 알아내는 기능
  • autodecltype이 있다.

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라고 추론한다.

어느 정도 귬 있는 첫 C++ 프로그램

Leave a comment