7 분 소요

타입 카테고리

클래스/구조체/공용체메모리 정렬 방식에 따라서 다른 언어와 데이터가 호환 될 수도 있고, memcpy()등으로 쉽게 복사가 가능할 수도 있습니다.

C++11 부터는 이러한 호환성의 체계를 수립하기 위해, 각 타입의 카테고리를 규정하고 해당 카테고리에 속하는지 확인 할 수 있는 is_trivial<>, is_standard_layout<>, is_pod<>를 제공합니다.(타입 특성(type_traits) 참고)

스칼라 타입

하나의 값만 저장하는 기본 타입들입니다.

  1. int, char, long, float, double
  2. 열거형
  3. 포인터
  4. 멤버에 대한 포인터
  5. nullptr_t

Trivial 타입(간단한 타입)

인접한 메모리 영역에 멤버들이 할당되며, memcpy()로 복사 가능한 타입입니다. 멤버들은 public, protected, private접근 지정될 수 있습니다.

표준 레이아웃 타입

다른 언어와 통신하는데 유용한 타입으로서, 인접한 메모리 영역에 멤버들이 할당되며, memcpy()로 복사 가능한 타입입니다. 멤버들은 public, protected, private 중 모두 동일한 것으로 접근 지정되어야 합니다.

POD 타입(Plan Old Data)

Trivial 타입이면서 표준 레이아웃인 타입입니다. 메모리 레이아웃은 연속적이며, C언어와 데이터를 직접 교환하여 사용할 수 있습니다.

리터럴 타입

리터럴 타입은 컴파일 타임에 상수로 사용될 수 있는 타입입니다.

집합 타입

집합 타입은 여러 데이터를 저장하는 개체입니다.

using을 이용한 타입 별칭

기존에는 타입 별칭을 위해 typedef를 사용했었는데요(타입 별칭 참고),

C++11 부터는 using을 이용한 타입 별칭이 추가되었습니다.

using을 이용한 타입 별칭은 다음과 같이 typedef와 별칭의 순서가 다릅니다만, 좀 더 직관적입니다.

1
2
typedef unsigned long ulong;
using ulong_11 = unsigned long; // typedef와 순서가 반대입니다.

함수 포인터인 경우는 훨씬 더 직관적입니다.

1
2
typedef void (*Func)(int); 
using Func_11 = void (*)(int);

클래스 템플릿 별칭

using을 이용한 타입 별칭클래스 템플릿 별칭도 지원합니다.

typedef를 사용하여 별칭을 작성하면, 템플릿의 경우 구체화된 타입으로만 별칭이 작성되어 향후 다른 타입으로 변경할 수 없지만,

1
2
3
4
5
// typedef는 템플릿의 구체화된 타입만 지원합니다.
typedef std::vector<int> MyVector;

MyVector v; // std::vector<int> 타입입니다. 
MyVector<char> v2; // (X) 컴파일 오류. 다른 타입으로 정의할 수 없습니다.

using을 사용하면 클래스 템플릿 별칭을 이용해서 여러가지 타입으로 템플릿 인스턴스화를 할 수 있습니다.

1
2
3
4
5
6
// using은 템플릿을 지원합니다.
template<typename T>
using MyVector_11 = std::vector<T>; 

MyVector_11<int> v1; // std::vector<int> 타입입니다.
MyVector_11<char> v2; // std::vector<char> 타입입니다.

nullptr

