#!if 문서명2 != null
, [[]]#!if 문서명3 != null
, [[]]#!if 문서명4 != null
, [[]]#!if 문서명5 != null
, [[]]#!if 문서명6 != null
, [[]]| 프로그래밍 언어 문법 | |
| {{{#!folding [ 펼치기 · 접기 ] {{{#!wiki style="margin: 0 -10px -5px; word-break: keep-all" | 프로그래밍 언어 문법 C(포인터 · 구조체 · size_t) · C++(이름공간 · 클래스 · 특성 · 상수 표현식 · 람다 표현식 · 템플릿/제약조건/메타 프로그래밍) · C# · Forth · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript(표준 내장 객체, this) · Haskell(모나드) · 숨 |
| 마크업 언어 문법 HTML · CSS | |
| 개념과 용어 함수(인라인 함수 · 고차 함수 · 콜백 함수 · 람다식) · 리터럴 · 문자열 · 식별자(예약어) · 상속 · 예외 · 조건문 · 반복문 · 비트 연산 · 참조에 의한 호출 · eval · 네임스페이스 · 호이스팅 | |
| 기타 #! · == · === · deprecated · GOTO · NaN · null · undefined · S-표현식 · 배커스-나우르 표기법 · 콰인(프로그래밍) | }}}}}} |
| 프로그래밍 언어 목록 · 분류 · 문법 · 예제 |
1. 개요2. Hello, world!3. 프로그램 진입점4. 원시 자료형
4.1. Truthy / Falsy
5. 함수 (Function)6. 이름공간 (Namespace)7. 특성 (Attribute)8. 구조체 (Struct)9. 자료형 한정자10. 참조자11. 포인터 (Pointer)12. 문자열 리터럴 (String Literal)13. Ranged For-Loop14. 분기문 초기화 구문15. using16. 클래스 (Class)17. 메모리 할당18. 부패19. decltype(expression)20. 견본 자료형 지시자21. 상수 표현식 (Constant Expression)22. 템플릿 (Template)23. 템플릿 제약조건 (Constraint)24. 람다 표현식 (Lambda Expression)25. 메타 프로그래밍 (Meta Programming)26. 코루틴 (Coroutine)27. 둘러보기1. 개요
C++의 문법에 대하여 전반적으로 설명하는 문서이다. C++의 문법은 기본적으로 C(프로그래밍 언어)를 기본으로 깔고가는 요소들이 많으므로 용이하 이해를 위해서는 C(프로그래밍 언어)/문법 문서와 비교하여 참조하는 것이 좋다. 하지만 C언어에 더하여 클래스 사용법만 안다고 해서 C++를 잘 아는건 아니다. 일단 C++11 이후로 추가된 기능이 엄청나게 많기 때문이다. 템플릿 공부도 많이 하는 것이 추천된다.2. Hello, world!
#!syntax cpp
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
고전적인 방식으로는 <iostream> 모듈의 std::cout 객체와 << 연산자를 통한 출력을 사용할 수 있다. 또한 C언어처럼 <cstdio> 혹은 <stdio.h>를 통해 printf 함수도 사용 가능하다.#!syntax cpp
#include <print>
// import <print>;
// MSVC를 제외한 gcc와 clang에서는 모듈을 아직 제대로 지원하지 않는다.
int main()
{
std::println("Hello, world!");
std::println("{}, {} {}!", "Hello", "world", "again");
return 0;
}
C++23 이후에 <print> 모듈의 print, println 함수를 사용할 수 있게 되었다.3. 프로그램 진입점
// (1) 매개변수가 없는 main 함수 // (2) 매개변수를 받는 main 함수 [include(틀:C++ 요소, kw1=int, body_f=main, arg1_t_kw=int, arg1_param=argc, arg2_t_kw=char, arg2_t_post=*, arg2_param=argv[], body_bopen=1)] |
main 함수일반적으로 C++의 프로그램은 항상 진입점 함수
main을 요구한다. main 함수는 C++ 프로그램의 시작과 끝을 담당한다. 사용자가 main 함수의 중괄호 안에 코드를 작성하고 컴파일하면 해당 코드를 실행한다.표준에 따르면
main 함수를 정의하는 방식은 두 가지가 있다. 첫번째는 아무런 매개변수가 없이 소괄호만 있는 경우다. 대부분의 경우 이걸로 충분하다. 두번째는 외부에서 문자열로 된 매개변수가 전달된 main 함수다. 많은 운영체제에서는 프로그램을 실행할 때 외부에서 매개변수를 전달할 수 있다. 가령 Windows에서는 프로그램의 바로가기를 만들고 프로그램 경로 뒤에 -path "C:\\Program Files\\Microsoft"처럼 매개변수를 전달할 수 있다.이 함수는 프로그램 종료 시엔 정수로 된 종료 부호를 반환한다. 가령 콘솔에서 실행하는 프로그램은 프로그램이 반환한 부호를 확인할 수 있다. 이 부호는 운영체제마다 다른 의미를 갖고 있는데, 보통
0을 반환하면 main 함수의 실행에 오류가 없이 성공적으로 종료되었다는 뜻이다. 그러나 사용자는 return 0;를 반드시 쓰지 않아도 된다. 특별한 규칙으로서, 반환 구문이 없어도 컴파일러가 알아서 처리해준다.4. 원시 자료형
| 자료형 이름 | 설명 | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| true와 false 두 가지 진릿값을 표현하는 논리 자료형[1]으로, 크기는 구현에 따라 달라질 수 있으나, 대부분의 컴파일러에서 1바이트로 구현된다. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| 문자(character)를 저장하기 위해 정의된 자료형이자, C++에서 메모리의 최소 단위(바이트)를 나타내는 특별한 정수형으로, 항상 1바이트 크기이지만 8비트라는 보장은 없다. 기본적으로 x86/x64에서는 signed char, ARM/PowerPC에서는 unsigned char로 동작한다. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| 멀티바이트 문자를 나타내는 자료형. 보통 아스키 코드 외의 문자를 표현하는데 사용한다. Windows 환경에서는 2바이트, Linux 환경에서는 4바이트다. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 2바이트 정수를 나타내는 자료형. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| 부호 없는 2바이트 정수를 나타내는 자료형. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| 4바이트 정수를 나타내는 자료형. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| 부호 없는 4바이트 정수를 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 4바이트 정수를 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 부호 없는 4바이트 정수를 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 8바이트 정수를 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 부호 없는 8바이트 정수를 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 4바이트 부동 소수점을 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| 8바이트 부동 소수점을 나타내는 자료형. | ||||||||||
| <bgcolor=#20b580> | |||||||||||
| Windows에서는 8바이트 부동 소수점을 나타내며, Linux에서는 10바이트에서 16바이트 사이의 부동 소수점을 나타내는 자료형. | ||||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| UTF-8 표준의 1바이트 문자를 나타내는 자료형. | C++20 | |||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| UTF-16 표준의 문자 단위를 나타내는 자료형. | C++11 | |||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
| UTF-32를 담을 수 있도록 4바이트 문자를 나타내는 자료형. | C++11 | |||||||||
| |||||||||||
| <bgcolor=#20b580> | |||||||||||
C언어의 자료형을 그대로 사용할 수 있다. C++에서는 C언어에서는 파생 자료형으로 존재하는
size_t, rsize_t, ptrdiff_t 등을 공식 이름공간인 std 안에서도 제공한다. 자세한 내용은 표준 라이브러리의 <cstddef>를 참조하자. 또한 플랫폼 간의 호환성을 위하여 고정 크기의 자료형도 제공한다. 이에 대해서는 표준 라이브러리의 <cstdint>와 <stdfloat>에서 확인할 수 있다.4.1. Truthy / Falsy
C언어에서는 1이 참(True), 0이 거짓(False)로 대응되는 것이 기본적인 사용법이다. 또한0이 아닌 숫자도 모두 참으로 간주된다. C++에서도 마찬가지이며, 여기에 진리값(bool) 자료형이 도입되면서 true와 false를 이용할 수 있다.한편 C언어에서 진리값(
bool)을 사용하려면 C17 버전까지는 <stdbool.h>를 삽입하고 _Bool, 0, 1을 써야 했으나, C23 버전에서 bool, true, false가 정식으로 편입되면서 원시 자료형에서는 C언어와 C++의 차이가 아예 사라졌다.5. 함수 (Function)
|
함수는
main 함수 말고도 사용자가 직접 만들 수 있다. 여기서 말하는 함수는 C++에서 실행할 수 있는 코드의 집합이자 하나의 분기점이다. 이렇게 작성해 두었다가 함수의 이름과 함께 ()[2]를 붙여 실행할 수 있다. 함수가 실행되면 결과 값을 반환하고 본래의 호출 스택으로 되돌아온다. 앞서 살펴본 main 함수는 사용자가 이용할 수 있는 함수 중의 하나다. main 함수는 프로그램 안에서 유일해야 한다는 제약만 빼면 마찬가지로 정수를 반환하는 보통의 함수와 다를 것이 없다. 개발자는 함수를 통해 코드를 쉽게 정리하고 재사용할 수 있다. 궁극적으로 더 효율적이고 유지보수가 용이한 프로그램을 만들 수 있다.함수의 정의엔 기본적으로 반환 자료형, 식별자, 소괄호가 필요하며 그리고 선택적으로 매개변수를 추가할 수 있다. 또한 연결성 지시자(
static, extern), 예외 사양(nothrow/noexceptC++11), 평가 지시자(constexprC++11, constevalC++20), 혹은 특성C++11을 부착할 수도 있다.5.1. 반환 자료형
|
main 함수는 32비트 정수를 반환하는 규칙이 있으므로 int를 함수의 이름 앞에 기입해야 했다. 만약 함수가 아무것도 반환하지 않고, 어떤 코드만 실행한다면 void를 기입하면 된다.5.1.1. return
|
return 명령어return은 함수를 종료하고 이전 스택으로 되돌아가거나, 값을 반환하는 명령어다. main 함수에는 return 0;을 쓰지 않아도 된다는 특별한 규칙이 있다. 하지만 이 규칙은 사용자가 만든 함수에는 적용되지 않는다. 사용자는 반환 자료형만 기입해선 안되고 함수 내부에서 return 반환값; 구문을 실행해줘야 한다. |
void 함수라면 실행할 필요는 없다. 만약 void 함수에서 return;을 실행하면 함수가 즉시 종료된다. 특이한 점은 아무것도 반환하지 않는 함수에서 어떤 아무것도 반환하지 않는 함수 실행 구문을 return하는 건 문제가 되지 않는다. 해당 함수가 실행되고나서 함수가 즉시 종료된다.5.2. 매개변수
|
C++에는 함수 내부로 값을 전달하는 기능이 있다. 이렇게 함수 외부에서 전달된 값이 들어오는 변수를 매개변수라고 한다. 함수의 지역 변수(Local Variable)로 사용하기 위해 값을 전달하는 것이다. 함수를 호출할 때 소괄호 안에 전달할 인자를 기입하면 된다. 이렇게 전달한 인자는 함수의 매개 변수에 값이 들어온다.
#!syntax cpp
void Function(int value)
{
std::cout << value << std::endl;
}
int main()
{
// "10" 출력
Function(10);
}
상기 코드는 정수를 매개변수로 받아서 출력하는 예제다.#!syntax cpp
void Function(int lhs, int rhs)
{
std::cout << lhs + rhs << std::endl;
}
int main()
{
// "150" 출력
Function(100, 50);
}
매개변수는 여러개를 선언할 수 있다.- <C++ 예제 보기>
#!syntax cpp void increment1(int x) { ++x; } void increment2(int x) { ++x; } void increment3(int x, int y) { x += y; } int main() { int a = 100; int b = 500; // (1) increment1(a); // (2) increment1(7124820); // (3) increment2(a); // (4) increment2(b); // (5) increment3(a, 9058142); // (6) increment3(a, b); std::cout << "a의 값: " << a << std::endl; // 100을 출력함 std::cout << "b의 값: " << b << std::endl; // 500을 출력함 }
increment1, increment2, increment3를 통해 변수 a와 b를 조작하려고 시도하는 예제다. 하지만 a와 b의 값은 변하지 않는다. 함수 increment1, increment2, increment3에 전달된 a와 b는 각각 함수의 매개변수에 값만 전달되었기 때문이다. 함수 안에서는 매개변수인 x와 y를 수정하기 때문에 원래 변수 a, b에는 아무런 영향을 주지 못한다.5.3. 오버로딩
|
C++에서는 사용자가 같은 이름의 함수를 여러 개 정의할 수 있다. 함수 오버로딩 혹은 함수 중복 정의 기능은 언어에서 가장 발전되었다고 볼 수 있는 기능이다. 원래 C언어에서는 모든 함수의 이름이 무조건 달라야 해서 동일한 동작을 수행하는 함수들도 다른 이름으로 구별해야하는 불편함이 있었다. 예를 들어서
int 또는 long long을 받아 문자열로 바꾸는 함수가 있다면, 그 함수의 이름은 ConvertIntToString(), ConvertLongLongToString() 따위로 분리해야 했었다. 또한 이름공간의 부재로 인한 식별자 부족 현상도 있었다. C++에서는 ConvertToString() 처럼 같은 양식의 식별자로 통일하고 어디에서나 일관적인 코드 작성이 가능해졌다.반환 자료형만 다르게는 만들지 못한다. 함수 오버로딩은 매개변수의 변형을 기준으로 함수를 구분한다. 즉 매개변수가 달라야 오버로딩으로 인정된다.
5.4. inline
|
inline 함수inline을 반환-자료형 앞에 붙이면 해당 함수의 쓰임새 부분이 함수의 코드 자체로 대체될 수 있음을 나타낸다 [3]. inline 함수는 정의와 선언이 같이 행해져야 한다. 정의가 없는 inline 함수는 컴파일 오류를 발생시킨다.이 기능이 적절하게 쓰이면 함수 호출 오버헤드를 줄이고, 호출 스택도 아낄 수 있어서 좋다. 심지어 최적화 과정에서 아예 함수의 코드를 날리고 결과값만 남길 수도 있다. 그렇지만 대부분의 현대 컴파일러는 알아서 처리를 해준다. 그래서 이 지시자의 의의는 코드를 읽는 다른 개발자들에게 이 함수가 인라이닝이 되도록 설계되었다는 것을 알리면서 컴파일러에게 더 적극적으로 인라이닝을 하라는 지시에 가깝다.
5.5. noexcept
|
noexcept를 통해 지정할 수 있다. 이를 통해 컴파일러에게 예외 검사를 배제하도록 지시할 수 있다. 예외를 던질지 말지는 사용자의 자유이지만, 확실하게 오류가 없는 함수라면 noexcept를 붙이면 된다.C++20부터는 noexcept(표현식)를 써서 선택적으로 예외 여부를 지정할 수도 있다. 이를 위해 표준에서는 <type_traits> 모듈에서 std::is_nothrow_*같은 명칭의 메타 함수를 제공하고 있다. noexcept(expr)안의 표현식은 상수 시간에 예외 여부만 평가되기 때문에 복잡한 코드가 달려있다고 성능에 문제는 생기지 않는다.6. 이름공간 (Namespace)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/이름공간#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/이름공간#|]] 부분을 참고하십시오.7. 특성 (Attribute)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/특성#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/특성#|]] 부분을 참고하십시오.8. 구조체 (Struct)
|
구조체는 C언어에서 여러 데이터를 한데 묶기 위해 고안된 복합 자료형이다. 사용자가 직접 제작하고 응용할 수 있는 자료형이다. 구조체를 만들면 기존 원시 자료형처럼 새로운 자료형이 생기는 것이다. 구조체를 만들면 기존의 원시 자료형의 경우와 같이 변수와 함수에 사용할 수 있다. 원시 자료형과 다른 점은 이렇게 만든 구조체 내부의 또다른 변수를 이용할 수 있다.
#!syntax cpp
struct Squirrel
{
int myAge;
std::string myName;
};
구조체를 정의할 때는 struct 예약어 뒤에 구조체의 이름을 붙이면 된다.#!syntax cpp
struct [[nodiscard]] Item
{
std::string myName;
bool isConsumable;
};
int main()
{
Item potion{};
potion.myName = "Potion";
potion.isConsumable = true;
// 특성 [[nodiscard]]의 효과
// 사용되지 않은 값에 대해 경고를 출력한다.
// 경고! `Item`의 반환 값이 사용되지 않았습니다.
Item{};
}
구조체를 사용할 때는 원시 자료형의 경우와 같이 자료형으로 이용하면 된다. 구조체 변수의 이름 뒤에 .를 붙이면 구조체의 멤버에 접근할 수 있다.struct와 식별자 사이에 특성C++11을 부착할 수도 있다. 구조체는 사용자가 본격적으로 데이터를 응용하기 위해 활용할 수 있다. 다른 정보와 구별되거나 따로 관리가 필요한 정보를 구조체 안에 담을 수도 있다. 예를 들어 게임에서 플레이어의 정보, 프로그램의 설정 정보, 너무 일반적이거나 짧은 이름을 갖고 있어서 구분이 힘든 변수를 담는 용도가 있다. 그 외에는 구조체 변수는 인스턴스(Instance, 개체)라는 특별한 이름으로 칭한다는 점도 특기할만 하다.
사실 구조체는 C++의 클래스(Class)와 거의 같은 역할을 수행한다. 자세한 내용은 이후의 클래스 문서를 참조하자.
9. 자료형 한정자
#!if version2 == null
{{{#!wiki style="border:1px solid gray;border-top:5px solid gray;padding:7px;margin-bottom:0px"
[[크리에이티브 커먼즈 라이선스|[[파일:CC-white.svg|width=22.5px]]]] 이 문단의 내용 중 전체 또는 일부는 {{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/C++/문법/자료형|C++/문법/자료형]]}}}{{{#!if external != "o"
[[C++/문법/자료형]]}}}}}} 문서의 {{{#!if uuid == null
'''uuid not found'''}}}{{{#!if uuid != null
[[https://namu.wiki/w/C++/문법/자료형?uuid=8d7dc401-c434-4b4c-adc4-a140dc705126|r49]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/문법/자료형?uuid=8d7dc401-c434-4b4c-adc4-a140dc705126#s-|번 문단]]}}}에서 가져왔습니다. [[https://namu.wiki/history/C++/문법/자료형?from=49|이전 역사 보러 가기]]}}}#!if version2 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="border:1px solid gray;border-top:5px solid gray;padding:7px;margin-bottom:0px"
[[크리에이티브 커먼즈 라이선스|[[파일:CC-white.svg|width=22.5px]]]] 이 문단의 내용 중 전체 또는 일부는 다른 문서에서 가져왔습니다.
{{{#!wiki style="text-align: center"
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki style="text-align: left; padding: 0px 10px"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/C++/문법/자료형|C++/문법/자료형]]}}}{{{#!if external != "o"
[[C++/문법/자료형]]}}}}}} 문서의 {{{#!if uuid == null
'''uuid not found'''}}}{{{#!if uuid != null
[[https://namu.wiki/w/C++/문법/자료형?uuid=8d7dc401-c434-4b4c-adc4-a140dc705126|r49]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/문법/자료형?uuid=8d7dc401-c434-4b4c-adc4-a140dc705126#s-|번 문단]]}}} ([[https://namu.wiki/history/C++/문법/자료형?from=49|이전 역사]])
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid2 == null
'''uuid2 not found'''}}}{{{#!if uuid2 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph2 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]]){{{#!if version3 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid3 == null
'''uuid3 not found'''}}}{{{#!if uuid3 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph3 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version4 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid4 == null
'''uuid4 not found'''}}}{{{#!if uuid4 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph4 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version5 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid5 == null
'''uuid5 not found'''}}}{{{#!if uuid5 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph5 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version6 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid6 == null
'''uuid6 not found'''}}}{{{#!if uuid6 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph6 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version7 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid7 == null
'''uuid7 not found'''}}}{{{#!if uuid7 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph7 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version8 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid8 == null
'''uuid8 not found'''}}}{{{#!if uuid8 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph8 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version9 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid9 == null
'''uuid9 not found'''}}}{{{#!if uuid9 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph9 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version10 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid10 == null
'''uuid10 not found'''}}}{{{#!if uuid10 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph10 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version11 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid11 == null
'''uuid11 not found'''}}}{{{#!if uuid11 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph11 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version12 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid12 == null
'''uuid12 not found'''}}}{{{#!if uuid12 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph12 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version13 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid13 == null
'''uuid13 not found'''}}}{{{#!if uuid13 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph13 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version14 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid14 == null
'''uuid14 not found'''}}}{{{#!if uuid14 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph14 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version15 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid15 == null
'''uuid15 not found'''}}}{{{#!if uuid15 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph15 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version16 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid16 == null
'''uuid16 not found'''}}}{{{#!if uuid16 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph16 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version17 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid17 == null
'''uuid17 not found'''}}}{{{#!if uuid17 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph17 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version18 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid18 == null
'''uuid18 not found'''}}}{{{#!if uuid18 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph18 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version19 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid19 == null
'''uuid19 not found'''}}}{{{#!if uuid19 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph19 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version20 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid20 == null
'''uuid20 not found'''}}}{{{#!if uuid20 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph20 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version21 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid21 == null
'''uuid21 not found'''}}}{{{#!if uuid21 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph21 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version22 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid22 == null
'''uuid22 not found'''}}}{{{#!if uuid22 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph22 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version23 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid23 == null
'''uuid23 not found'''}}}{{{#!if uuid23 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph23 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version24 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid24 == null
'''uuid24 not found'''}}}{{{#!if uuid24 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph24 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version25 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid25 == null
'''uuid25 not found'''}}}{{{#!if uuid25 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph25 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version26 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid26 == null
'''uuid26 not found'''}}}{{{#!if uuid26 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph26 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version27 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid27 == null
'''uuid27 not found'''}}}{{{#!if uuid27 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph27 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version28 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid28 == null
'''uuid28 not found'''}}}{{{#!if uuid28 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph28 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version29 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid29 == null
'''uuid29 not found'''}}}{{{#!if uuid29 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph29 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version30 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid30 == null
'''uuid30 not found'''}}}{{{#!if uuid30 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph30 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version31 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid31 == null
'''uuid31 not found'''}}}{{{#!if uuid31 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph31 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version32 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid32 == null
'''uuid32 not found'''}}}{{{#!if uuid32 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph32 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version33 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid33 == null
'''uuid33 not found'''}}}{{{#!if uuid33 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph33 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version34 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid34 == null
'''uuid34 not found'''}}}{{{#!if uuid34 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph34 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version35 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid35 == null
'''uuid35 not found'''}}}{{{#!if uuid35 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph35 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version36 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid36 == null
'''uuid36 not found'''}}}{{{#!if uuid36 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph36 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version37 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid37 == null
'''uuid37 not found'''}}}{{{#!if uuid37 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph37 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version38 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid38 == null
'''uuid38 not found'''}}}{{{#!if uuid38 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph38 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version39 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid39 == null
'''uuid39 not found'''}}}{{{#!if uuid39 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph39 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version40 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid40 == null
'''uuid40 not found'''}}}{{{#!if uuid40 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph40 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version41 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid41 == null
'''uuid41 not found'''}}}{{{#!if uuid41 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph41 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version42 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid42 == null
'''uuid42 not found'''}}}{{{#!if uuid42 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph42 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version43 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid43 == null
'''uuid43 not found'''}}}{{{#!if uuid43 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph43 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version44 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid44 == null
'''uuid44 not found'''}}}{{{#!if uuid44 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph44 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version45 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid45 == null
'''uuid45 not found'''}}}{{{#!if uuid45 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph45 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version46 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid46 == null
'''uuid46 not found'''}}}{{{#!if uuid46 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph46 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version47 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid47 == null
'''uuid47 not found'''}}}{{{#!if uuid47 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph47 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version48 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid48 == null
'''uuid48 not found'''}}}{{{#!if uuid48 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph48 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version49 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid49 == null
'''uuid49 not found'''}}}{{{#!if uuid49 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph49 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version50 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid50 == null
'''uuid50 not found'''}}}{{{#!if uuid50 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph50 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}}}}}}}}}}}}}}}}자료형 한정자 (Type Qualifier)
9.1. const
|
변수의 자료형 앞에 붙여 그 값이 불변함(Immutable)을 나타낸다. 변수 선언 시에
const를 붙여 그 값을 변경하지 못하도록 할 수 있다. 함수 작성 시에는 매개변수에 붙이면 사용자의 실수를 줄이고 예측할 수 없는 값의 수정을 막을 수 있다.#!syntax cpp
const int immutable = 40;
// 컴파일 오류!
immutable = 60;
가령 const int는 변하지 않는 정수 값임을 의미한다.9.2. volatile
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리/thread#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리/thread#|]] 부분을 참고하십시오. |
변수 선언 시에 자료형 앞에 붙이면 그 변수에 접근할 때 캐시에 대하여 최적화를 막고 항상 값이 보이도록 한다. 이를 이해하려면 운영체제와 컴파일러에 대해 이해가 필요하다. 컴파일러는 바이너리를 구축하는 과정에서 인라이닝, 캐싱, 파이프라인 분기 예측, 상수식 평가 등을 동원해 가장 빠른 바이너리를 만든다. 최적화를 하는 과정에서 몇가지 변수와 상수 표현식으로 선언된 상수는 없어진다. 컴파일 시간에 평가할 수 있는 구문은 미리 작성한다. 그리고 평가되지 않을 구문 역시 아예 코드에서 배제된다. 심지어는 함수의 존재 자체가 없어지고 모든 쓰임새가 반환값으로 대체될 수도 있다. 예를 들어 어떤 동일한
for문이 여러 곳에서 반복된다면 for문의 내용을 진짜 반복하는 대신에 결과를 미리 계산해놓고 가져다 쓸 것이다.이 과정은 거의 대부분의 경우 이득을 가져다 주지만, 변수의 존재 삭제와 구문 단축이 문제가 된다. 다중 스레드 환경의 예를 들어보자. 한 스레드에서
while(bool 변수);로 문맥의 흐름을 막았다고 해보자. 그럼 while문 안의 bool 변수의 값이 다른 스레드에서 false로 바뀌면 무한 루프를 빠져나갈까? 정답은 그렇지 않다. 계속 정지 상태로 남아있다. 왜냐하면 최적화를 위해서 while안의 변수는 메모리를 계속 읽는 대신에, 컴파일 순간 정해진 캐시의 값을 가져오기 때문이다 [4]. 또다른 예로는 프로그램 내내 같은 주소를 가리키는 포인터의 값을 읽을 필요가 있다고 해보자. 이러면 컴파일러는 컴파일 당시에 해당 포인터가 가리켰던 값만을 가져온다. 그래서 해당 포인터가 가리키는 값이 변경되어도 프로그램에선 바뀐 값을 알지 못한다. 바로 이럴때 volatile을 붙여서 캐시말고 항상 메모리에서 가져오도록 만들 수 있다. 즉 휘발성이라는 단어는 현재 변수에서 읽어오는 값이 임시적이라는 뜻이다.참고로 컴파일러의 캐시 최적화나 인위적인 코드 패치 순서 수정[5]을 막을 뿐이므로 다중 스레드를 사용할 때는 여전히 값의 읽기 순서에 따른 동기화 문제가 발생할 수 있다. 이를 해결하는 방법은 원자적 메모리 모듈을 응용하는 것이다.
10. 참조자
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#값 범주|값 범주]] 부분을 참고하십시오.참조자 (Reference)
10.1. 좌측값 참조자
|
변수 선언 또는 함수 정의 시에
&를 자료형 뒤에 붙여 이 값이 참조 형태임을 나타낼 수 있다. 가령 int&는 다른 정수를 참조하는 변수임을 의미한다. 참조자를 통해 참조자가 가리키는 원본 변수에 접근할 수 있다. 어떤 식으로 동작하는지 살펴보자.#!syntax cpp
int main()
{
int integer;
int& int_ref = integer;
// `int_ref`는 `integer`를 가리키므로 `integer`에 30이 대입됨.
int_ref = 30;
}
첫번째는 참조 변수를 만들고 수정하는 방법이다. 참조자를 선언하려면 반드시 참조할 다른 변수의 이름이 필요하며, 자료형이 동일해야만 한다. 만약 참조자에 값을 대입하면 원래 변수에 값이 전달된다.#!syntax cpp
struct Position { float x, y; };
int main()
{
Position my_place{ 10, 20 };
// 참조자 `where`은 `my_place`를 참조함.
Position& where = my_place;
// (1) `my_place`의 `y`에 4가 대입됨.
where.y = 4;
// (2) `my_place`의 `x`는 10.0f
float my_x = where.x;
}
구조체 참조자의 경우 .로 원래 구조체의 멤버에 접근할 수 있다.#!syntax cpp
int main()
{
long long very_long;
// `longlong_ref`는 `very_long`을 가리킨다.
long long& longlong_ref = very_long;
// `longlong_ref`에 `another_very_long`의 값인 100000이 대입되며, 참조 대상은 변하지 않음.
long long another_very_long = 100'000;
longlong_ref = another_very_long;
}
참조자가 한번 정의되면 참조자가 가리키는 대상은 다신 바꿀 수 없다.#!syntax cpp
struct Squirrel {};
// (1) 컴파일 오류! (prvalue)
Squirrel& ref = Squirrel{};
void Function(Squirrel& refparam);
// (2) 컴파일 오류! (prvalue)
Function(Squirrel{});
int Function() { return 1; }
// (3) 컴파일 오류!
// 참조자가 없는 함수의 반환값도 리터럴 (prvalue)
int& ref = Function();
// 불변 좌측값 참조자는 문제 없음.
const Squirrel& cref = Squirrel{};
참조자를 사용할 때 한가지 주의할 점이 있다. const가 아니면 리터럴과 임시 객체를 이용할 수 없다는 것이다. 리터럴에는 100.0f 따위의 숫자가 있다. `Squirrel{}`은 Squirrel 구조체의 리터럴이다. 이들은 이름을 가지고 있지 않아서 스스로 존재할 수 없는데, 좌측값 참조자도 스스로 존재할 수 없어 존재를 이루기가 부족하다.이름이 없으므로 수정할 수 없다. 그런데
& 참조자는 수정이 가능한 값만 받을 수 있다. 그러므로 const를 붙여서 상수 좌측 참조자를 만들어야 임시 객체와 리터럴 값을 받을 수 있다.참조한다는 것은 다른 변수의 본체를 가리킨다는 뜻이다. 마치 포인터처럼 말이다. 그러면 포인터랑 대체 다른 것이 뭔가? 라면 일단 일반 사용자 단에서는 변수를 사용할 때
(*handle)이나 handle->... 문법을 사용하지 않아도 된다. 메모리에 대해 조금 알고 있다면 임의 주소 참조나 메모리 오염 문제가 없을 거라는 예상을 할 수 있다.그렇다면 주소를 얻는 방법을 살펴보자.
#!syntax cpp
int main()
{
// C++14의 자릿수 구분자 ' 사용
int original_v1 = 1'000'000;
int original_v2 = 7'000'000;
int original_v3 = 9'000'000;
int& ref_v1 = original_v1;
int& ref_v2 = original_v2;
int& ref_v3 = original_v3;
// (1) original_v1의 값이 1000이 된다.
ref_v1 = 1000;
// (2) original_v1의 주소를 가져온다.
int* handle_v1 = &ref_v1;
// (3) 참조 변수 ref_v1을 통해 original_v1의 값이 7000000이 된다.
ref_v1 = original_v2;
// (4) 참조 변수 ref_v2를 통해 original_v2의 값을 가져오고, ref_v1을 통해 original_v1의 값이 7000000이 된다.
ref_v1 = ref_v2;
// (5) handle_v1의 주소가 바뀌지 않는다. original_v1의 값이 9000000이 된다.
*handle_v1 = original_v3;
}
상기한 코드는 정수 변수 original_v1, original_v2, original_v3에 대해 각각의 참조 변수를 만들고 접근하는 예제다. 각각 ref_v1, ref_v2, ref_v3로 참조되어 이 참조 변수를 통해 원래 변수를 수정할 수 있다. 만약 & 연산자를 변수 앞에 붙여 주소를 얻으면 참조 변수의 주소가 아니라, 가리키는 변수의 주소가 나온다. 주소를 가져오는 std::addressof함수를 사용해도 마찬가지다.참조자에는 아주 이상한 특징이 있다. 바로 이름만 가진 변수다. 참조자는 명백하게 존재하고 여기저기 갖다 쓸 수 있으나 참조자 자체는 고유한 주소와 고유한 값을 가지지 않는다. 즉 참조 변수는 참조자가 가리키는 어떤 변수에 접근할 수 있을 뿐 스스로 존재할 수 없다 [6]. 이것이 포인터와의 차이점이다.
결론적으로 참조자는 값이 아니라 다른 변수의 별칭(Alias)이라고 볼 수 있다. 참조자를 정의할 때는 어떠한 값의 복사나 수정 오버헤드도 일어나지 않는다. 어떠한 조작 없이 원래 변수에 다른 식별자를 붙여주었을 뿐이기 때문이다!
그럼 변수의 이름만 바꿔 부르는 기능을 어디에 쓰는 걸까? 참조자는 함수에서 더 잘 활용할 수 있다.
10.1.1. 함수에 좌측값 참조로 전달
|
참조자가 쓰인 함수의 매개변수는 함수에 전달된 외부 변수를 그대로 이용할 수 있다. 즉 매개변수로 복사되지 않고 원본을 그대로 접근할 수 있다. 앞서 참조자를 쓴다는 것은 변수에 일종의 별칭을 붙여주는 것이라고 했다. 인자로 전달된 값에 함수 내부에서 새로운 이름을 붙여주는 셈이다.
#!syntax cpp
void Add(int& target, int increament)
{
target += increament;
}
int main()
{
int integer = 7;
Add(integer, 10);
// `integer`의 값은 17이 됨.
}
상기 코드는 함수에 참조할 변수를 전달해서 원본 변수를 수정하는 예제다. 함수 `Add`에서 변수 `integer`를 참조자로 받아서 수정하고 있다.참조자의 진가는 구조체와 같이 활용할 때 드러난다. 다음 예제를 보자.
- <C++ 예제 보기>
#!syntax cpp struct Player { float x, y; float myHealth; }; // 전역 변수 Player playerInstance{}; void PlayerMoveX(float dist) { playerInstance.x += dist; } void PlayerMoveY(float dist) { playerInstance.y += dist; }; [[nodiscard]] float PlayerGetHealth() { return playerInstance.myHealth; } int main() { PlayerMoveX(-40); PlayerMoveY(10); }
PlayerMoveX, PlayerMoveY를 쓰는데 문제가 없다. 그런데 플레이어 개체가 다수라면 어떻게 될까? 그 경우 함수에서 직접 사용하는 플레이어 개체 변수가 하나 뿐이라 이 방법은 쓰지 못한다. 또한 플레이어 말고도 게임에서 다수가 등장하는 적이 있으면 적의 모든 개체에 대응하는 함수를 만들지 않는 이상 다른 방법이 없다. 다행히도 함수의 매개변수를 참조 변수로 만들면, 함수가 모든 개체에 대해 적용되게 만들어서 이 문제를 해결할 수 있다.#!syntax cpp
Player everyPlayers[2];
void PlayerMoveX(Player& player, float dist)
{
player.x += dist;
}
void PlayerMoveY(Player& player, float dist)
{
player.y += dist;
}
// `Player`의 상수 좌측값 참조자 사용함.
// 읽기 전용 함수
float PlayerGetHealth(const Player& player)
{
return player.myHealth;
}
int main()
{
everyPlayers[0] = Player{ 20, 400, 100.0f };
everyPlayers[1] = Player{ 180, 400, 100.0f };
PlayerMoveY(everyPlayers[1], -30);
}
상기 코드는 플레이어 이동 및 체력을 얻는 함수를 임의의 플레이어가 쓸 수 있도록 고친 예시를 보여주고 있다. 이 예제의 PlayerMoveX, PlayerMoveY는 플레이어 객체의 변수를 수정해야 한다. 플레이어 개체 변수를 참조자 없이 함수에 전달하면 플레이어 변수가 복사되므로 사용할 수 없다. 그래서 함수의 첫번째 매개변수로 Player의 참조자를 받아서 멤버 변수에 접근한다.이런 식으로 원본이 필요한 곳에는
&를, 아닌 곳에는 일반 자료형을 쓰면 문제가 다수 해결된다. 간단한 어플리케이션을 개발하는 정도에선 적재적소에 참조자를 쓰는 정도로 충분하다.10.2. 우측값 참조자
자료형&& 변수-식별자 = static_cast<자료형&&>(이동대상-식별자); 자료형&& 변수-식별자 = 함수-식별자(...); |
C++11에서 새로 도입된 인자 전달 방식이다.
&&를 자료형 뒤에 붙여 리터럴 또는 임시 객체를 가리키는 참조자임을 나타낼 수 있다. 다른 말로 이동 연산자라고도 부른다.도입 목적은 메모리의 잦은 할당을 방지하며 재사용을 지시하고, 깊은 복사를 막는 것이다. 우측값 참조자를 통해 변수가 사용되는 메모리 공간에 복사없이, 마치 처음부터 존재하는 것처럼 처리할 수 있다. 복사가 아예 일어나지 않기에 값의 교환을 효율적으로 할 수 있다. 즉 최적화를 위해 추가된 한정자인데, 그 경위를 자세히 살펴보자.
원래 전통적으로 C언어에서는 리터럴 또는 임시 객체 값들을
rvalue(Right-Value, 우측값)라고 칭했다. 왜냐하면 임시 값이 선언, 대입, 비교식에서 식의 오른쪽에 놓이는 경향이 있어서 이렇게 불렸다 [7]. 우측값의 종류에는 숫자 리터럴, 구조체&클래스의 생성자를 호출해서 만든 임시 객체, & 참조가 아닌 함수의 반환 값 등이 있다.C언어는 고성능 언어지만 변수의 대입 및 함수의 인자 전달 과정에서 깊은 복사가 일어나는 문제가 있다 [8]. 그래서 깊은 복사를 막기 위해 포인터 매개변수와 메모리 풀링 메커니즘을 오랜 세월 이용했다.
#!syntax cpp
// `cref`는 수정할 수 없음.
// `cref`는 int 50을 값으로 갖고 있는 어떤 임시 객체를 참조하고 있음.
const int& cref = 50;
C++의 좌측값 참조자는 최대한 얕은 복사를 쓰기 쉽게 해줬지만, 임시 객체 생성, 변수의 중복 선언 등 여전히 문제가 많았다. 좌측값 참조자는 정의가 존재하지 않는 리터럴 따위를 담지 못한다. const&(상수 좌측값 참조자)를 쓰면 가능하지만 내부적으로 임시 객체가 생성되며 const라서 수정할 수도 없는 임시 객체가 무슨 짓을 할지 모른다.#!syntax cpp
// 시스템 자원을 들고있는 구조체. 시스템 자원은 절대로 복사하면 안된다.
struct CThread
{
// (1) 기본 생성자
CThread();
// (2) 복사 생성자
CThread(const CThread& other);
// (3) 리소스 생성자
// 시스템 자원을 생성한다.
CThread(int id, void* data);
// (4) 소멸자
// 시스템 자원을 파괴한다.
~CThread();
};
임시 객체 생성으로 인해 생기는 가장 큰 문제 중 하나는 시스템 리소스의 중복 문제였다. 가령 스레드, 뮤텍스와 핸들은 시스템에서 생성되고 관리된다. 사용자는 이를 운영체제 호출을 통해 간접적으로 제어할 수 있다. 그런데 생성과 제어는 그렇다 치고 이 자원들이 파괴되는 경우가 있을 것이다. 다른 곳에서 자원이 사라진걸 모르면 잘못된 운영체제 호출이 발생하고, 이 오류는 단순한 런타임 오류와는 궤를 달리할 것이다.#!syntax cpp
CThread th1();
CThread th2(th1);
int data = 10;
CThread th3(CThread(3, (void*)&data));
시스템 자원을 변수에 넣지 않고 `th3` 처럼 시스템 자원 객체를 바로 전달받아도 복사 생성자에서 필연적으로 임시 객체 const CThread&가 생성된다. 이때 보이지 않는 const CThread& 객체는 `th3`에 시스템 자원을 순순히 넘겨주는 것처럼 보여도, 만약 소멸자에서 시스템 자원을 해제하도록 했다면 `th3`는 생성하자마자 죽은 객체가 된다. 이를 막으려면 두 가지 방법이 있다. 임시 객체인지 표시하는 플래그를 넣던가, 자원을 해제하는 전역 함수를 별도로 만들어야 하는데 최적화와 깔끔함 둘 다 만족시키지 못한다. 우측값 참조자는 컴파일러와 사용자에게 복사 및 참조와는 구분하게 하고 중복 자원의 문제도 깔끔하게 해결한다. 객체를 생성하는 방법을 하나 더 제시함으로써 많은 문제가 해결된 것이다.#!syntax cpp
std::vector<int> old_vector;
std::vector<int> new_vector = static_cast<std::vector<int>&&>(old_vector);
// 이후 old_vector의 크기는 0
보통 이동 연산을 한 객체는 대개 한번 사용되면 중복해서 사용할 수 없도록 구현된다. 예를 들어 가변 배열인 std::vector는 이동된 객체는 크기가 0으로 텅 비어버린다. 또다른 예로는 std::thread는 운영체제 자원을 사용하기에 복사할 수 없고, 오직 이동만 하도록 구현된다. 이를 이동시키면 내부의 시스템 자원이 새로운 인스턴스에 전달되기에 원래 인스턴스는 사용할 수 없다.사실 우측값 참조자는 입문 시기에는 직접 쓸 필요가 없다. 컴파일러가 알아서 복사와 이동 연산을 해주니까. 그리고 운영체제 기능을 쓰지 않는다면 로직 구현에 필수적인 기능은 아니다. 하지만 성능을 위해서라면 반드시 알아두는 것이 좋다. 학습 진도를 조금만 넘겨도 혜성처럼 등장하고, 고급 단계에서 이해하지 못하면 C++의 알 수 없는 기전에 좌절할 수 있다.
10.2.1. 함수에 우측값 참조로 전달
|
우측값 참조를 실제로 활용하려면 사실상 함수의 사용이 필수적이다. 왜냐하면
&&의 부스러지는 특징 때문이다. 변수에서 쓰이는 && 참조형은 거진 의미가 없다. &&는 & 참조자와 마찬가지로 스스로 존재할 수 없는 존재인데다가 &&는 변수의 이름 자체도 아무런 의미가 없다. 이게 무슨 뜻이냐 하면, C++에서 변수의 이름은 좌측값(lvalue)인데 우측값 참조 자의 이름을 언급하는 순간 그건 rvalue가 아니게 되는 것이다! [9]#!syntax cpp
int value = 100;
int&& rref = static_cast<int&&>(value);
// `value`의 값이 1000으로 바뀜.
rref = 1000;
사용자가 자료형에 &&를 명시하더라도 무조건 &로 바뀐다. 함수의 도움 없이는 &&는 항상 &로, const&&는 항상 const&로 연역된다.표준 라이브러리에서
std::move라는 함수로 간편한 이동 연산을 제공한다. 또는 사용자가 직접 static_cast<T&&>(value)로 수행할 수도 있다.11. 포인터 (Pointer)
|
*를 자료형 뒤에 붙이면 그 자료형를 가리키는 주소임을 나타낼 수 있다. C언어에서의 사용법과 다르지 않지만 참조자와 관련된 규칙이 여럿 추가되었다.
#!syntax cpp
T lvalue;
// (1) 일반 변수를 가리키는 포인터
T* ptr = &lvalue;
// (2) 불변 변수를 가리키는 포인터
const T* ptr_to_immutable = &lvalue;
// (3) 일반 변수를 가리키는 불변 포인터
T* const immutable_ptr = &lvalue;
// (4) 불변 변수를 가리키는 불변 포인터
const T* const immutable_ptr_to_immutable = &lvalue;
포인터는 위의 네가지 용법으로 사용할 수 있다. 표준 라이브러리의 addressof(T*) 함수로 주소를 얻어올 수도 있다.#!syntax cpp
T*& reference_to_ptr = ptr;
T* const& immutable_reference_to_ptr = ptr;
T* const& immutable_ptr = &lvalue;
const T* const& immutable_ptr_to_immutable = &lvalue;
포인터의 참조자는 가능하지만 참조자의 포인터는 불가능하다. 코드를 쓸 때는 *를 &나 && 앞에 붙여야 한다.참조자는 객체가 아니라서 주소를 가지지 않는다. 그래서 참조자 자체의 주소는 얻을 수 없고 참조자가 가리키는 원본 변수의 주소만 쓸 수 있다.
#!syntax cpp
int value;
// 컴파일 오류! 리터럴(prvalue)을 사용할 수 없음.
int* ptr = &(1000);
// 컴파일 오류! 임시 객체(xvalue)를 사용할 수 없음.
int* ptr = &(std::move(value));
포인터로 가리킬 수 있는 변수는 오직 lvalue만이 가능하다. rvalue는 사용할 수 없다.#!syntax cpp
const T lvalue;
const T* cptr = &lvalue;
불변(const)인 변수는 반드시 const 포인터로 가리켜야 한다.const와 조합했을때는 포인터가 가리키는 값이 불변인지, 아니면 포인터 자체가 불변인지가 달라진다. 예를 들어 const int* ptr;은 const int의 포인터이므로, *ptr로 접근한 값은 const라서 수정할 수 없다. 그러나 ptr이라는 변수 자체는 더하고 빼고 등등 임의의 연산도 할 수 있다. int* const ptr;은 ptr 자체는 불변이지만 *ptr로 접근한 값은 그냥 int라서 바꿀 수 있다.#!syntax cpp
const char* string = "Hello, world!";
// (1) `string`의 이름은 'H'를 가리키는 포인터
const char the_H = *string;
// (2) 이제 `string`의 값은 'E'를 가리키는 주소
const char the_E = *(++string);
가장 많이 볼 사례는 문자열 포인터가 있겠다. 추후 설명하겠지만 const char* string은 C++에서 문자열을 의미한다. 그런데 포인터 변수 자체가 아니라 문자열의 값이 불변이라서 변수 string에 1을 더하고 빼는 등 값을 변경할 수 있다. 반면 char* const& character는 character가 가리키는 char* 값 하나는 *character = 'B'; 처럼 언제든지 값이 바뀔 수 있다. 그러나 character는 불변인 포인터다. 이해하기 어렵다면 다른 자료형 앞에 붙어야 의미가 있다는 것을 기억하자.표준 라이브러리의
<algorithm> 모듈에서는 이를 이용해 자료구조 뿐만 아니라 문자열에 대해서도 동일한 연산을 지원한다.한편 상수 포인터, 상수를 가리키는 포인터는 요즘은 고려할 필요가 적다. C++17의
std::string_view, C++20의 std::span의 도움으로 웬만한 포인터 사용을 대체할 수 있기 때문이다 [10].12. 문자열 리터럴 (String Literal)
|
C++의 문자열 리터럴, 내지 문자열은 큰따옴표로 문장을 감싸 표현할 수 있다. 컴파일러에 따라 다르지만 문자열 리터럴을 사용하면 프로그램 내부에 문자열 길이 만큼의 메모리를 할당한다 [11].
#!syntax cpp
const char* my_string = "Good Morning.";
const char* const& my_string_cref = my_string;
문자열은 const char* 변수에 담을 수 있다. const&가 const char*를 받는 const char* const&도 가능하다.C언어 까지는
char[Size] 또는 여기서 연역된 char*였으나, 이는 실제로 수정할 수 없는 리터럴에 대한 오해를 사게 만들었다.#!syntax cpp
void Function(const char* str);
함수의 매개변수에서도 똑같이 사용하면 된다.#!syntax cpp
const char my_string[14] = "Good Morning.";
배열 const char[Size]에 담을 수 있다. 정확한 개형을 말해보자면 lvalue인 const (&char)[Size]로 표현된다.#!syntax cpp
template<size_t Length>
void Function1(const char (&str)[Length]);
// 그냥 const char[Size]로 쓰면 문자열의 포인터가 복사된다.
template<size_t Length>
void Function2(const char str[Length]);
만약 함수의 매개변수에서 정확한 문자열을 표현하려면 템플릿의 도움이 필요하다. 너무 어려운 문법은 아니고 문자열의 길이만 명시하면 된다.#!syntax cpp
// (1) `str1`은 const char*
constexpr auto str1 = "Nice to meet you!";
// (2) `str2`는 const (char&)[22]
auto& str2 = "Nice to meet you too.";
이후 배울 auto와 상수 표현식으로도 문자열을 받을 수 있다.13. Ranged For-Loop
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리/vector#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리/vector#|]] 부분을 참고하십시오.범위 기반 For 반복문
#!syntax cpp
for (init-statement; item-decl : range)
{
// ...
}
범위 기반 for 반복문은 범위(Range)를 자동으로 순회하는 간편한 반복문이다. 기존 for문에서 범위를 순회하려면 반복되는 (Boilerplate) 코드가 너무 많았는데, 이를 개선하기 위해 문법적 설탕으로써 도입되었다. #!syntax cpp
// std::array 등 순회자를 반환하는 `begin`, `end` 멤버 함수를 가지고 있다면 모두 사용할 수 있음.
std::vector<int> list{ 1, 2, 3, 4, 5 };
// int&, const int&도 사용할 수 있음.
for (int& v : list)
{
// ...
}
상기 코드는 벡터에서 범위 for문을 실행하는 예시를 보여주고 있다. 만약 자료구조가 지원한다면 범위 for문은 참조자를 사용할 수 있으며 원소의 수정도 가능하다. 대신 문법의 한계로 원소의 삭제는 불가능하다.#!syntax cpp
int array[] = { 1, 2, 3, 4, 5 };
// `v`는 int&
for (decltype(auto) v : array)
{
std::print("{} ", v);
}
상기 코드는 1부터 5까지의 자연수를 출력한다.14. 분기문 초기화 구문
분기문 초기화 구문은 실행문의 앞 부분에 별도의 문장을 삽입할 수 있는 기능이다. 이 기능은for문과 닮은 기능을 하는 문법적 설탕으로써 도입되었다. 초기화 구문은 해당 분기문이 실행되기 직전에 먼저 실행되며, 구문 안에 선언된 변수나 인스턴스는 실행문에 따라오는 스코프 안에서만 나타난다. 특정 조건문이 평가되기 전에 특정 값의 초기화를 할 수 있으며 객체 수명 관리가 쉬워진다. 그리고 깔끔한 코드를 작성할 수 있다.14.1. if문 초기화
if문 초기화 구문C++17#!syntax cpp
if (init-statement; condition)
{
// ...
}
C++17에서 도입된 if 초기화 구문은 if 문안에 또다른 문장과 ;를 넣어 작성할 수 있다.#!syntax cpp
if (int* ptr = new int; ptr != nullptr)
{
// `ptr`은 if문 밖에서는 보이지 않는 지역 변수임.
}
동적 할당을 사용할 때 메모리 검사를 좀 더 깔끔하게 작성할 수 있다.#!syntax cpp
struct Position { float x, y; };
bool IsInDistance(const Position& lhs, const Position& rhs, float range)
{
if (float dist = std::sqrt(std::sqrf(lhs.x - rhs.x) + std::sqrf(lhs.y - rhs.y)); dist < range)
{
return true;
}
else
{
return false;
}
}
상기 코드에서는 두 좌표 사이의 거리가 특정 거리 미만인지 검사한다. 여기서 좌표 사이의 거리를 임시 변수로서 초기화 구문으로 선언했다.14.2. switch문 초기화
switch문 초기화 구문C++17#!syntax cpp
switch (init-statement; condition)
{
// case...
}
C++17에서 도입된 switch 초기화 구문은 switch 문안에 또다른 문장과 ;를 넣어 작성할 수 있다.#!syntax cpp
struct Battery { float percentage; /* 0.0f ~ 1.0f */ };
std::string GetBatteryStatus(const Battery& battery)
{
switch(int grade = std::floor(battery * 100.0f) / 20; grade) // `grade`를 분기함.
{
case 0: return std::string("매우 낮음"); // 0.0f ~ 20.0f
case 1: return std::string("낮음"); // 20.0f ~ 40.0f
case 2: return std::string("중간."); // 40.0f ~ 60.0f
case 3: return std::string("약간 높음"); // 60.0f ~ 80.0f
case 4: return std::string("높음"); // 80.0f ~ 99.f
case 5: return std::string("완충"); // 100.0f
default: throw "오류!";
}
}
상기 코드는 switch문 초기화 구문의 예시를 보여주고 있다.14.3. 범위 for문 초기화
범위for문 초기화 구문C++20#!syntax cpp
for (init-statement; item-decl : range)
{
// ...
}
C++20에서 도입된 범위 for 초기화 구문은 범위 for 문안에 또다른 문장과 ;를 넣어 작성할 수 있다.#!syntax cpp
void Print(const std::vector<int>& list)
{
for (size_t i = 0; auto& item : list)
{
if (++i == list.size())
{
std::println("{}", item);
}
else
{
std::print("{}, ", item);
}
}
}
상기 코드에서는 벡터의 원소를 한줄에 쉼표와 함께 출력하되, 마지막 원소는 쉼표를 붙이지 않고 한줄을 띄운다.15. using
|
예약어
using을 사용하여 자료형의 별칭(Alias)을 선언할 수 있다. C언어의 typedef 예약어를 대체하는 기능으로써 가독성 상승을 비롯하여 템플릿을 사용할 수 있게 되었다. 이 구문은 변수를 선언하는 것처럼 새로운 자료형을 선언하는 것이라고 볼 수 있고 표준에서도 자료형을 선언한다고 표현한다. 그런데 참조자와 같은 맥락으로 이해해야 한다. 기존의 자료형에 다른 이름을 붙이는 것이기 때문에, 기존의 자료형과 using으로 선언한 자료형은 호환된다. 템플릿도 마찬가지로 void Function(std::vector<int>& vec);이라는 함수가 있다면, 이 함수에는 using IntVector = std::vector<int>;와 그냥 std::vector<int> 모두를 인자로 받을 수 있다.#!if version2 == null
{{{#!wiki style="border:1px solid gray;border-top:5px solid gray;padding:7px;margin-bottom:0px"
[[크리에이티브 커먼즈 라이선스|[[파일:CC-white.svg|width=22.5px]]]] 이 문단의 내용 중 전체 또는 일부는 {{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/C++/문법/자료형|C++/문법/자료형]]}}}{{{#!if external != "o"
[[C++/문법/자료형]]}}}}}} 문서의 {{{#!if uuid == null
'''uuid not found'''}}}{{{#!if uuid != null
[[https://namu.wiki/w/C++/문법/자료형?uuid=6011d8f6-8b82-400b-97e1-00d1cf1b16bc|r53]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/문법/자료형?uuid=6011d8f6-8b82-400b-97e1-00d1cf1b16bc#s-|번 문단]]}}}에서 가져왔습니다. [[https://namu.wiki/history/C++/문법/자료형?from=53|이전 역사 보러 가기]]}}}#!if version2 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="border:1px solid gray;border-top:5px solid gray;padding:7px;margin-bottom:0px"
[[크리에이티브 커먼즈 라이선스|[[파일:CC-white.svg|width=22.5px]]]] 이 문단의 내용 중 전체 또는 일부는 다른 문서에서 가져왔습니다.
{{{#!wiki style="text-align: center"
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki style="text-align: left; padding: 0px 10px"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/C++/문법/자료형|C++/문법/자료형]]}}}{{{#!if external != "o"
[[C++/문법/자료형]]}}}}}} 문서의 {{{#!if uuid == null
'''uuid not found'''}}}{{{#!if uuid != null
[[https://namu.wiki/w/C++/문법/자료형?uuid=6011d8f6-8b82-400b-97e1-00d1cf1b16bc|r53]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/문법/자료형?uuid=6011d8f6-8b82-400b-97e1-00d1cf1b16bc#s-|번 문단]]}}} ([[https://namu.wiki/history/C++/문법/자료형?from=53|이전 역사]])
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid2 == null
'''uuid2 not found'''}}}{{{#!if uuid2 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph2 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]]){{{#!if version3 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid3 == null
'''uuid3 not found'''}}}{{{#!if uuid3 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph3 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version4 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid4 == null
'''uuid4 not found'''}}}{{{#!if uuid4 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph4 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version5 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid5 == null
'''uuid5 not found'''}}}{{{#!if uuid5 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph5 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version6 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid6 == null
'''uuid6 not found'''}}}{{{#!if uuid6 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph6 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version7 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid7 == null
'''uuid7 not found'''}}}{{{#!if uuid7 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph7 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version8 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid8 == null
'''uuid8 not found'''}}}{{{#!if uuid8 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph8 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version9 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid9 == null
'''uuid9 not found'''}}}{{{#!if uuid9 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph9 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version10 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid10 == null
'''uuid10 not found'''}}}{{{#!if uuid10 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph10 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version11 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid11 == null
'''uuid11 not found'''}}}{{{#!if uuid11 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph11 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version12 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid12 == null
'''uuid12 not found'''}}}{{{#!if uuid12 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph12 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version13 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid13 == null
'''uuid13 not found'''}}}{{{#!if uuid13 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph13 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version14 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid14 == null
'''uuid14 not found'''}}}{{{#!if uuid14 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph14 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version15 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid15 == null
'''uuid15 not found'''}}}{{{#!if uuid15 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph15 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version16 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid16 == null
'''uuid16 not found'''}}}{{{#!if uuid16 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph16 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version17 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid17 == null
'''uuid17 not found'''}}}{{{#!if uuid17 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph17 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version18 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid18 == null
'''uuid18 not found'''}}}{{{#!if uuid18 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph18 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version19 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid19 == null
'''uuid19 not found'''}}}{{{#!if uuid19 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph19 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version20 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid20 == null
'''uuid20 not found'''}}}{{{#!if uuid20 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph20 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version21 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid21 == null
'''uuid21 not found'''}}}{{{#!if uuid21 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph21 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version22 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid22 == null
'''uuid22 not found'''}}}{{{#!if uuid22 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph22 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version23 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid23 == null
'''uuid23 not found'''}}}{{{#!if uuid23 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph23 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version24 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid24 == null
'''uuid24 not found'''}}}{{{#!if uuid24 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph24 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version25 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid25 == null
'''uuid25 not found'''}}}{{{#!if uuid25 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph25 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version26 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid26 == null
'''uuid26 not found'''}}}{{{#!if uuid26 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph26 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version27 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid27 == null
'''uuid27 not found'''}}}{{{#!if uuid27 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph27 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version28 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid28 == null
'''uuid28 not found'''}}}{{{#!if uuid28 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph28 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version29 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid29 == null
'''uuid29 not found'''}}}{{{#!if uuid29 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph29 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version30 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid30 == null
'''uuid30 not found'''}}}{{{#!if uuid30 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph30 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version31 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid31 == null
'''uuid31 not found'''}}}{{{#!if uuid31 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph31 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version32 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid32 == null
'''uuid32 not found'''}}}{{{#!if uuid32 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph32 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version33 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid33 == null
'''uuid33 not found'''}}}{{{#!if uuid33 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph33 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version34 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid34 == null
'''uuid34 not found'''}}}{{{#!if uuid34 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph34 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version35 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid35 == null
'''uuid35 not found'''}}}{{{#!if uuid35 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph35 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version36 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid36 == null
'''uuid36 not found'''}}}{{{#!if uuid36 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph36 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version37 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid37 == null
'''uuid37 not found'''}}}{{{#!if uuid37 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph37 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version38 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid38 == null
'''uuid38 not found'''}}}{{{#!if uuid38 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph38 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version39 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid39 == null
'''uuid39 not found'''}}}{{{#!if uuid39 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph39 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version40 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid40 == null
'''uuid40 not found'''}}}{{{#!if uuid40 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph40 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version41 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid41 == null
'''uuid41 not found'''}}}{{{#!if uuid41 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph41 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version42 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid42 == null
'''uuid42 not found'''}}}{{{#!if uuid42 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph42 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version43 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid43 == null
'''uuid43 not found'''}}}{{{#!if uuid43 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph43 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version44 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid44 == null
'''uuid44 not found'''}}}{{{#!if uuid44 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph44 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version45 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid45 == null
'''uuid45 not found'''}}}{{{#!if uuid45 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph45 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version46 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid46 == null
'''uuid46 not found'''}}}{{{#!if uuid46 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph46 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version47 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid47 == null
'''uuid47 not found'''}}}{{{#!if uuid47 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph47 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version48 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid48 == null
'''uuid48 not found'''}}}{{{#!if uuid48 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph48 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version49 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid49 == null
'''uuid49 not found'''}}}{{{#!if uuid49 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph49 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}{{{#!if version50 != null
{{{#!wiki style="display: block;"
{{{#!wiki style="display: inline-block"
{{{#!if external == "o"
[[https://namu.wiki/w/|]]}}}{{{#!if external != "o"
[[]]}}}}}} 문서의 {{{#!if uuid50 == null
'''uuid50 not found'''}}}{{{#!if uuid50 != null
[[https://namu.wiki/w/?uuid=|r]]}}} 판{{{#!if paragraph50 != null
, [[https://namu.wiki/w/?uuid=#s-|번 문단]]}}} ([[https://namu.wiki/history/?from=|이전 역사]])}}}}}}}}}}}}}}}}}}}}}16. 클래스 (Class)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/클래스#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/클래스#|]] 부분을 참고하십시오.17. 메모리 할당
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#저장소 지속시간|저장소 지속시간]] 부분을 참고하십시오.C++에서 객체의 수명은 4가지 방식으로 관리된다. 정적, 동적, 자동, 그리고 스레드 방식이 있다. 정적과 동적 할당은 이 문서에서 문법 위주로 설명하고 나머지는 명세 문서에서 자세히 살펴볼 수 있다. 스레드 방식은 C++/표준 라이브러리/thread 문서를 참고하라.
17.1. 동적 메모리 할당
동적 할당 방식에는new 연산자를 사용한 생성, malloc, calloc, realloc등의 C언어 방식의 생성등의 방법이 있다. 동적 할당을 왜 쓰며 언제 쓸 수 있는지 알아보자.#!syntax cpp
#include <memory>
int* AllocateMemory()
{
int x = 10;
int *ptr = std::addressof(x);
return ptr;
// 함수의 호출 스택을 빠져나가면서 객체 `x`가 회수된다.
// 참조 대상 소실 (Dangling Pointer/Reference)
}
int main()
{
int* dangling_memory = AllocateMemory();
// 컴파일러에 따라 디버그 모드에서 메모리 접근 위반 감지, 메모리 커럽션 감지를 해주기도 한다.
// 이런 코드를 주의깊게 보지 않으면 보안 문제가 발생할 수 있다.
*dangling_memory = 30;
}
함수 `AllocateMemory`의 의도는 정수를 가리키는 포인터를 만드는 것이다. 처음에 자동 할당으로 정수 x를 생성하고, 정수 포인터 ptr에게 x의 메모리 주소를 가리키라고 명령하고 ptr를 반환하는 것. 하지만 변수 x는 자동 지속시간을 가진 변수이기 때문에, 함수의 범위를 벗어나면 x에게 주어진 메모리는 운영체제가 회수한다. 이 함수를 실제로 사용하면 반환되는건 정수를 가리키는 포인터가 아니라, 아무 말도 안 되는 걸 가리키는 사용하면 안되는 포인터다.위에서 의도하려 했던 기능을 만들려면 다음과 같이 코드를 짜면 된다.
#!syntax cpp
int* AllocateMemory()
{
int *ptr = new int(10); // 동적 할당
return ptr;
}
int main()
{
int *ptr = AllocateMemory();
// "The value of ptr is '10'." 출력
std::println("The value of ptr is '{}'.", *ptr);
delete ptr; // `ptr`이 가리키는 메모리를 해제한다. 이제 `*ptr`은 사용하면 안된다.
}
사용이 끝난 동적 할당된 객체는 반드시 delete 연산자를 써서 해제해야 한다. 해제 시에 주의해야 할 점이 있는데, 이미 해제한 메모리를 재차 해제하려 하거나(Double-Free), 이미 해제한 메모리에 접근하려 할 경우(Use-After-Free) 문제가 발생한다.동적 메모리 할당은 다른 메모리 할당 방식보다 주의가 요구된다. 수동으로 관리해줘야 하는 불편함이 있고 다른 장소에서 자원이 삭제됐다는 사실을 알 수가 없다는 문제가 있다. 동적 할당이 필요하면 표준 라이브러리의 스마트 포인터를 사용하는 게 좋으며
new, delete는 존재는 한다 정도로만 알아두는 게 훨씬 낫다. 메모리 누수 때문에 특히 방대한 자료를 다루는 프로그램이라면 메모리를 잘 관리하는 것이 필수적이다. 2025년 지금까지 메모리 초과로 버그가 빈발하거나 먹통이 되어 버리는 문제로 수많은 게임과 프로그램이 고통받고 있다. 그래서 사용이 끝난 메모리는 반드시 풀어주어야 한다. #!syntax cpp
int* ptr1 = new int;
int* ptr2 = new int();
int* ptr3 = new int{};
int* ptr4 = new int{ 10 };
간단하게는 new 자료형;와 같은 표현식을 사용하면 된다. T *ptr = new T;와 같이 쓰면 된다.#!syntax cpp
struct TestClass
{
TestClass();
TestClass(int);
TestClass(int&, int);
};
// (1) 기본 생성자 `TestClass()` 호출
TestClass* ptr1 = new TestClass;
TestClass* ptr2 = new TestClass();
TestClass* ptr3 = new TestClass{};
// (2) 생성자 `TestClass(int)` 호출
TestClass* ptr4 = new TestClass(10);
TestClass* ptr5 = new TestClass{ 10 };
int value;
// (3) 생성자 `TestClass(int&, int)` 호출
TestClass* ptr6 = new TestClass(value, 10);
클래스의 생성자를 함께 호출할 수 있다.#!syntax cpp
import <new>;
try
{
int* ptr = new int;
}
catch (std::bad_alloc& e)
{
// ...
}
new 연산자는 메모리를 할당하는데 실패할 경우 std::bad_alloc 예외를 던진다. 동적 할당은 프로그램이 처음에 의도한 경로에서 벗어나 프로그램 외부의 영향을 받을 수 있다. 예를 들면 운영체제나 기억장치의 어떤 사정 때문에 할당이 실패하고 오류가 발생할 수 있다. 사용자 마음대로 생성하는 건 성능 문제 때문에라도 못한다.예외 발생으로 인한 성능 저하를 걱정한다면
set_new_handler 함수를 쓰면 객체의 생성 과정을 제어할 수 있다. 할당 실패 시 예외 대신 nullptr 값을 반환하도록 만들 수도 있다.17.1.1. 호환성
호환성과 관련하여 언급할 점이 몇가지 있다. C언어에서 사용하는malloc, free, 등으로 C++의 객체를 조작하는 건 주의해야 한다. 왜냐하면 C++의 생성자와 소멸자 때문이다. 의도한 루틴이 실행되지 않아서 프로그램 동작이 꼬일 위험성이 높다. 일단 C의 메모리 공간은 C++에서도 존재하며 똑같이 포인터로 표현할 수 있다. 내부적으로는 malloc 등의 방식으로 동적 할당을 수행하고 std::construct_at로 객체를 생성하는 식으로 구현된다. [12]new는 메모리를 할당하고 객체의 생성자를 호출하는데, malloc은 순수히 메모리 할당만 수행한다. delete는 객체의 소멸자를 호출하고 메모리를 호출하는데, free는 메모리를 해제하기만 한다. 다시 말해서 new는 바로 해당 메모리 공간에 어떤 객체가 들어있고, 객체의 수명(Lifetime)이 관리되며, 곧 생성자와 소멸자를 호출할거라고 선포한 상태란 것이다. C++의 객체를 C언어 방식으로 조작하면 생성자, 소멸자가 호출되지 않으므로 정말 특수한 상황이 아니면 malloc, free 등은 적용하지 말아야 한다.마지막으로 백엔드에서 서로 다른 런타임을 사용할 때는 경우 메모리 해제는 할당한 프로그램이 사용하는 런타임 영역 내에서 이루어 져야 한다. GCC가 사용하는 libstdc++, LLVM이 사용하는 libc++, 그리고 MSVC가 사용하는 msvcp는 서로 다른 ABI를 가지고 있으며 따라서 할당 방법도 다르다. libstdc++에서 할당한 포인터를 msvcp가 해제하는 경우 힙 커럽션등의 문제가 생길 수 있다.
17.2. 정적 메모리 할당
static 자료형{{{#FFA3D2,#Violet ' 식별자'}}};static inline constexpr 자료형{{{#FFA3D2,#Violet ' 식별자'}}} = 초기값;C++11static inline 자료형{{{#FFA3D2,#Violet ' 변수-식별자'}}} = 초기값;C++17static inline constinit 자료형{{{#FFA3D2,#Violet ' 식별자'}}} = 초기값;C++20 |
#!syntax cpp
// 전역 변수이면서 static 변수
static int global;
// C++17
static inline int inglobal = 10;
int main()
{
// "global: 0" 출력
std::println("global: {}", global);
// "inglobal: 10" 출력
std::println("inglobal: {}", inglobal);
}
선언한 static 변수는 기본값으로 초기화된다. inline을 쓰면 즉시 값을 대입한다.#!syntax cpp
struct Shape
{
constexpr virtual ~Shape() noexcept = default;
protected:
constexpr Shape() noexcept = default;
};
struct Triangle : public Shape
{
public:
constexpr Triangle() noexcept = default;
constexpr ~Triangle() noexcept = default;
};
struct Vector3
{
Vector3(float x, float y, float z);
};
// (1) 컴파일 오류! Shape::~Shape()에 액세스할 수 없습니다.
static Shape shape;
// (2) 컴파일 오류! 기본 생성자를 호출할 수 없습니다
static Vector3 allIsNotWell;
// (3) 문제 없음.
static Triangle triangle;
// (4) 문제 없음.
// ~Shape 가 public이 아니면 이 경우도 실패한다.
static inline Shape initshape = Triangle();
만약 static 변수가 클래스의 인스턴스이고, 초기값을 할당하지 않았다면 그 변수는 클래스의 기본 생성자를 호출한다 [13]. 때문에 기본 생성자가 없으면 컴파일 오류가 발생하니 주의해야 한다.#!syntax cpp
static int GlobalStaticVariable1;
static inline int GlobalStaticVariable2 = 1000;
static const int GlobalStaticImmutable;
static constexpr int GlobalStaticConstant = 10000;
void Function()
{
static int StaticVariable1;
static inline int StaticVariable2 = 10;
static const int StaticImmutable;
static constexpr int StaticConstant = 100;
}
- 상수 표현식과 조합하면 프로그램 안에서 자주 쓰이는 값들을 모조리 미리 계산해놓을 수도 있다.
static inline constexpr을 다 기입할 필요는 없고,static constexpr만 써도 된다. C++17부터는inline지시자를 붙이면 정적 변수를 선언한 즉시 값을 정의할 수 있다.C++20부터는constinit과 조합해서 유용하게 쓸 수 있다.constinit은 상수 시간 초기화만 수행하고 불변(const)이 아니다. 덕분에 활용도가 높아지고 성능에도 도움이 된다.static inline은 값 초기화를 바로 하는 건 좋으나, 런타임에 객체를 생성하고, 생성자에서 예외가 뜰 수가 있으며, 만약 외부 데이터나 DLL의 함수를 끌어다 쓸 때는 쓸 수가 없었다.constinit은 런타임에 객체 생성이 영향을 끼치지 않게 하면서 앞선 두 문제를 해결한다. 컴파일 시간에 객체를 생성하고, 아예 잘못된 값이 들어오면 컴파일이 실패하도록 작성할 수 있다.
#!syntax cpp
static inline constinit int* memory = nullptr;
int main()
{
memory = new int;
*memory = 100;
delete memory;
}
정적 포인터가 가리키는 메모리는 여전히 할당 및 해제가 가능한데, 포인터 변수 자체는 프로그램의 시작과 끝을 함께 하는 것이 맞다. 하지만 포인터가 정적인 상태인 것이지, 포인터가 가리키는 대상은 정적이든 아니든 상관이 없다.17.2.1. 익명 이름공간 (Unnamed Namespace)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/이름공간#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/이름공간#익명 이름공간|익명 이름공간]] 부분을 참고하십시오.C++17에서
static 대신에 내부 연결만 적용할 수 있는 이름없는 이름공간이 도입되었다.17.3. RAII
자원 획득이 곧 초기화다 (Resource Acquisition Is Initialization)메모리 관리 기법 중 하나인 RAII에 대하여 알아보자. RAII는 자원[메모리]의 획득[할당]은 초기화[선언]라는 뜻이다. C++의 메모리 관리 분야에서의 주요 방법론 중 하나이다. RAII의 요점은 생성자에서 할당하고, 소멸자에서 해제하는 역할을 쥐어주는 것이다.
우리가 지금까지 C/C++을 쓰면서 변수의 할당과 해제에 대하여 깊게 생각해본 적이 있는가? 어셈블리어, 포트란이나 코볼 등의 상대적 저급 언어보다 C언어에서 가장 발전되었다고 말할 수 있는 부분이 메모리 관리다 [17]. 메모리의 추상화 기능에 의하여 그냥 자료형과 식별자만 넣으면 컴파일러와 운영체제가 알아서 해준다. 그러나 동적 할당한 가진 메모리는 여전히 수동으로 관리해야 하며 C++의 치명적인 약점 중 하나이다. 애초에 Rust의 등장 연유가 이것 때문이었으니 말이다. C++에서는 생성자와 소멸자로 메모리 초기화 방법과 정리 방법을 정의할 수 있다. C++11에서 추가된 스마트 포인터들도 소멸자에서 해제하는 기법을 활용하여 구현되었다. 게다가 소멸자는 예외가 발생하더라도 호출된다. 다른 언어의
finally 기능과 비슷하다.#!syntax cpp
struct TestClass
{
TestClass() = default;
~TestClass() = default;
int data1 = 0;
long* data2 = nullptr;
};
예를 들어서 위와 같은 클래스가 있다고 해보자. 여기서 자명한 자료형인 비정적 데이터 멤버 `data1`과 `data2`는 자동 저장소 지속시간을 가지고 있다. 알아서 메모리가 할당되고 해제된다.#!syntax cpp
int main()
{
{
TestClass instance1; // 아무것도 넣지 않은 인스턴스
}
// (1) `instance1` 해제
// `instance1`의 `data1` 해제
// `instance1`의 `data2` 해제
TestClass instance2; // 아무것도 넣지 않은 인스턴스
}
// (2) `instance2` 해제
// `instance2`의 `data1` 해제
// `instance2`의 `data2` 해제
클래스의 인스턴스가 해제되면 당연히 데이터 멤버들도 같이 해제된다.#!syntax cpp
int main()
{
{
TestClass instance1;
instance1.data2 = new long;
}
// `instance1` 해제
// `instance1`의 `data1` 해제
// `instance1`의 `data2` 해제
// `data2`가 가리키는 `long*`은 해제되지 않음. 메모리 누수!
}
그런데 이때 `data2`가 동적 할당한 포인터를 가지면 어떻게 될까? 이를 해제해주지 않으면 메모리 누수가 발생한다.17.3.1. 자신의 자원 해제
#!syntax cpp
struct TestClass
{
~TestClass()
{
// (1)
delete data2;
// (2) 조건문은 선택 사항
if (data2 != nullptr)
{
delete data2;
data2 = nullptr;
}
// (3) 조건문은 선택 사항
/*if (data2 != nullptr)*/ { delete std::exchange(data2, nullptr); }
}
int data1 = 0;
long* data2 = nullptr;
};
상기 코드는 RAII 해제의 예시를 보여주고 있다.대부분의 경우 첫번째 방식으로 충분하다. 그런데 여기서 몇가지 위험 상황을 생각해볼 수 있다. 첫번째 사례는 NULL인 포인터를 한번 더 삭제하려고 하는 경우인데 다행히도
delete 연산자는 NULL 포인터에 대해서는 아무 동작도 하지 않으므로 아무 문제가 없다. 두번째 사례는 소멸자 이후에 포인터가 외부에서 또 참조되는 경우다. 삭제한 변수를 참조하는 사태가 일어났을때, 이전에 포인터를 nullptr로 바꿔놓지 않았으면 어떤 미지의 정보가 읽힐 수 있다. [18] 그러므로 삭제한 자원은 바로 NULL로 만드는 게 좋다. C++ 벡터 문서에서 관련 예제를 소개하고 있다.17.3.2. 외부의 자원 해제
#!syntax cpp
struct Node {};
struct NodeFactory
{
struct Failsafe
{
constexpr ~Failsafe() noexcept
{
if (!safe)
{
delete std::exchange(target, nullptr);
}
}
Node*& target;
bool safe = false;
};
static constexpr std::optional<Node*> Create()
{
Node* instance = nullptr;
Failsafe failsafe{ .target = instance };
instance = new Node;
// std::construct_at에서 생성자 실행
// 이때 예외가 발생하면 `instance` 해제
std::construct_at(instance);
failsafe.safe = (instance != nullptr);
if (failsafe.safe)
{
return instance;
}
else
{
// `instance`는 자동으로 해제됨.
return std::nullopt;
}
}
};
#!syntax cpp
template<typename T, typename... Args>
[[nodiscard]]
constexpr T* AllocateMemory(Args&&... args)
{
struct Failsafe
{
constexpr ~Failsafe() noexcept
{
if (!safe)
{
delete std::exchange(target, nullptr);
}
}
T*& target;
bool safe = false;
};
T* result = nullptr;
Failsafe failsafe{ .target = result };
result = new T;
// std::construct_at에서 생성자 실행
// 이때 예외가 발생하면 `result` 해제
std::construct_at(result, std::forward<Args>(args)...);
failsafe.safe = (result != nullptr);
// 예외가 발생하더라도 `result`는 자동으로 해제됨.
return result;
}
상기 코드는 팩토리 함수에서 객체를 생성하는 예시를 보여주고 있다. 예외가 발생하더라도 동적 할당한 메모리가 자동으로 해제된다.18. 부패
부패 (Decay)지금까지 우리가 함수를 쓰면 인자가 매개변수로 복사된다는 사실을 알았다. 이 현상을 부패 (Decay)라고 한다. 부패의 목적은 함수 내부의 값과 외부의 값을 분리하고 의도치 않은 동작을 막는 것이다.
함수의 매개변수가 복사되던 이유는 이 부패 현상 때문에 그런 것이다. 부패가 일어나면 함수로 들어온 인자는 매개변수에 들어올 때 모든 한정자와 참조자를 상실한다. 인자가 갖고 있던
const, volatile, &, &&를 무시하고 값으로 전달을 시행한다.우리가 자료형 한정자와 참조자를 잘 적어야 하는 이유는 부패 과정에서 성능 하락이나 오히려 의도치 않은 동작을 일으키기 때문이다. 라이브러리와 프로그래밍 패러다임 발전과는 별개로 이 문제는 어플리케이션 개발자들도 자주 맞닥뜨릴 문제이기 때문에 조금이나마 알아두는 편이 좋다.
#!syntax cpp
void Function(int param);
int value0;
const int value1;
volatile int value2;
// (1) 매개변수 `param`에 `value0`의 값이 복사됨.
Function(value0);
// (2) 매개변수 `param`에 `value1`의 값이 복사됨.
Function(value1);
// (3) 매개변수 `param`에 `value2`의 값이 복사됨.
Function(value2);
함수 외부에서 전달된 인자의 한정자는 사라진다. 상기 코드에서 매개변수 param은 정수를 받아서 복사 생성된다.#!syntax cpp
int value;
int& ref = value;
const int& cref = value;
// (1) 매개변수 `param`에 `int&`의 값이 복사됨.
Function(ref);
// (2) 매개변수 `param`에 `const int&`의 값이 복사됨.
Function(cref);
함수 외부에서 전달된 인자의 참조자는 사라진다. 상기 코드에서 매개변수 param은 정수 lvalue를 받아서 복사 생성된다.#!syntax cpp
int value;
// (1) 매개변수 `param`에 `int&&`의 값이 이동됨. (리터럴, prvalue)
Function(1000);
// (2) 매개변수 `param`에 `int&&`의 값이 이동됨. (임시 객체, xvalue)
Function(std::move(value));
우측값 참조자 역시 사라진다. 상기 코드에서 매개변수 param은 정수 리터럴과 임시값을 받아서 이동 생성된다.#!syntax cpp
void Function1(int* param);
int array[10];
// (1) int*로 (int&)[10]의 주소가 복사됨.
Function1(array);
// void Function2(int (param)[Length])를 쓸 수도 있음.
template<size_t Length>
void Function2(int (¶m)[Length]);
// (2) (int&)[10]가 그대로 남음.
Function2(array);
C언어로부터 내려오는 배열은 포인터로 연역되는 규칙이 있다. 여기서 배열의 lvalue에서 한정자와 참조자가 증발하기 때문에 배열의 이름 자체는 배열의 첫번째 원소를 가리키는 포인터다. 배열의 속성을 전부 유지하려면 매개변수에 템플릿을 써야 한다.#!syntax cpp
// (1) 길이가 14인 문자열 리터럴.
const char string1[14] = "Good evening.";
// (2) 문자열 포인터로 연역됨.
const char* string2 = string1;
앞서 살펴본 문자열 리터럴 역시 부패 현상으로 설명할 수 있다. 문자열 리터럴을 정확하게 받기 위해 템플릿을 쓰는 예시도 확인했었다.#!syntax cpp
void Function(const volatile int param);
int value0;
const int value1;
volatile int value2;
// (1) 매개변수 `param`에 `value0`의 값이 복사됨.
Function(value0);
// (2) 매개변수 `param`에 `value1`의 값이 복사됨.
Function(value1);
// (3) 매개변수 `param`에 `value2`의 값이 복사됨.
Function(value2);
함수 매개변수의 자료형 한정자 const와 volatile는 &, &&의 참조자와 함께 보완하지 않으면 함수 안에서는 아무 의미를 갖지 못한다. 인자로 받을 수 있는 값을 제한하는 역할은 참조자가 담당하며, 한정자는 단지 코딩에서 실수를 줄이거나 모호함을 줄이기 위해 구태여 붙이는 것이다.#!syntax cpp
template<typename T>
void Function(T& param);
void Function(auto& param);
템플릿과 auto를 쓸 때도 부패 현상이 일어난다. 이에 대한 내용은 후술한다.19. decltype(expression)
|
decltype 지시자인자로 전달한 표현식을 컴파일 시간에 평가한 결과의 자료형을 얻는다. 자료형만을 평가하며 가령
decltype(50 + 400U)는 unsigned int를 반환한다. 변수의 자료형, 함수의 매개변수와 반환 자료형에 사용할 수 있다.표현식은 실제로 실행되지는 않으며 상수 표현식일 필요는 없다. 예를 들어서
decltype((int*)malloc(5000000000))같은 무지막지한 식이라도 얌전히 int*를 반환할 것이다. 성능 문제가 있을까 싶지만 C++의 타입 시스템은 반드시 컴파일 시점에 모든 자료형을 확정짓는다.이것이 유용한 예로는 정수와 큰 정수의 연산, 실수와 정수 사이의 연산, 메모리 비용이 큰 연산을 하기 전에 자료형을 가져와서 미리 준비를 할때가 있다. 서로 다른 정수 사이에는 더 바이트 수가 큰 정수로 승급하는 규칙이 있고, 실수와 정수 사이에는 실수로 변환되는 규칙이 있다. 비용이 큰 연산에는 직렬화, 문자열 보간 등이 있다. 직렬화를 하려면 원본 자료형을 더 작은 자료형으로 변환하거나, 일정한 크기의 메모리에 변환된 값들을 쓰는 과정이 필요하다. 이때 실제로 변환 작업을 하기 전에 먼저 필요한 메모리를 할당하는 수가 있다. 표준 라이브러리에서 사용하는 예로는 시간 라이브러리
<chrono>에서 서로 다른 시간 단위를 서로 연산할 때 불필요한 시간 변환 작업을 줄이기 위해서 decltype과 using을 사용하여 반환형을 미리 가져온다.20. 견본 자료형 지시자
견본 자료형 지시자 (Placeholder Type Specifiers)20.1. auto
|
auto는 C++에서 일반 사용자가 가장 유용하게 사용할 수 있는 기능 중에 하나다. C++에서 극히 희귀한 문법적 설탕 요소이며 자료형을 일일이 명시해야하는 문제를 해결하기 위해 도입되었다. 사용법은 변수와 함수에서 자료형을 작성할 때 대신 auto를 기입하면 된다. 컴파일러가 해당 변수의 자료형, 함수의 반환형을 추론하여 바이너리에 알아서 반영해준다. 라이브러리를 직접 작성하는 것이 아니라면 클라이언트 단에서는 적극적으로 사용해도 문제는 없다.주의할 점은 변수와 함수의 선언 시에 곧바로 정의를 해주지 않으면 안된다는 것이다.
auto가 어떤 자료형인지는 컴파일 시점에 결정되는데 auto 자체는 마치 참조자마냥 원래 쓰일 자료형의 별칭이라서 혼자서는 존재할 수 없다. 클래스를 작성할 때도 마찬가지로 auto를 사용한 멤버들은 정의도 동반돼야 한다.앞서 함수에 전달한 인자들이 부패된다는 사실을 살펴봤다.
auto도 같은 규칙을 따른다. auto를 쓰면 &, &&, 배열[N]이 모두 증발한 자료형이 추론된다. 그리고 반환형에 한정자가 붙은 함수를 auto로 값을 받으면 마찬가지로 한정자가 무시된다. 부패를 막는 방법 중 하나는 사용자가 직접 사용자가 직접 &, const&, && 따위의 한정자를 기입하는 것이다. 또 하나의 방법은 이후에 설명한다.#!syntax cpp
struct Counter
{
size_t& GetNumber() noexcept { return myNumber; }
const size_t& GetNumber() const noexcept { return myNumber; }
auto GetNumber2() noexcept { return myNumber; } // 한정자 없는 size_t 반환
auto GetNumber2() const noexcept { return myNumber; } // 한정자 없는 size_t 반환
size_t myNumber = 0;
};
int main()
{
Counter counter_v{ 300 };
const Counter counter_c{ 500 };
auto cnt1 = counter_v.GetNumber(); // size_t
const auto cnt2 = counter_v.GetNumber(); // const size_t
auto& cnt3 = counter_v.GetNumber(); // counter_v.myNumber의 참조 변수다.
size_t cnt4 = counter_v.GetNumber();
size_t& cnt5 = counter_v.GetNumber(); // cnt5를 통해 counter_v.myNumber 필드를 수정할 수 있다.
auto cnt6 = counter_c.GetNumber(); // size_t
auto& cnt7 = Counter{ 700 }.GetNumber(); // 형식 한정자 오류! 참조 형식에 rvalue를 전달할 수 없습니다.
// const size_t&이며 counter_v.myNumber의 불변 참조 변수다.
const size_t& cnt7 = counter_v.GetNumber();
// const size_t& 이지만 참조 변수가 아니다.
const auto& cnt8 = counter_v.GetNumber2();
}
그러나 auto를 쓰는 이유가 뭔지 생각해본다면 조금 아쉬운 면이 있다. 자주 사용하는 한정자는 const& 또는 때때로 헷갈림 방지를 위해 *가 있는데 사실 여기서 더 붙일 일은 많이 없을 것이다.20.2. decltype(auto)
|
auto는 조금 부족한 면이 있었다. auto 혼자서는 온전한 자료형을 얻을 수 없기 때문이다. 불필요한 복사가 발생하는 문제도 있다. 해결책으로는 const& 등의 한정자를 붙이는 방법이 있지만 반복적인 작업일 뿐이다. 대신 decltype(auto)는 값 범주(Value Category)까지 완벽한 자료형을 가져온다. 반환 자료형에 auto 대신에 사용하면 된다. 함수의 매개변수에는 사용할 수 없다.#!syntax cpp
// lvalue
decltype(auto) Translate(auto& value)
{
return value;
}
// rvalue
decltype(auto) Translate(auto&& value)
{
return std::move(value);
}
// 전역 변수
int i;
int main()
{
int j;
int& ref_j = j;
const int k = 1;
// (1) `result_0`는 int&&
decltype(auto) result_0 = Translate(100);
// (2) `result_1`은 int&
decltype(auto) result_1 = Translate(j);
// (3) `result_2`는 const int&
decltype(auto) result_2 = Translate(std::as_const(j));
// (4) `result_3`는 const int&
decltype(auto) result_3 = Translate(k);
}
decltype(auto)를 반환하는 함수는 값 범주(Value Category)를 최대한 준수한다. 반환하는 자료형 원본을 T라고 했을 때 함수 외부의 변수, 전역 변수, 혹은 클래스의 데이터 멤버 같이 이름이 있는 lvalue라면 T&로 추론된다. 반환하는 값이 이름없는 객체나 리터럴같은 prvalue면 T로 추론된다. 우측 참조자 형변환 따위의 xvalue라면 T&&로 추론된다.#!syntax cpp
struct MyStruct
{
int value;
};
int main()
{
MyStruct instance{ 300 };
const MyStruct immutable{ 500 };
// (1) `cnt1`은 int&
// instance.value의 참조 변수임.
decltype(auto) cnt1 = instance.value;
// (2) `cnt2`는 const int&
// immutable.value의 참조 변수임.
decltype(auto) cnt2 = immutable.value;
// (3) 컴파일 오류! decltype(auto)는 한정자를 붙일 수 없다.
const decltype(auto)& cnt3 = instance.value;
const decltype(auto)& cnt4 = immutable.value;
}
참고로 decltype(auto)는 그 자체로 완성된 자료형을 가져오므로 형식 한정자를 더 붙일 수 없다. 원한다면 using으로 별칭을 만들고 한정자를 붙여야 한다.20.3. 후속 반환형
|
함수의 반환 자료형에
auto를 사용한 경우 함수의 닫는 소괄호 맨 뒤쪽에 ->와 함께 반환 자료형을 적을 수 있다. 자료형의 한정자 때문에 완벽한 자료형을 명시할 필요가 있으나 자료형을 작성하기가 까다로우면 고려할 수 있다. 이를테면 auto Add(auto&& t, auto&& u) -> decltype(t + u) 처럼 작성할 수 있다.#!syntax cpp
auto Multiply(const int& lhs, const int& rhs) -> long long
{
//return static_cast<long long>(lhs * rhs);
return lhs * rhs;
}
명시한 자료형으로 형변환된다.#!syntax cpp
// 반환 자료형은 int&
auto AddTo(int& lhs, const int& rhs) -> decltype(lhs += rhs)
{
// int의 += 연산자는 int&를 반환함.
return lhs += rhs;
}
decltype은 자료형을 부패시키지 않고 온전히 보전한다.#!syntax cpp
int x = 1;
// 반환 자료형은 int
// decltype(auto) f() { return x; }과 같음.
auto f() -> decltype(x) { return x; }
// 반환 자료형은 int&
// decltype(auto) f() { return(x); }과 같음.
auto f() -> decltype((x)) { return x; }
decltype의 특징을 다시 확인해보자.#!syntax cpp
// lvalue
auto TranslateV2(auto& value) -> decltype(auto)
{
return value;
}
// rvalue
auto TranslateV2(auto&& value) -> decltype(auto)
{
return std::move(value);
}
// 전역 변수
int i;
int main()
{
int j;
int& ref_j = j;
const int k = 1;
// (1) `result_0`는 int&
decltype(auto) result_0 = TranslateV2(i);
// (2) `result_1`는 int&
decltype(auto) result_1 = TranslateV2(j);
// (3) `result_2`는 const int&
decltype(auto) result_2 = TranslateV2(std::as_const(j));
// (4) `result_3`는 const int&
decltype(auto) result_3 = Translate(k);
// (5) `result_4`는 int&&
decltype(auto) result_4 = TranslateV2(10);
// (6) `result_5`는 int&&
decltype(auto) result_5 = TranslateV2(std::move(j));
}
decltype(auto)도 후속 반환형으로 쓸 수 있다.21. 상수 표현식 (Constant Expression)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/상수 표현식#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/상수 표현식#|]] 부분을 참고하십시오.C++11 부터 도입된 상수 표현식, 또는 상수식은 실제 코드가 실행되는 실행 시점(런타임)이 아니라 컴파일 시점(컴파일 타임)으로 코드의 평가를 앞당길 수 있는 획기적인 기능이다. C++의 킬러 요소라고 말할 수 있는 핵심 기능이다.
21.1. static_assert
|
정적인 표명문은
C++11에서 추가된 기능 중 하나로 전달받은 상수 표현식을 평가한 값이 거짓(false)이면 컴파일 오류를 띄우는 기능이다. 인자의 상수 표현식에는 정수를 포함하여 문맥 상 bool을 반환하는 표현식이 올 수 있다. 다시 말하면 bool로 암시적으로 변환할 수 있는 표현식이 올 수 있다[19].기존에 존재하던 자료형 관련 기능의 구멍을 메우는 역할을 한다. 원래는 SFINAE가 줄창 사용되었으나 이 기능으로 일부분 대체할 수 있다.
#!syntax cpp
#include <type_traits>
#include <string>
struct Flag
{
constexpr operator bool() const noexcept { return value; }
bool value;
};
int main()
{
constexpr Flag True{ true };
constexpr Flag False{ false };
// (1) 항상 성공하는 표명문
// 여기에 전달된 문자열들은 코드 평가 과정에서 배제되고 메모리에 할당되지도 않음.
static_assert(true); // C++17
static_assert(true, "Uneducated string"); // C++11
static_assert(true, std::string{ "Nothing happened" }); // C++26
// (2-1) 항상 실패하는 표명문
// 여기에 전달된 문자열들은 코드 평가 과정에서 배제되고 메모리에 할당되지도 않으며 다만 오류 메시지로 출력됨.
// C++20까지는 false를 그대로 전달할 수 없었다!
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2593r1.html 참고
static_assert(false); // C++20
static_assert(false, "Error caused"); // C++20
static_assert(false, std::string{ "Stranger things" }); // C++26
// (2-2) C++20 이전 버전에서는 이런 변수 템플릿을 만들어서 써야했다.
// 템플릿이 실제로 쓰이는 실체화 시점까지 평가되지 못하도록 미루면 표명문도 늦게 평가된다.
template<typename>
constexpr bool AlwaysFalse = false;
// (3) 암시적으로 bool로 변환되는 자료형
static_assert(1);
static_assert(True);
static_assert(Flag{ true });
static_assert(0);
static_assert(False);
static_assert(Flag{ false });
// (4) 메타 함수 활용
static_assert(std::is_constructible_v<int, float>); // true
static_assert(std::is_constructible_v<double, long>); // true
static_assert(std::is_constructible_v<Flag, bool>); // true
static_assert(std::is_constructible_v<std::string, std::vector<char>>); // false
}
상기 코드는 정적 표명문의 사용 예시를 보여주고 있다.한편 인자에 들어가는 미평가 문자열(Unevaluated String)들은
C++26부터는 컴파일 시간에만 존재하며, 더 이상 메모리에 존재하지 않는 값이 되었다.21.2. if constexpr
상수 조건 표현식 (Constexpr If)C++17상수 조건 표현식 내지는 상수 조건문은 반드시 컴파일 시간에 조건문을 실행하도록 지시하는 구문이다. C++ 코드에서는
if 뒤에 constexpr을 붙이고 상수 표현식을 조건으로 쓰면 된다.기존에도 조건문에 상수 표현식을 넣을 수 있었지만, 조건문이 런타임에 평가되며 다른 조건문과 다를 바 없는 분기 코드를 만들었다. 이제 상수 조건식을 쓰면 매크로와 템플릿과 마찬가지로 C++ 코드로만 보이며, 컴파일 시간에 모든 평가를 끝내고 실행 결과만을 최종 코드에 반영할 수 있다.
#!syntax cpp
if constexpr (false)
{
// 컴파일 결과에서 증발함.
}
else
{
// 처음부터 조건문이 없는 것처럼 컴파일함.
}
최종 결과만 반영된다는 말은 조건문으로 걸러진 코드는 아예 삭제된다는 뜻이다. 디버그 모드에서도 마찬가지로. 만약 쓸 수 있다면 사기적인 최적화가 가능하기 때문에 반드시 쓰는 것이 좋다.#!syntax cpp
#include <type_traits>
#include <tuple>
template<typename... Ts>
consteval bool IsIntegrals(const std::tuple<Ts...>&) noexcept
{
if constexpr (std::is_integral_v<Ts> && ...)
{
return true;
}
else
{
return false;
}
}
template<>
consteval bool IsIntegrals(const std::tuple<>&) noexcept
{
return false;
}
상기 코드는 std::tuple[20]에 속한 자료형이 전부 정수인지 확인하는 함수를 보여주고 있다. 이 함수는 상수 시간에 평가되며 컴파일 시점에 값을 바로 알 수 있다.21.3. noexcept(expr)
noexcept(expr) 연산자C++20C++20에서 noexcept에 함수처럼 괄호와 표현식을 전달하면, 그 표현식이 예외를 던지는지 아닌지 평가하는 기능이 추가되었다.#!syntax cpp
// 상수 표현식인지 아닌지는 상관없음.
void Function1();
void Function2() noexcept;
// (1) `nothrow1`의 값은 false
constexpr bool nothrow1 = noexcept(Function1());
// (2) `nothrow2`의 값은 true
constexpr bool nothrow2 = noexcept(Function2());
noexcept(expr) 연산자는 bool을 반환하는 상수 표현식이다. 이때 인자로 전달하는 표현식은 상수 표현식이 아니여도 된다. 왜냐하면 표현식 안에 실행되는 함수와 연산자의 서명이 noexcept인지 아닌지만 검사하기에, 항상 상수 시간에 검사할 수 있어서다.#!syntax cpp
struct NaturalPlacement
{
// 자명한 자료형의 기본 생성자와 집결(Aggregation) 초기화 생성자는 noexcept(true)
int x = 0;
int y = 0;
};
struct ArtificialPlacement
{
// 자명한 자료형의 기본 생성자는 noexcept(true)
constexpr ArtificialPlacement() = default;
// noexcept(false)
constexpr ArtificialPlacement(int xv, int yv)
: x(xv), y(yv)
{}
int x = 0;
int y = 0;
};
// (1) `nothrow1`의 값은 true
constexpr bool nothrow1 = noexcept(NaturalPlacement{});
// (2) `nothrow2`의 값은 true
constexpr bool nothrow2 = noexcept(NaturalPlacement{ 8620, 7430 });
// (3) `nothrow3`의 값은 true
constexpr bool nothrow3 = noexcept(ArtificialPlacement{});
// (4) `nothrow4`의 값은 false
constexpr bool nothrow4 = noexcept(ArtificialPlacement{ 8620, 7430 });
상기 코드는 클래스의 생성자의 예외 여부를 확인하는 예제다. 여기서 자명한 클래스 `NaturalPlacement`의 생성자는 모두 예외를 던지지 않는다. 그런데 4번의 결과가 다른데, 놀라울 것이 없다. `ArtificialPlacement`의 두번째 생성자에 noexcept가 없어서 기본적으로 예외를 던지는 것으로 표시되기 때문이다. 명백하게 아무 문제없는 코드이지만 noexcept를 다는 걸 까먹으면 이런 일이 생기기도 한다.분명 성능과 유지보수에 도움이 되는 기능이지만
noexcept를 모든 함수에 다는 건 Java의 try-catch 강제나 다름이 없다. 너무 귀찮다면 당장은 달지 말거나 혹은 생성자에만 다는 것도 괜찮다.22. 템플릿 (Template)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿#|]] 부분을 참고하십시오.23. 템플릿 제약조건 (Constraint)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿 제약조건#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿 제약조건#|]] 부분을 참고하십시오.C++에서 템플릿 매개변수에 대하여 조건을 달 수 있는 기능이 생겼다. 이 조건은 컴파일 시점에 평가되며 런타임엔 성능이 영향이 없도록 최적화된다.
23.1. 개념 (Concept)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿 제약조건#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿 제약조건#concept|concept]] 부분을 참고하십시오.24. 람다 표현식 (Lambda Expression)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/람다 표현식#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/람다 표현식#|]] 부분을 참고하십시오.25. 메타 프로그래밍 (Meta Programming)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/메타 프로그래밍#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/메타 프로그래밍#|]] 부분을 참고하십시오.26. 코루틴 (Coroutine)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리/coroutine#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리/coroutine#|]] 부분을 참고하십시오.27. 둘러보기
||<:><-12><width=90%><tablewidth=100%><tablebordercolor=#20b580><rowbgcolor=#090f0a,#050b09><rowcolor=#d7d7d7,#a1a1a1>C++||||
}}}
}}}||
}}}||
| C언어와의 차이점 | 학습 자료 | 평가 | |||||||||||||
| <bgcolor=#20b580> | |||||||||||||||
| <rowcolor=#090912,#bebebf>C++ 문법 | |||||||||||||||
| <bgcolor=#ffffff> | |||||||||||||||
main | 헤더 | 모듈 | |||||||||||||
| 함수 | 구조체 | 이름공간 | |||||||||||||
| 한정자 | 참조자 | 포인터 | |||||||||||||
| 클래스 | 값 범주론 | 특성 | |||||||||||||
auto | using | decltype | |||||||||||||
| 상수 표현식 | 람다 표현식 | 객체 이름 검색 | |||||||||||||
| 템플릿 | 템플릿 제약조건 | 메타 프로그래밍 | |||||||||||||
| <bgcolor=#20b580> | |||||||||||||||
| <rowcolor=#090912,#bebebf>C++ 버전 | |||||||||||||||
| <bgcolor=#ffffff> | |||||||||||||||
| C++26 | C++23 | C++20 | |||||||||||||
| C++17 | C++14 | C++11 | |||||||||||||
| C++03 | C++98 | C with Classes | |||||||||||||
| <bgcolor=#20b580> | |||||||||||||||
| <rowcolor=#090912,#bebebf>C++ 표준 라이브러리 | |||||||||||||||
| <rowcolor=#090912,#bebebf>문서가 있는 모듈 목록 | |||||||||||||||
| <bgcolor=#ffffff> | |||||||||||||||
#개요 | C++11 #개요 | <unordered_map>C++11 #개요 | |||||||||||||
C++20 #개요 | #개요 | #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++17 #개요 | |||||||||||||
#개요 | <string_view>C++17 #개요 | C++20 #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++11 #개요 | |||||||||||||
C++20 #개요 | C++23 #개요 | ||||||||||||||
| <bgcolor=#20b580> | |||||||||||||||
| <rowcolor=#090912,#bebebf>예제 목록 | |||||||||||||||
| <bgcolor=#ffffff> | |||||||||||||||
| {{{#!wiki style=""text-align: center, margin: 0 -10px" {{{#!folding [ 펼치기 · 접기 ] | 임계 영역과 경쟁 상태std::mutex | 개선된 스레드 클래스std::jthread | 동시성 자료 구조 1 스레드 안전한 큐 구현 | 동시성 자료 구조 2 스레드 안전한 집합 구현 | |||||||||||
메모리 장벽std::atomic_thread_fence | 스레드 상태 동기화 1 스레드 대기와 기상 | 원자적 메모리 수정std::compare_exchange_strong | 스레드 상태 동기화 2 스핀 락 구현 | ||||||||||||
| 함수 템플릿 일반화 프로그래밍 | 전이 참조 완벽한 매개변수 전달 | 튜플 구현 가변 클래스 템플릿 | 직렬화 함수 구현 템플릿 매개변수 묶음 | ||||||||||||
| SFINAE 1 멤버 함수 검사 | SFINAE 2 자료형 태그 검사 | SFINAE 3 메타 데이터 | SFINAE 4 자료형 트레잇 | ||||||||||||
| 제약조건 1 개념 (Concept) | 제약조건 2 상속 여부 검사 | 제약조건 3 클래스 명세 파헤치기 | 제약조건 4 튜플로 함자 실행하기 | ||||||||||||
| 메타 프로그래밍 1 특수화 여부 검사 | 메타 프로그래밍 2 컴파일 시점 문자열 | 메타 프로그래밍 3 자료형 리스트 | 메타 프로그래밍 4 안전한 union | ||||||||||||
}}}||
| <bgcolor=#20b580> | |||||||||||||||
| <rowcolor=#090912,#bebebf>외부 링크 | |||||||||||||||
| <bgcolor=#ffffff> | |||||||||||||||
| {{{#!wiki style=""text-align: center, margin: 0 -10px" | |||||||||||||||
| <bgcolor=#20b580> | |||||||||||
| <rowcolor=#090912,#bebebf>C++ |
[1] bool은 'Boolean'의 줄임말이다.[2] 호출 연산자[3] 이를 인라이닝(Inlining)이라고 한다[4] 이를 다른 스레드에서 보이지 않는 값이 되었다고 한다[5] MSVC에서만[6] 이를 객체 (Object)가 아니다라고 한다[7] 예를 들어 if (handle == NULL) 같은 경우[8] 이는 C++도 해당된다[9] 이러면 메모리 주소를 얻을 수 있고 변수의 값을 바꿀 수는 있겠지만 성능 상의 이득이 사라진다[10] 그러나 C++14 이하의 버전을 사용해야만 하는데 문자열 처리를 구현해야 하면 공부해서 나쁠 건 없다[11] 최적화를 위해 프로그램의 문자열 리터럴을 한데 모은 문자열 풀(Pool)을 구성하고 가져다 쓰기도 한다[12] 런타임 오버헤드 때문에 상대적으로 느리기도 하지만[13] 기본 생성자는 default가 아니여도 된다[메모리] [할당] [선언] [17] 정확하게 말하자면 자동 저장소 지속시간 덕분에 메모리 관리에 대하여 씨름할 필요가 매우 줄었다[18] delete의 사례는 버퍼 오버플로우는 아니지만, 비슷한 하트블리드 참조[19] explicit 생성자 등의 이유로 직접 변환을 해야한다면 그리 해줘야 한다[20] 관련 내용은 튜플 참조