모듈 임포트
- C++20부터 새롭게 추가된 대표적인 기능.
- 헤더 파일의 메커니즘을 완전히 대체한다.
- 헤더 파일은 일반적으로 전체 내용이 복사되어 컴파일 단계에서 소스 파일에 포함된다. 따라서 변경 사항이 있는 헤더 파일이 포함된 모든 소스 파일을 다시 컴파일해야 한다.
- 하지만 모듈은 필요한 기능만 가져와 사용할 수 있기 때문에 전체 내용이 복사되지 않는다. 이는 빌드 시간을 줄이고 의존성을 관리하기 쉽게 만든다.
export module employee;
export struct Employee
{
int salary;
}
I/O 스트림
- C++20부터는 스트링 포맷은 <format>에 정의된 std::format()으로 지정하는 방식을 권장한다.
std::cout << std::format("coding {}", 123) << std::endl
- endl은 성능에 영향을 미치기 때문에 루프와 같은 문장에서 남용하면 좋지 않다.
using
- 헤더 파일 안에서는 절대로 using 문을 작성하면 안 된다. 그러면 그 헤더 파일을 인클루드하는 모든 파일에서 using 문으로 지정한 방식으로 호출해야 하기 때문이다.
리터럴
- 숫자 리터럴에서는 자릿수 구분자를 사용할 수 있다.
12'345'678 0.123'456f
균일 초기화
- 유니폼 초기화.
- C++11 버전부터 도입되었다.
- C++11 이전에는 struct 타입 변수와 class 타입 변수를 초기화하는 방법이 서로 달랐다.
- 균일 초기화를 사용하면 축소 변환(좁히기)를 방지할 수 있다.
- 동적으로 할당되는 배열을 초기화할 때도 적용할 수 있다.
int* pArray = new int[] {0, 1, 2, 3};
- 생성자의 초기자에서 클래스 멤버로 정의한 배열을 초기화할 때도 사용할 수 있다.
Class MyClass
{
MyClass() : m_array {0, 1, 2, 3} {}
int m_array[4];
}
char/signed char/unsigned char
- char 타입은 signed char나 unsigned char와 다른 타입이다.
- char 타입은 문자만 표현해야 한다.
- char 타입의 경우 패리티 비트(통신 에러 검출을 위한 비트)와 2의 7승으로 아스키코드 문자를 표현하는 자료형이다.
- signed char는 부호 비트와 2의 7승으로 정수를 표현한다.
- unsigned char는 부호 없이 2의 8승으로 정수를 표현한다.
- std::byte를 사용하여 메모리의 한 바이트를 다룬다는 사실을 분명하게 표현할 수 있다.
std::byte b {42};
숫자 경곗값
- <limits>에서 제공하는 std::numeric_limits 클래스 템플릿을 사용한다.
numeric_limits<double>::max() numeric_limits<double>::min() numeric_limits<double>::lowest()
- 정수의 경우 최솟값(min)과 최젓값(lowest)가 같지만, 부동소수점수에서는 최솟값은 가장 작은 양의 값인 반면, 최젓값은 가장 작은 음수다.
연산자
- ^는 비트 단위 XOR(배타적 논리합) 연산을 수행한다.
- XOR 연산은 두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1을 계산한다.
열거 타입
enum class PieceType : unsigned long
{
King = 1;
Queen,
Rook = 10,
Pawn
};
using enum PieceType;
PieceType piece {King};
- 예전 방식의 열거 타입은 값이 항상 정수로 해석되기 때문에 본의 아니게 열것값을 전혀 다른 열거 타입과 비교할 수도 있고, 함수를 호출할 때 엉뚱한 열거 타입 값을 전달하는 오류가 발생할 수 있다.
조건문
if의 초기자
- if 문 안에 초기자를 넣을 수 있다.
if(<초기자>; <조건문>)
[[fallthrough]] 어트리뷰트는 switch 문에서 의도적으로 폴스루 방식으로 작성했다고 컴파일러에게 알려준다.
3방향 비교 연산자
- 우주선 연산자.
- 주어진 표현식의 평가 결과가 비교 대상이 되는 값과 같은지 아니면 그보다 크거나 작은지 알려준다. 이 연산자는 true나 false가 아닌 세 가지 결과 중 하나를 알려줘야 하기 때문에 bool 타입을 리턴할 수 없다.
<=>
auto
- auto 키워드를 함수 리턴 타입에 적으면 return 문에 나온 표현식의 타입에 따라 리턴 타입을 추론한다.
- 함수 안에 return 문이 여러 개가 있다면 모두 타입이 같아야 한다.
- 리턴값이 재귀 호출일 수도 있는데, 이때는 재귀 호출이 아닌 return 문도 반드시 함께 있어야 한다.
어트리뷰트
- 컴파일러는 어트리뷰트 정보를 사용해 정보 메시지를 생성하거나 특정 사용 코드를 컴파일 할 때 특정 동작을 할 수 있다.
[[nodiscard]]
- 어떤 값을 리턴하는 함수에 대해 지정한다면 컴파일러는 이 함수가 호출될 때 리턴값에 아무런 작업을 하지 않으면 경고 메시지를 출력한다.
- C++20부터 이유를 설명하는 스트링을 추가할 수 있다.
[[nodiscard("Some explanation")]] int func();
[[maybe_unused]]
- 뭔가 사용하지 않았을 때 컴파일러가 경고 메시지를 출력하지 않도록 설정하는 데 사용된다.
[[noreturn]]
- 함수에 지정하면 호출 지점으로 다시 돌아가지 않는다.
- 주로 프로세스나 스레드 종료과 같이 뭔가가 끝나게 만들거나, 익셉션을 던지는 함수가 여기에 해당한다.
[[deprecated]]
- 지원 중단된 대상임을 지정하는 데 사용된다.
- 즉, 현재 사용할 수는 있지만 권장하지 않는 대상임을 표시한다.
[[deprecated("Unsafe method, please use xyz")]] void func();
[[likely]]와 [[unlikely]]
- 이 어트리뷰트를 이용하여 if와 switch 문에서 수행될 가능성이 높은 브랜치를 표시할 수 있다.
- 하지만 최신 컴파일러와 하드웨어는 브랜치 예측 능력이 상당히 뛰어나기(대단하다.) 때문에 필요한 경우는 드물다.
std::optional
- <optional>에 정의된 std::optional은 특정한 타입의 값을 가질 수도 있고, 아무 값도 가지지 않을 수도 있다.
- 아무 값도 가지지 않은 상태에서 디폴트 객체를 가질 필요가 없기 때문에 생성자가 호출되지 않는다.
optional<int> func(bool giveIt)
{
if(giveIt)
return 42;
return nullout;
}
int main()
{
optional<int> data1 {func(true)};
optional<int> data2 {func(false)};
cout << "data1.has_value = " << data1.has_value() << endl;
cout << data1.value() << endl;
cout << *data1 << endl;
cout << data2.value_or(0) << endl;
return 0;
}
- optional에 레퍼런스는 담을 수 없다. 대신 포인터를 저장할 수는 있다.
구조적 바인딩
- 구조적 바인딩을 이용하면 여러 변수를 선언할 때 array, struct, pair 등에 담긴 원소들을 이용하여 변숫값을 한꺼번에 초기화할 수 있다.
array values {11, 22, 33};
auto [x, y, z] {values};
pair myPair {"hello", 5};
auto [theString, theInt] {myPair};
지정 초기자
- C++20부터 도입.
- 묶음 타입의 데이터 멤버를 초기화하는 데 사용된다.
묶음 타입: public 데이터 멤버만 갖고, 사용자 정의 생성자나 상속된 생성자가 없고, virtual 함수도 없으며, virtual, private, protected 베이스 클래스도 없는 배열 타입의 객체나 구조체 객체, 클래스 객체
- 점 뒤에 데이터 멤버의 이름을 적는 방식으로 표기한다.
- 반드시 데이터 멤버가 선언된 순서를 따라야 한다.
struct Employee
{
char initial;
int employeeNumber;
int salary {75'000};
};
Employee anEmployee {
.initial = 'A',
.salary = 80'000
};
- 구조체에 멤버를 추가하더라도 지정 초기자를 이용한 기존 코드는 그대로 작동한다는 것이 장점이다. 새로 추가된 데이터 멤버는 디폴트값으로 초기화된다.
스택과 프리스토어
- C++ 애플리케이션에서 사용하는 메모리는 크게 스택과 프리스토어로 나뉜다.
스택
- 현재 실행 중인 함수에서 다른 함수를 호출하면 스택 프레임이 올라온다.
- 스택 프레임은 각 함수마다 독립적인 메모리 공간을 제공한다는 점에서 유용하다.
- 스택에 할당된 변수는 프로그래머가 직접 할당 해제(삭제)할 필요 없이 자동으로 처리된다.
프리스토어
- 프리스토어는 현재 함수 또는 스택 프레임과는 완전히 독립적인 메모리 공간이다.
- 함수가 끝난 후에도 그 안에서 사용하던 변수를 계속 유지하고 싶다면 프리스토어에 저장한다.
const의 다양한 용도
const 상수
- define은 전처리기가 처리하고, const는 컴파일러가 처리한다. 즉, define 문은 단순히 텍스트 매칭 작업을 수행하는 반면, const는 C++ 코드 문맥 안에서 컴파일러가 평가한다. 그러므로 const로 정의할 대상에 타입이나 스코프를 적용할 수 있다는 장점이 있다.
const 포인터
- 포인터 변수나 변수가 가리키는 값을 const로 지정할 수 있다.
const 매개변수
- C++에서는 비 const 변수를 const 변수로 캐스트할 수 있다. 이렇게 하면 다른 코드에서 변수를 변경하지 못하게 보호할 수 있다.
- 함수를 호출할 때 전달한 매개변수가 변경되지 않도록 보장하고 싶다면 const 매개변수를 받도록 함수를 작성하면 된다.
const 메서드
- const는 클래스 메서드에도 지정할 수 있다.
- 해당 클래스의 데이터 멤버를 수정할 수 없게 만든다.
- 이런 멤버 함수를 '인스펙터'라고 부른다. 비 const 멤버 함수를 '뮤테이터'라고 부른다.
constexpr 키워드
- 상수 표현식.
- 컴파일 시간에 평가되는 표현식이다.
- constexpr 함수는 다른 constexpr 함수를 호출할 수 있지만, constexpr이 아닌 함수는 호출할 수 없다.
- constexpr 생성자를 정의하면 사용자 정의 타입에 대한 상수 표현식 변수를 만들 수 있다.
Class Rect
{
public:
constexpr Rect(size_t width, size_t height)
: m_width(width), m_height(height) {}
constexpr size_t getArea() const {return m_width * m_height;}
private:
size_t m_width{0}, m_height{0};
};
int main()
{
constexpr Rect r {8, 2};
int myArray[r.getArea()];
return 0;
}
consteval 키워드
- constexpr 키워드는 함수가 컴파일 시간에 실행될 수도 있다고 지정할 뿐 반드시 컴파일 시간에 실행되도록 보장하는 것은 아니다.
constexpr double inchToMm(double inch) {return inch * 25.4;}
constexpr double const_inch {6.0};
double dynamic_inch {8.0};
- 함수가 항상 컴파일 시간에 평가되도록 보장하고 싶다면 C++20부터 제공하는 consteval 키워드로 해당 함수를 즉시 실행 함수로 만든다.
consteval double inchToMm(double inch) {return inch * 25.4;}
constexpr double const_inch {6.0};
double dynamic_inch {8.0};