기존에는 널 포인터를 표현하기 위해 0이나 NULL(#define NULL 0)을 사용했는데요(널 포인터 참고),

C++11 부터는 nullptr 리터럴이 제공됩니다.

1
2
3
int* ptr1 = 0;
int* ptr2 = NULL;
int* ptr3_11 = nullptr; // C++11

0이나 NULL은 사실 포인터가 아니라 정수이기 때문에 오버로딩된 함수 결정int 타입이 선택됩니다.

다음의 ptr1, ptr2, ptr3는 모두 int*여서 f(int*)가 호출되는데요,

1
2
3
4
5
6
7
8
9
10
int f(int) {return 1;} // #1
int f(int*) {return 2;} // #2

int* ptr1 = 0;
int* ptr2 = NULL;
int* ptr3_11 = nullptr;

EXPECT_TRUE(f(ptr1) == 2); // int* 이므로 f(int*) 호출
EXPECT_TRUE(f(ptr2) == 2); // int* 이므로 f(int*) 호출
EXPECT_TRUE(f(ptr3_11) == 2); // int* 이므로 f(int*) 호출

auto를 사용한다면 0NULL은 다음처럼 초기값에 따라 int와 같은 정수형으로 추론되어(auto 참고), f(int)가 호출되는 문제가 있습니다. 포인터인데, 정수로 추론되니 뭔가 사이드 이펙트가 발생할 우려가 있죠. 따라서, nullptr을 사용하시는게 좋습니다.

1
2
3
4
5
6
7
8
// auto를 사용하면
auto ptr1 = 0; // (△) 비권장. int
auto ptr2 = NULL; // (△) 비권장. long long
auto ptr3_11 = nullptr; // nullptr_t

EXPECT_TRUE(f(ptr1) == 1); // (△) 비권장. f(int) 호출  
EXPECT_TRUE(f(ptr2) == 1); // (△) 비권장. f(int) 호출
EXPECT_TRUE(f(ptr3_11) == 2); // f(int*) 호출

또한 다음과 같은 구문을 만났을때 nullptr을 사용하는게 가독성이 좋습니다.

1
2
3
auto result_11 = Func();
if (result_11 == 0) {} // (△) 비권장. result_11이 정수인지 포인터인지 불명확 합니다.
if (result_11 == nullptr) {} // result_11은 포인터라는 것이 좀더 명확합니다.

nullptr_t

nullptr_tnullptr을 저장할 수 있는 타입이며, 어떠한 포인터로도 암시적으로 변환될 수 있는 타입입니다. 크기는 sizeof(void*)와 동일합니다.(decltype()참고)

1
using nullptr_t = decltype(nullptr);

long long

기존 long은 시스템 비트수에 따라 변하는데요(기본 타입 참고),

C++11 부터는 long long을 제공하며 최소 8byte를 보장합니다.

항목 내용 용량
long int보다 크거나 같은 정수 16bit : 4byte,
32bit : 4byte,
64bit : 8byte
long long (C++11~) long보다 크거나 같은 정수 최소 8byte
1
unsigned long long val_11{18446744073709550592ull}; 

char16_t 와 char32_t

유니코드는 전 세계의 모든 문자에 고유 숫자를 부여한 코드 체계인데요, 기존 wchar_t는 시스템에 따라 2byte 또는 4byte로 가변적이어서(기본 타입 참고), 유니코드 처리가 어려웠습니다.

C++11 부터는 유니코드 지원을 위해 UTF-16 인코딩 문자를 저장할 수 있는 char16_tUTF-32 인코딩 문자를 저장할 수 있는 char32_t가 추가되었습니다.

항목 내용 용량
char 1byte 문자 1byte
wchar_t 와이드 문자 시스템의 비트수에 따라 다르며, 대부분 2byte 또는 4byte.
Windows는 2byte
char16_t (C++11~) UTF-16 인코딩 문자 16bit(2byte) 보다 크거나 같음
char32_t (C++11~) UTF-32 인코딩 문자 32bit(4byte) 보다 크거나 같음

(C++20~) char8_t 타입이 추가되어 UTF-8 인코딩 문자를 지원합니다.

유니코드 리터럴

기존에는 바이트 문자열와이드 문자열만 지원했는데요(문자열 상수 참고),

C++11 부터는 UTF-8 인코딩, UTF-16 인코딩, UTF-32 인코딩유니코드 문자 및 문자열을 지원합니다.(UTF-8 인코딩 문자열은 C++11에 추가됐지만, UTF-8 인코딩 문자는 C++17에 추가되었습니다.)

항목 내용
1byte 문자열 “abc”
와이드 문자열 L”abc한글”
UTF-8 인코딩 문자열 (C++11~) u8"abc한글"
UTF-16 인코딩 문자열 (C++11~) u"abc한글"
UTF-32 인코딩 문자열 (C++11~) U"abc한글"
UTF-8 인코딩 문자 (C++17~) u8'한'
UTF-16 인코딩 문자 (C++11~) u'한'
UTF-32 인코딩 문자 (C++11~) U'한'
1
2
char16_t ch_11 = u'한';
const char16_t* str_11 = u"abc한글";

Raw String 리터럴

기존 문자열은 개행을 하기 위해 다음처럼 이스케이프 문자(\r\n)를 사용해야 했는데요(이스케이프 문자 참고),

1
2
const char* str = "abc\r\ndef";
std::cout << str << std::endl;
1
2
abc
def

C++11 부터는 R"()"()안에 표기된 그대로를 문자열로 처리해 줍니다. 이스케이프 문자도 그대로 출력합니다.

1
2
3
4
// 이스케이프 문자와 개행을 소스코드에 기재된 그대로 출력합니다.
const char* str_11 = R"(abc\r\n
def)";     
std::cout << str_11 << std::endl;
1
2
abc\r\n
def

"()" 자체를 출력할 때에는 파싱 오류가 날 수 있으므로, 다음 예의 aaa처럼 임의 구분자를 지정하여 사용할 수 있습니다.

1
2
3
4
// 임의 구분자 aaa 사용
const char* str_11 = R"aaa(abc"()"
def)aaa";     
std::cout << str_11 << std::endl;
1
2
abc"()"
def

(C++14~) 이진 리터럴

0b, 0B 접두어를 이용하여 이진수 상수를 표현할 수 있습니다.

1
2
3
4
5
6
7
int val1_14 = 0b11;
int val2_14 = 0b1111;
int val3_14 = 0B11111111;

EXPECT_TRUE(val1_14 == 0x03);
EXPECT_TRUE(val2_14 == 0x0F);
EXPECT_TRUE(val3_14 == 0xFF);

(C++14~) 숫자 구분자

작은 따옴표(')를 숫자 사이에 선택적으로 넣을 수 있습니다.

큰 숫자인 경우 가독성이 좋아집니다.

1
2
3
4
int val = 1000000;
int val_14 = 1'000'000;

EXPECT_TRUE(val == val_14);

(C++17~) 16진수 부동 소수점 리터럴

기존에는 16진수 정수 리터럴만 제공했는데요,

1
int val = 0x1a; // 16진수. x 또는 X. a~f 또는 A~F

C++17 부터는 16진수 부동 소수점 리터럴도 제공합니다.

0x[소수점 이상 16진수값].[소수점 이하 16진수값]p[십진수 지수]

가수 p또는 P는 10진수 2입니다.

예를들어 0xA.9p11은,

  1. A : 10진수 10
  2. 9 : 1/16 * 9 = 0.5625
  3. p11 : 2의 11승 == 2048

이어서

10.5625 * 2 ^ 11 = 21632

입니다.

1
2
3
4
// A(10진수 10), 9(1/16 * 9 = 0.5625), (p11은 2의 11승 == 2048)
// 즉, 10.5625 * 2 ^ 11 =  21632
float floatVal_17 = 0xA.9p11; // 21632 
EXPECT_TRUE(floatVal_17 == 21632.0);

(C++20~) char8_t

C++20 부터는 char8_t 타입이 추가되어 UTF-8 인코딩을 지원합니다.

char와 크기가 같고(적어도 1byte입니다.), 부호 처리도 동일하지만, UTF-8 인코딩 문자를 처리하는 별개의 타입입니다.

그런데, UTF-8 인코딩에서 영문자는 1byte 이지만, 한글등 다국어 문자는 여러 바이트를 사용할 수도 있죠. 예를 들어 한글 0xEA, 0xB0, 0x80의 3개의 byte가 필요합니다.(UTF-8 인코딩 참고)

따라서 char8_t은 영문자는 저장할 수 있지만, 한글등 다국어 문자는 저장할 수 없습니다.

1
2
3
4
5
6
// UTF-8 에서 영문 1글자는 1byte 입니다.
char8_t en_20 = u8'a';

// UTF-8에서 한글 1글자는 3byte입니다. 
// 따라서 문자 1개를 1byte로는 저장할 수 없습니다.
char8_t kr_20 = u8'가'; // (X) 컴파일 오류

그래서, 안타깝지만, 다국어 문자를 저장하려면 다음과 같이 문자열 상수로 저장해야 합니다.

1
2
char8_t arr_20[] = u8"가"; // 한글 1글자는 UTF-8로 3byte입니다.
EXPECT_TRUE(sizeof(arr_20) == 4); // 널문자 포함하여 4byte입니다.

(C++20~) 정수에서 2의 보수 범위 보장

기존에는 부호있는 정수의 표현 범위를 1의 보수를 보장하였으나, 실제로는 모든 컴파일러가 2의 보수를 표현하였습니다.

C++20 부터는 정수에서 2의 보수 범위를 보장합니다.

항목 내용
1의 보수 1byte로 -127 ~ 127 표현
2의 보수 1byte로 -128 ~ 127 표현

태그:

카테고리:

업데이트:

댓글남기기