#!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# · 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. 함수6. 구조체 (Struct)7. 자료형 한정자8. 참조자9. 부패10. 문자열 리터럴 (String Literal)11. 이름공간 (Namespace)12. 클래스 (Class)13. 값 범주 (Value Category)14. using15. 견본 자료형 지시자16. Ranged For-Loop17. 조건문 초기자 구문18. 상수 표현식 (Constant Expression)19. 템플릿 (Template)20. 템플릿 제약조건 (Constraint)21. 특성 (Attribute)22. 람다 표현식 (Lambda Expression)23. 메타 프로그래밍 (Meta Programming)24. 언어 연결성 (Language Linkage)25. 코루틴 (Coroutine)26. 저장소 지속요건과 객체 수명26.1. 동적 저장소 지속요건
27. RAII28. 둘러보기26.1.1. 호환성
26.2. 정적인 저장소 지속요건26.2.1. 전역 변수26.2.2. static 변수26.2.3. static 함수
26.3. 자동 저장소 지속요건26.4. 스레드 저장소 지속요건26.2.3.1. 주요 용도
26.2.4. static 지역 변수26.2.5. 정적 멤버 함수26.2.6. 정적 데이터 멤버26.2.7. extern26.2.8. extern 함수26.2.9. 정적인 객체의 주소26.2.10. 익명 이름공간 (Unnamed Namespace)1. 개요
C++의 문법을 전반적으로 설명하는 문서이다. C언어하고도 중첩되는 요소들이 많으므로 이 문서를 쉽게 이해하기 위해서는 C언어/문법 문서와 비교하여 참조하는 것이 좋다. 하지만 C언어의 문법에 객체지향 문법만 안다고 해서 C++를 잘 아는건 아니다. 일단 C++11 이후로 추가된 기능이 엄청나게 많기 때문이다. 템플릿 공부도 많이 하는 것이 추천되는 편이다.2. Hello, world!
#!syntax cpp
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
C++23 이후에
<print> 모듈의 print, println 함수를 사용할 수 있게 되었다. 고전적인 방식으로는 <iostream> 모듈의 cout 객체와 << 연산자를 통한 출력을 사용할 수 있다. 또한 C언어처럼 <cstdio> 혹은 <stdio.h>를 통해, printf 함수도 사용 가능하다.아래는 print 모듈을 이용한 예시이다.[1]
#!syntax cpp
import <print>;
int main()
{
std::println("Hello, world!");
std::println("{}, {} {}!", "Hello", "world", "again");
return 0;
}
3. 프로그램 진입점
[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, body_bclose=1)] |
main 함수일반적으로 C++의 프로그램은 항상 진입점 함수
main을 요구한다. main 함수는 C++ 프로그램의 시작과 끝을 담당한다. 사용자가 main 함수의 중괄호 안에 코드를 작성하고 컴파일하면 해당 코드를 실행한다.표준에 따르면
main 함수를 정의하는 방식은 두 가지가 있다. 첫번째는 아무런 매개변수가 없이 소괄호만 있는 경우다. 대부분의 경우 이걸로 충분하다. 두번째는 외부에서 문자열로 된 매개변수가 전달된 main 함수다. 많은 운영체제에서는 프로그램을 실행할 때 외부에서 매개변수를 전달할 수 있다. 가령 Windows에서는 프로그램의 바로가기를 만들고 프로그램 경로 뒤에 -path "C:\\Program Files\\Microsoft"처럼 매개변수를 전달할 수 있다.이 함수는 프로그램 종료 시엔 정수로 된 종료 부호를 반환한다. 가령 콘솔에서 실행하는 프로그램은 프로그램이 반환한 부호를 확인할 수 있다. 이 부호는 운영체제마다 다른 의미를 갖고 있는데, 보통
0을 반환하면 main 함수의 실행에 오류가 없이 성공적으로 종료되었다는 뜻이다. 그러나 사용자는 return 0;를 반드시 쓰지 않아도 된다. 특별한 규칙으로서, 반환 구문이 없어도 컴파일러가 알아서 처리해준다.4. 원시 자료형
| true와 false 두 가지 진릿값을 표현하는 논리 자료형[2]으로, 크기는 구현에 따라 달라질 수 있으나, 대부분의 컴파일러에서 1바이트로 구현된다. | |
| 문자(character)를 저장하기 위해 정의된 자료형이자, C++에서 메모리의 최소 단위(바이트)를 나타내는 특별한 정수형으로, 항상 1바이트 크기이지만 8비트라는 보장은 없다. 기본적으로 x86/x64에서는 signed char, ARM/PowerPC에서는 unsigned char로 동작한다. | |
| 멀티바이트 문자를 나타내는 자료형. 보통 아스키 코드 외의 문자를 표현하는데 사용한다. Windows 환경에서는 2바이트, Linux 환경에서는 4바이트다. | |
| 2바이트 정수를 나타내는 자료형. | |
| 부호 없는 2바이트 정수를 나타내는 자료형. | |
| 4바이트 정수를 나타내는 자료형. | |
| 부호 없는 4바이트 정수를 나타내는 자료형. | |
| 4바이트 정수를 나타내는 자료형. | |
| 부호 없는 4바이트 정수를 나타내는 자료형. | |
| 8바이트 정수를 나타내는 자료형. | |
| 부호 없는 8바이트 정수를 나타내는 자료형. | |
| 4바이트 부동 소수점을 나타내는 자료형. | |
| 8바이트 부동 소수점을 나타내는 자료형. | |
| Windows에서는 8바이트 부동 소수점을 나타내며, Linux에서는 10바이트에서 16바이트 사이의 부동 소수점을 나타내는 자료형. | |
| C++20 | UTF-8 표준 단위인인 1바이트 문자를 나타내는 자료형. |
| C++11 | UTF-16 표준의 문자 단위를 나타내는 자료형. |
| C++11 | UTF-32를 담을 수 있도록 4바이트 문자를 나타내는 자료형. |
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. 함수
[[특성]] |
함수는 사실
main 함수 말고도 사용자가 직접 만들 수 있다. 여기서 말하는 함수는 C++에서 실행할 수 있는 코드의 집합이자 하나의 분기점이다. 이렇게 작성해 두었다가 함수의 이름과 함께 ()[호출]를 붙여 실행할 수 있다. 함수가 실행되면 결과 값을 반환하고 원래 호출 스택으로 되돌아온다. 앞서 살펴본 main 함수는 사용자가 이용할 수 있는 함수 중의 하나다. main 함수는 프로그램 안에서 유일해야 한다는 제약만 빼면 마찬가지로 정수를 반환하는 보통의 함수와 다를 것이 없다. C++ 개발자는 함수를 통해 코드를 쉽게 정리하고 재사용할 수 있다. 궁극적으로 더 효율적이고 유지보수가 용이한 프로그램을 만들 수 있다.함수의 정의엔 기본적으로 반환-자료형, 식별자, 소괄호가 필요하며 그리고 선택적으로 매개변수를 추가할 수 있다. 또한 연결성 지시자(
static, extern), 예외 사양(nothrow/noexceptC++11 ), 평가 지시자(constexprC++11 , constevalC++20 ), 혹은 특성C++11 을 부착할 수도 있다.5.1. 반환 자료형
|
main 함수는 32비트 int를 반환하는 규칙이 있으므로 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 #include <iostream>; 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언어에서 가장 발전되었다고 볼 수 있는 기능이다. 원래 C언어에서는 모든 함수의 이름이 무조건 달라야 했으나 비슷한 자료형을 인자로 받고 동일한 동작을 수행하는 함수들도 다른 이름으로 구별해야하는 불편함이 있었다. 예를 들어서
int 또는 long long을 받아 문자열로 바꾸는 함수가 있다면, 그 함수의 이름은 ConvertIntToString(), ConvertLongLongToString() 따위로 분리해야 했었다. 여기에 이름공간의 부재로 인한 식별자 부족 현상도 있었다. C++에서는 ConvertToString() 처럼 같은 양식의 식별자로 통일하고 어디에서나 일관적인 코드 작성이 가능해졌다.한편 오버로딩을 할 때 반환 자료형만 다르게는 만들지 못한다. 함수 오버로딩은 매개변수의 변형을 기준으로 함수를 구분한다. 즉 매개변수가 달라야 중복 정의를 수행할 수 있다.
5.4. inline
... |
inline 함수inline을 반환-자료형 앞에 붙이면 해당 함수의 쓰임새 부분이 함수의 코드 자체로 대체될 수 있음을 나타낸다 [4]. 이 기능이 적절하게 쓰이면 함수 호출 오버헤드를 줄이고, 호출 스택도 아낄 수 있어서 좋다. 심지어 최적화 과정에서 아예 함수의 코드를 날리고 결과값만 남길 수도 있다. 그렇지만 대부분의 현대 컴파일러는 알아서 처리를 해준다. 그래서 이 지시자의 의의는 코드를 읽는 다른 개발자들에게 이 함수가 인라이닝이 되도록 설계되었다는 것을 알리면서 컴파일러에게 더 적극적으로 인라이닝을 하라는 지시에 가깝다.inline 함수는 정의와 선언이 같이 행해져야 한다. 정의가 없는 inline 함수는 컴파일 오류를 발생시킨다.한편 C++17부터는
inline 함수에 새로운 규칙이 생겼다. 컴파일러의 해석 단위(Translation Unit)[헤더]의 이름공간 안의 외부 연결인 함수[6]는 이름이 같으면 마찬가지로 같은 명세를 가지도록 정의하게 되었다. 즉 기존의 static 함수와 같은 규칙을 가지게 되었다. 다시 말해서 여러 헤더에서 참조하는 동일한 동일한 이름의 inline 함수는 매개변수나 noexcept 등의 정의가 달라지면 안된다는 뜻이다. 이런 규칙이 제정된 이유는 헤더를 삽입했는데 같은 서명을 가진 함수가 다른 객체로 인식되는 문제가 있기 때문이다. 같은 헤더의 같은 이름공간에서 같은 이름의 함수를 가져왔는데 어떻게 다른 함수일 수 있겠냐는 것이다. 그리고 이로써 inline 함수는 프로그램 내에서는 똑같은 이름은 곧 동일한 함수며 따라서 항상 같은 메모리 위치에 존재함이 보장되었다.5.5. noexcept
|
noexcept를 통해 지정할 수 있다. 이를 통해 컴파일러에게 예외 검사를 배제하도록 지시할 수 있다. 예외를 던질지 말지는 사용자의 자유이지만, 확실하게 오류가 없는 함수라면 noexcept를 붙이면 된다.C++20부터는 noexcept(표현식)를 통해 선택적으로 예외 여부를 지정할 수도 있다. 이를 위해 표준 라이브러리에서는 <type_traits> 모듈에서 std::is_nothrow_*같은 명칭의 메타 함수를 제공하고 있다. noexcept(expr)안의 표현식은 상수 시간에 예외 여부만 평가되기 때문에 복잡한 코드가 달려있다고 성능에 문제는 생기지 않는다.6. 구조체 (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)와 거의 같은 역할을 수행한다. 자세한 내용은 이후의 클래스 문서를 참조하자.
7. 자료형 한정자
#!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)
7.1. const
|
다른 자료형 앞에 붙어 값이 불변함을 나타낸다. 가령
const int는 변하지 않는 정수 값임을 의미한다. 사용자 단에서는 변수 선언 시에 const를 붙여 그 값을 변경하지 못하도록 할 수 있다. 함수 구현 시에는 매개 변수에 붙여 사용자의 실수를 줄이고 예측할 수 없는 값의 수정을 막을 수 있다.7.2. volatile
|
변수 선언 시에 자료형 앞에 붙이면 그 변수에 접근할 때 캐시에 대하여 최적화를 막고 항상 값이 보이도록 한다. 이를 이해하려면 운영체제와 컴파일러에 대해 이해가 필요하다. 컴파일러는 바이너리를 구축하는 과정에서 인라이닝, 캐싱, 파이프라인 분기 예측, 상수식 평가 등을 동원해 가장 빠른 바이너리를 만든다. 최적화를 하는 과정에서 몇가지 변수와 상수 표현식으로 선언된 상수는 없어진다. 컴파일 시간에 평가할 수 있는 구문은 미리 작성한다. 그리고 평가되지 않을 구문 역시 아예 코드에서 배제된다. 심지어는 함수의 존재 자체가 없어지고 모든 쓰임새가 반환값으로 대체될 수도 있다. 예를 들어 어떤 동일한
for문이 여러 곳에서 반복된다면 for문의 내용을 진짜 반복하는 대신에 결과를 미리 계산해놓고 가져다 쓸 것이다.이 과정은 거의 대부분의 경우 이득을 가져다 주지만, 변수의 존재 삭제와 구문 단축이 문제가 된다. 다중 스레드 환경의 예를 들어보자. 한 스레드에서
while(bool 변수);로 문맥의 흐름을 막았다고 해보자. 그럼 while문 안의 bool 변수의 값이 다른 스레드에서 false로 바뀌면 무한 루프를 빠져나갈까? 정답은 그렇지 않다. 계속 정지 상태로 남아있다. 왜냐하면 최적화를 위해서 while안의 변수는 메모리를 계속 읽는 대신에, 컴파일 순간 정해진 캐시의 값을 가져오기 때문이다. 이를 다른 스레드에서 보이지 않는 값이 되었다고 한다. 또다른 예로는 프로그램 내내 같은 주소를 가리키는 포인터의 값을 읽을 필요가 있다고 해보자. 이러면 컴파일러는 컴파일 당시에 해당 포인터가 가리켰던 값만을 가져온다. 그래서 해당 포인터가 가리키는 값이 변경되어도 프로그램에선 바뀐 값을 알지 못한다. 바로 이럴때 volatile을 붙여서 캐시말고 항상 메모리에서 가져오도록 만들 수 있다. 즉 휘발성이라는 단어는 현재 변수에서 읽어오는 값이 임시적이라는 뜻이다.참고로
volatile은 컴파일러의 캐시 최적화, 인위적인 코드 패치 순서 수정[7]을 막을 뿐이므로 다중 스레드를 사용할 때는 여전히 값의 읽기 순서에 따른 동기화 문제가 발생할 수 있다. 이를 해결하는 방법은 원자적 메모리 모듈을 응용하는 것이다.8. 참조자
참조자 (Reference)8.1. 좌측값 참조자
|
변수 선언 또는 함수 정의 시에
&를 자료형 뒤에 붙여 이 값이 참조 형태임을 나타낼 수 있다. 가령 int&는 다른 정수를 참조하는 변수임을 의미한다. 참조자를 통해 참조자가 가리키는 원본 필드에 접근할 수 있다. 어떤 식으로 동작하는지 살펴보자.#!syntax cpp
int main()
{
int integer;
int& int_ref = integer;
long long very_long;
long long another_very_long = 100'000;
long long& longlong_ref = very_long;
// (1) `int_ref`는 `integer`를 가리키므로 `integer`에 30이 대입됨.
int_ref = 30;
// (2) `longlong_ref`는 `very_long`을 가리키며 참조 대상은 수정할 수 없음.
// `longlong_ref`에 `another_very_long`의 값인 100000이 대입됨.
longlong_ref = another_very_long;
}
첫번째는 참조 변수를 만들고 수정하는 방법이다. 참조자를 만드려면 반드시 참조할 다른 변수의 이름이 필요하며, 자료형이 동일해야만 한다. 참조자가 한번 정의되면 참조자가 가리키는 대상은 다신 바꿀 수 없다. 만약 참조자에 값을 대입하면 원래 변수에 값이 전달된다.#!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()
{
// 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로 참조되어 이 참조 변수를 통해 원래 변수를 수정할 수 있다. 만약 & 연산자를 변수 앞에 붙여 주소를 얻으면 참조 변수의 주소가 아니라, 가리키는 변수의 주소가 나온다. 주소를 가져오는 C++/표준 라이브러리의 std::addressof함수를 사용해도 마찬가지다.참조한다는 것은 해당 변수가 스스로 값을 가지지 못하고, 다른 변수의 본체를 가리킨다는 뜻이다. 마치 포인터처럼 말이다. 그러면 포인터랑 대체 다른 것이 뭔가? 라면 일단 일반 사용자 단에서는 변수를 사용할 때
(*handle)이나 handle->...을 사용하지 않아도 된다. 메모리에 대해 조금 알고 있다면 포인터처럼 임의 주소 참조나 보안 문제가 없을 거라는 예상을 할 수 있다. 그러나 깊게 파고들어가면 아주 이상한 특징이 있다. 참조자 자체는 어떤 주소, 고유한 값을 가지지 않는다. 즉 참조 변수는 스스로 존재할 수 없다[8]. 바로 이름만 가진 변수다. 참조자는 명백하게 존재하고 여기저기 갖다 쓸 수 있지만 참조자가 가리키는 어떤 변수만 조작할 수 있을 뿐, 정작 참조 변수 자체는 건드릴 방법이 없다. 이것이 포인터와의 차이점이다.결론적으로 참조자는 값이 아니라 다른 변수의 별칭(Alias)이라고 볼 수 있다. 참조자를 정의할 때는 어떠한 값의 복사나 수정 오버헤드도 일어나지 않는다. 어떠한 조작 없이 원래 변수에 다른 식별자를 붙여주었을 뿐이기 때문이다!
그런데 변수의 이름만 바꿔 부르는 이 단순한 기능을 어디에 쓰는 걸까? 참조자는 함수에서 더 잘 활용할 수 있다.
8.1.1. 함수에 좌측값 참조로 전달
|
함수의 매개변수인 참조 변수는 함수에 전달된 외부 변수를 그대로 이용할 수 있다. 즉 매개변수로 복사되지 않고 원본을 그대로 사용할 수 있다. 앞서 C++에서 참조 변수를 쓴다는 것은 변수에 일종의 별칭을 붙여주는 것이라고 했다. 함수 내부에서 인자로 전달된 값에 새로운 이름을 붙여주는 셈이다.
#!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[4];
int localPlayerIndex = 0;
void PlayerMoveX(Player& player, float dist)
{
player.x += dist;
}
void PlayerMoveY(Player& player, float dist)
{
player.y += dist;
}
[[nodiscard]]
float PlayerGetHealth(Player& player)
{
return player.myHealth;
}
int main()
{
everyPlayers[localPlayerIndex] = Player{ 20, 180, 100.0f };
everyPlayers[localPlayerIndex + 1] = Player{ 180, 180, 100.0f };
everyPlayers[localPlayerIndex + 2] = Player{ 20, 20, 100.0f };
everyPlayers[localPlayerIndex + 3] = Player{ 180, 20, 100.0f };
// (1)
PlayerMoveY(everyPlayers[localPlayerIndex + 2], 30);
// (2) 컴파일 오류! 참조 형식에 rvalue를 전달할 수 없습니다.
PlayerMoveX(Player{ 100, 100, 100.0f }, 50);
}
상기 코드는 플레이어 이동 및 체력을 얻는 함수를 임의의 플레이어가 쓸 수 있도록 고친 예시를 보여주고 있다. 각 함수의 첫번째 매개변수로 Player의 참조자를 받아서 개체의 멤버 변수에 접근한다 [9].참조형을 사용할 때 한가지 주의할 점이 있다.
const가 아니면 임시 객체와 리터럴 값을 이용할 수 없다는 것이다. 임시 객체와 리터럴 값은 이름이 없어서 스스로 존재할 수 없으며 수정할 수 없는 값들이다. 가령 예제의 Player{ 100, 100, 100.0f }는 Player 구조체의 이름이 없는 임시 객체다. 그리고 100.0f 따위의 숫자값은 리터럴 값이다. & 참조자는 수정이 가능한 값만 받을 수 있다. 그러므로 const를 붙여서 상수 좌측 참조자를 만들어야 임시 객체와 리터럴 값을 받을 수 있다.이 예제의
PlayerMoveX, PlayerMoveY는 원래 수정되어야 할 플레이어 객체를 사용하므로 임시 값을 받는 게 오히려 문제가 된다. 이런 식으로 원본이 필요한 곳에는 &를, 아닌 곳에는 일반 자료형을 쓰면 문제가 다수 해결된다. 간단한 어플리케이션을 개발하는 정도에선 적재적소에 참조자를 쓰는 정도로 충분하다.8.2. 우측값 참조자
자료형&& 변수-식별자 = static_cast<자료형&&>(이동대상-식별자); 자료형&& 변수-식별자 = 함수-식별자(...); |
C++11에서 새로 도입된 인자 전달 방식이다.
&&를 자료형 뒤에 붙이면 이름이 없는 임시 객체 또는 리터럴을 가리키는 참조자임을 나타낼 수 있다.주요 목적은 메모리의 잦은 할당을 방지하며 재사용을 지시하고, 깊은 복사를 막는 것이다. 우측값 참조자를 통해 변수가 사용되는 메모리 공간에 복사없이, 마치 처음부터 존재하는 것처럼 처리할 수 있다. 그래서 다른 말로 이동 연산자라고도 부른다. 복사가 아예 일어나지 않기에 값의 교환을 효율적으로 할 수 있다. 즉 최적화를 위해 추가된 한정자인데, 그 경위를 자세히 살펴보자.
원래 임시 객체 또는 리터럴 값들은 전통적으로 C언어에서
rvalue(Right-Value, 우측값)라고 칭한다. 왜냐하면 임시 값이 선언, 대입, 비교문에서 식의 오른쪽에 놓이는 경향이 있어서 이렇게 지칭되어 왔다 [10]. 우측값의 종류에는 구조체&클래스의 생성자를 호출해서 만든 임시 객체, 숫자 리터럴, & 참조가 아닌 함수의 반환 값 등이 있다.C언어는 고성능 언어지만 변수의 대입 및 함수의 인자 전달 과정에서 깊은 복사가 일어나는 문제가 있다 [11]. 그래서 깊은 복사를 막기 위해 포인터 인자와 메모리 풀링 메커니즘을 오랜 세월 이용했다.
#!syntax cpp
// (1) 컴파일 오류!
// `0`은 리터럴 (prvalue)
int& ref = 0;
int Function() { return 1; }
// (2) 컴파일 오류!
// 참조자가 없는 함수의 반환값도 리터럴 (prvalue)
int& ref = Function();
C++의 좌측값 참조자는 최대한 얕은 복사를 쓰기 쉽게 해줬지만, 임시 객체 생성, 변수의 중복 선언 등 여전히 문제가 많았다. 좌측값 참조자는 이미 정의가 존재하는 객체[12]를 가리키기 위한 것이라 정의가 존재하지 않는 리터럴 따위를 담지 못한다. const&(상수 좌측값 참조자)를 쓰면 가능하지만 내부적으로 임시 객체가 생성되며 const라서 수정할 수도 없는 임시 객체가 무슨 짓을 할지 모른다는 단점이 있었다.#!syntax cpp
// 시스템 자원을 들고있는 구조체. 시스템 자원은 절대로 복사하면 안된다.
struct CThread
{
// (1) 기본 생성자
CThread();
// (2) 복사 생성자
CThread(const CThread& other);
// (3) 소멸자
// 시스템 자원을 파괴한다.
~CThread();
};
임시 객체 생성으로 인해 생기는 가장 큰 문제 중 하나는 시스템 자원의 중복 문제였다. 가령 스레드, 뮤텍스, GDI 객체, 핸들은 시스템에서 생성되고 관리된다. 사용자는 이를 운영체제 호출을 통해 간접적으로 제어할 수 있다. 그런데 생성과 제어는 그렇다 치고 이 자원들이 파괴되는 경우가 있을 것이다. 이때 다른 곳에서 핸들이 파괴된 일을 모르면 잘못된 운영체제 호출이 발생하고, 이 오류는 단순한 런타임 오류와는 궤를 달리할 것이다. 시스템 자원을 변수에 넣지 않고 CThread work_thread{CThread{ th_id, x, y }}; 처럼 시스템 자원 객체를 바로 전달받아도 복사 생성자에서 필연적으로 const CThread& 임시 객체가 생성되어 버린다. 이때 보이지 않는 const CThread& 객체는 work_thread에 시스템 자원을 순순히 넘겨주는 것처럼 보여도, 만약 소멸자에서 시스템 자원을 해제하도록 했다면 work_thread는 생성하자마자 죽은 객체가 된다. 이를 막으려면 두 가지 방법이 있다. 임시 객체인지 표시하는 플래그를 넣던가, 자원을 해제하는 전역 함수를 별도로 만들어야 하는데 최적화와 깔끔함 둘 다 만족시키지 못한다. &&는 &를 하나 더 붙여 컴파일러와 사용자에게 복사, 참조와는 구분하게 하고 중복 자원의 문제도 깔끔하게 해결한다. 객체를 생성하는 방법을 하나 더 제시함으로써 많은 문제가 해결된 것이다.#!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++의 알 수 없는 기전에 좌절할 수 있다.
8.2.1. 함수에 우측값 참조로 전달
|
한편 우측값을 실제로 활용하려면 사실상 함수의 사용이 필수적이다. 왜냐하면 변수에서 쓰이는
&& 참조형은 거진 의미가 없기 때문이다. 그 이유는 &&의 부스러지는 특징 때문이다. &&는 & 참조형처럼 여전히 스스로 존재할 수 없는 존재다. 그런데 &&는 변수의 이름 자체도 아무런 의미가 없다. 이게 무슨 뜻이냐 하면, C++에서 변수의 이름은 좌측값(lvalue)인데 && 참조 변수의 이름을 언급하는 순간 그건 rvalue가 아니게 되는 것이다! 아니면 함수의 도움 없이는 &&는 항상 &로, const&&는 항상 const&로 연역된다. 사용자가 직접 &&를 명시하더라도 무조건 &로 바뀐다 [13].- <C++ 예제 보기>
#!syntax cpp #include <string>; struct Position { float x, y, z; }; Position MakePosition_Copy(const Position& pos) { return Position{ pos }; } Position MakePosition_Move(Position&& pos) { // 매개변수 pos를 그대로 전이 return std::move(pos); // 또는 수동으로 rvalue 변환 return static_cast<Position&&>(pos); // 또는 이동 생성자 사용 return Position{ std::move(pos) }; } struct Squirrel { std::string myName; Position myPosition; }; void SetPosition_Copy(Squirrel& squirrel, const Position& pos) { squirrel.myPosition = pos; } void SetPosition_Move(Squirrel& squirrel, Position&& pos) { squirrel.myPosition = std::move(pos); // 또는 수동으로 rvalue 변환 squirrel.myPosition = static_cast<Position&&>(pos); } void Function3_pos_copy(Squirrel& squirrel, const float& x, const float& y, const float& z); void Function3_pos_move(Squirrel& squirrel, float&& x, float&& y, float&& z);
std::move라는 함수로 간편한 이동 연산을 제공한다. 또는 사용자가 직접 static_cast<T&&>(value)로 수행할 수도 있다.8.3. 포인터 (Pointer)
|
*를 자료형 뒤에 붙이면 어떤 변수를 가리키는 주소임을 나타낼 수 있다. C언어에서의 사용법과 다르지 않지만 참조자와 관련된 규칙이 여럿 추가되었다.
|
*를 &나 && 앞에 붙여야 한다.- <C++ 예제 보기>
#!syntax cpp // std::addressof #include <memory>; int main() { int integer0; const int integer1; // (1) &로 변수 `integer0`의 주소를 얻어온다. int* address_integer0_0 = &integer0; // (2) 표준 라이브러리의 `addressof` 함수로 같은 효과를 얻을 수 있다. int* address_integer0_1 = std::addressof(integer0); // (3) &로 변수 `integer0`의 주소를 얻어온다. const int* address_integer0_0 = &integer1; // (4) 마찬가지. const int* address_integer0_1 = std::addressof(integer1); // (5) 오류! 가변 포인터는 상수 `integer1`를 가리킬 수 없다. int* ptr_to_integer1 = &integer1; // (6) 불변 포인터는 변수 `integer0`를 가리킬 수 있다. const int* ptr_to_const_integer0 = &integer0; // (7) `cptr_to_integer0`는 변수 `integer0`를 가리킬 수 있으며 `*cptr_to_integer0`로 값을 수정할 수도 있다. // 하지만 `cptr_to_integer0` 자체는 불변이다. int* const cptr_to_integer0 = &integer0; }
const int* ptr;은 const int의 포인터이므로, *ptr로 접근한 값은 const라서 수정할 수 없다. 그러나 ptr이라는 변수 자체는 더하고 빼고 등등 임의의 연산도 할 수 있다. int* const ptr;은 ptr 자체는 불변이지만 *ptr로 접근한 값은 그냥 int라서 바꿀 수 있다.가장 많이 볼 사례는 문자열 포인터가 있겠다. 추후 설명하겠지만 함수의 인자로 전달된
const char* string은 C++에서 문자열을 의미한다. 그런데 포인터 변수 자체가 아니라 문자열의 값이 불변이라서 변수 string에 1을 더하고 빼는 등 값을 변경할 수 있다. 표준 라이브러리의 <algorithm> 모듈에서는 이를 이용해 자료구조 뿐만 아니라 문자열에 대해서도 동일한 연산을 지원한다. 반면 char* const& character는 character가 가리키는 char* 값 하나는 *character = 'B'; 처럼 언제든지 값이 바뀔 수 있다. 그러나 character는 불변인 포인터다.한편 상수 포인터, 상수를 가리키는 포인터는 요즘은 고려할 필요가 적다. C++17에서
std::string_view, C++20에서는 std::span의 도움으로 웬만한 포인터 사용을 대체할 수 있기 때문이다 [14]. 이해하기 어렵다면 다른 자료형 앞에 붙어야 의미가 있다는 것을 기억하자.9. 부패
부패 (Decay)지금까지 우리가 함수를 쓰면서 인자가 매개변수로 복사된다는 사실을 봤다. 복사되면서 생기는 여러 문제를 해결하기 위해 한정자와 참조자를 사용할 수 있다고도 배웠다. 지금 처음으로 돌아가 확인해봐야 할 것이 있다. 정말로 인자가 그대로 복사되는 걸까? 답은 아니다. 함수로 들어온 인자는 매개변수에 들어올 때 모든 한정자와 참조자를 상실한다. 이 현상을 부패 (Decay)라고 한다. 부패의 목적은 함수 내부의 값과 외부의 값을 분리하고 의도치 않은 동작을 막는 것이다.
#!syntax cpp
void Function0(int value) {}
void Function1_ptr(int* value) {}
void Function2_const_ptr(const int* value) {}
int main()
{
int integer0;
const int integer1;
volatile int integer2;
int* integer3 = new int{};
int integer4[10]{};
int& integer5 = integer0;
const int& integer6 = integer1;
// (1) 값으로 복사됨.
Function0(integer0);
// (2) `const`가 부패해서 사라짐. 값으로 복사됨.
Function0(integer1);
// (3) `volatile`이 부패해서 사라짐. 값으로 복사됨.
Function0(integer2);
// (4) `&`가 부패해서 사라짐. 값으로 복사됨.
Function0(*integer3);
// (5) `&`가 부패해서 사라짐. 값으로 복사됨.
Function0(*(integer4 + 5));
// (6) `&`가 부패해서 사라짐. 값으로 복사됨.
Function0(integer5);
// (7) `&`가 부패해서 사라짐. 값으로 복사됨.
Function0(integer6);
// (8) `int(&)[10]`이 부패해서 사라짐. 포인터가 값으로 복사됨.
// 다시 말하면 배열의 정보가 사라짐.
Function1_ptr(integer4);
// (9) `int(&)[10]`이 부패해서 사라짐. 포인터가 값으로 복사됨.
// 다시 말하면 배열의 정보가 사라짐.
Function2_const_ptr(integer4);
}
함수의 매개변수가 기본적으로 복사되는 이유는 이 부패 현상 때문에 그런 것이다. 부패가 일어나면 함수는 인자의 자료형을 날리고 포인터를 비롯한 순수한 자료형만 보존한다. 즉 인자가 갖고 있던 const, volatile, &, &&는 무시하고 값으로 전달을 시행한다.#!syntax cpp
void Function(const volatile int value) {}
int main()
{
int integer0;
const int integer1;
volatile int integer2;
int* integer3 = new int{};
int integer4[10]{};
int& integer5 = integer0;
const int& integer6 = integer1;
// (1) `const volatile int`에 `integer0`가 값으로 복사됨.
Function(integer0);
// (2) `const`가 부패해서 사라짐. `const volatile int`에 `integer1`이 값으로 복사됨.
Function(integer1);
// (3) `volatile`이 부패해서 사라짐. `const volatile int`에 `integer2`가 값으로 복사됨.
Function(integer2);
// (4) `&`가 부패해서 사라짐. `const volatile int`에 `integer3`이 값으로 복사됨.
Function(*integer3);
// (5) `&`가 부패해서 사라짐. `const volatile int`에 `integer4`가 값으로 복사됨.
Function(*(integer4 + 5));
// (6) `&`가 부패해서 사라짐. `const volatile int`에 `integer5`가 값으로 복사됨.
Function(integer5);
// (7) `&`가 부패해서 사라짐. `const volatile int`에 `integer6`이 값으로 복사됨.
Function(integer6);
}
함수 매개변수의 const, volatile의 자료형 한정자는 &, &&의 참조자와 함께 보완하지 않으면 함수 안에서는 아무 의미를 갖지 못한다. 함수 매개변수의 자료형 한정자는 단지 코딩에서 실수를 줄이거나 모호함을 줄이기 위해 구태여 붙이는 것이다. 매개변수에 별도의 참조자를 붙이지 않으면 아니면 그럼 복사가 되든 이동이 되든지 간에, 인자가 들어온 순간부터는 인자의 원본이랑은 전혀 연관이 없는 변수가 되면서 함수 안에서 밖으로 영향을 끼치지 못한다.우리가 자료형 한정자와 참조자를 잘 적어야 하는 이유는 부패 과정에서 성능 하락이나 오히려 의도치 않은 동작을 일으키기 때문이다. 라이브러리와 프로그래밍 패러다임 발전과는 별개로 이 문제는 어플리케이션 개발자들도 자주 맞닥뜨릴 문제이기 때문에 조금이나마 알아두는 편이 좋다.
10. 문자열 리터럴 (String Literal)
#!syntax cpp
void Function1(const char* string);
void Function2(auto& string);
template<size_t Length>
void Function3(const char (&string)[Length]);
void Function4(auto string);
int main()
{
const char* str1 = "abcde";
const char* const str2 = "fghij";
constexpr const char* str3 = "klmno";
const char str4[] = "pqrst"; // const char[6]
const char str5[6] = "uvwxy"; // const char[6]
const char(&str6)[2] = "z";
auto str7 = "abcde"; // const char*
const auto str8 = "fghij"; // const char* const
constexpr auto str9 = "klmno"; // const char* const
auto& str10 = "pqrst"; // const (&char)[6]
const auto& str11 = "uvwxy"; // const (&char)[6]
constexpr auto& str12 = "z"; // const (&char)[2]
// (1) 매개변수 `string`은 const char*
// 매개변수 `string`의 값은 nullptr로 끝나는 문자열
Function1("Hello, world! (1)");
// (2) 매개변수 `string`은 const (char&)[18]
// 매개변수 `string`의 값은 크기가 18인 char의 문자열
Function2("Hello, world! (2)");
// (3) 매개변수 `string`은 const (char&)[18]
// 매개변수 `string`의 값은 크기가 18인 char의 문자열
Function3("Hello, world! (3)");
// (4) 매개변수 `string`은 const char*
// "Hello, world! (4)"에서 const(&char)[18]이 부패하고 const char*만 남음.
Function4("Hello, world! (4)"); // string은 const char*이고 null로 끝나는 문자열
}
문자열 리터럴 (String Literal)C++의 문자열 리터럴은
lvalue인 const (&char)[Size]로 표현된다. 컴파일러에 따라 다르지만, 문자열 리터럴은 사용하면 프로그램 내부에 문자열 길이 만큼의 메모리를 할당한다 [15].현재 C++의 문자열 리터럴은 문자열에 대한 주소인
const lvalue인 const char[Size] 혹은 const (&char)[Size], C언어에서 계승된 배열[16]이 부패한 const char*, const&가 const char*를 받는 const char* const&로 문자열을 사용할 수 있다. 참고로 const char[Size]는 문자열 배열 안의 값은 복사가 일어나진 않지만, 문자열 식별자의 포인터는 복사가 된다. 사소한 사항이고, 성능에 영향도 거의 없지만, 어쨌든 &로 받는 걸 추천한다.11. 이름공간 (Namespace)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/이름공간#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/이름공간#|]] 부분을 참고하십시오.12. 클래스 (Class)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/클래스#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/클래스#|]] 부분을 참고하십시오.13. 값 범주 (Value Category)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#값 범주|값 범주]] 부분을 참고하십시오.14. using
#!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=|이전 역사]])}}}}}}}}}}}}}}}}}}}}}template<typename... Ts>using 템플릿-자료형-별칭 = 템플릿-자료형<Ts...>; |
예약어
using을 사용하여 자료형의 별칭(Alias)을 선언할 수 있다. C언어의 typedef 예약어를 대체하는 기능으로써 가독성 상승을 비롯하여 템플릿을 사용할 수 있게 되었다. 별칭의 이름으로 주어지는 식별자는 사용자가 구현한 클래스나 템플릿과 마찬가지로 새로운 자료형으로 분류된다. 즉 이 구문은 변수를 선언하는 것처럼 형식을 선언하는 것이라고 볼 수 있다. 실제로 표준에서도 자료형을 선언한다고 표현한다. 그러나 참조 변수와 같은 맥락으로 이해해야 한다. 기존의 자료형에 다른 이름을 붙이는 것이기 때문에, 기존의 자료형을 사용하더라도 using으로 선언한 자료형과 호환된다. 템플릿도 마찬가지로 void Function(std::vector<int>& vec);이라는 함수가 있다면, 이 함수에는 using IntVector = std::vector<int>;와 그냥 std::vector<int> 모두를 인자로 받을 수 있다.15. 견본 자료형 지시자
견본 자료형 지시자 (Placeholder Type Specifiers)15.1. auto
auto 식별자 = 값;auto 함수-식별자({{{#DodgerBlue,#CornFlowerBlue 'auto'}}} 매개변수...); |
auto는 C++에서 일반 사용자가 가장 유용하게 사용할 수 있는 기능 중에 하나다. C++에서 극히 희귀한 문법적 설탕 요소이며 자료형을 일일이 명시해야하는 문제를 해결하기 위해 도입되었다. 라이브러리를 직접 작성하는 것이 아니라면 클라이언트 단에서는 적극적으로 사용해도 큰 문제는 없다. 사용법은 변수와 함수에서 자료형을 작성할 때 대신 auto를 기입하면 된다. 컴파일러가 해당 변수의 자료형, 함수의 반환형을 추론하여 바이너리에 알아서 반영해준다.주의할 점은 변수와 함수의 선언 시에 곧바로 정의를 해주지 않으면 안된다는 것이다.
auto가 어떤 자료형인지는 컴파일 시점에 결정되는데 auto 자체는 마치 참조자마냥 원래 쓰일 자료형의 별칭이라서 혼자서는 존재할 수 없다. 클래스를 구현할 때도 마찬가지로 auto를 사용한 멤버들은 정의도 동반돼야 한다.한편 앞서 함수에 전달한 인자들이 부패된다는 사실을 살펴봤다.
auto도 같은 규칙을 따른다. auto를 쓰면 &, &&, 배열[N]이 모두 증발한 자료형이 추론된다. 그리고 반환형에 한정자가 붙은 함수를 auto로 값을 받으면 마찬가지로 한정자가 무시된다. 부패를 막는 방법 중 하나는 사용자가 직접 사용자가 직접 &, const&, && 따위의 한정자를 기입하는 것이다[17]. 자세한 내용은 이후에 설명한다.- <C++ 예제 보기>
#!syntax cpp class Counter { public: Counter() noexcept = default; Counter(const size_t& number) noexcept : myNumber(number) {} 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 반환 protected: 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 const auto& cnt3 = counter_v.GetNumber(); // counter_v.myNumber 필드의 불변 참조 변수다. size_t cnt4 = counter_v.GetNumber(); size_t& cnt5 = counter_v.GetNumber(); // cnt5를 통해 counter_v.myNumber 필드를 수정할 수 있다. const size_t& cnt6 = counter_v.GetNumber(); // counter_v.myNumber 필드의 불변 참조 변수다. auto cnt7 = counter_c.GetNumber(); // size_t auto& cnt8 = Counter{ 700 }.GetNumber(); // 형식 한정자 오류! 참조 형식에 rvalue를 전달할 수 없습니다. const auto& cnt8 = counter_c.GetNumber(); // const size_t& 이며 counter_c.myNumber 필드의 참조 변수다. }
*, &, &&, const, volatile를 직접 붙일 수 있다. 그러나 auto를 쓰는 이유가 뭔지 생각해본다면 조금 아쉬운 면이 있다. 자주 사용하는 한정자는 const& 또는 때때로 헷갈림 방지를 위해 *가 있는데 사실 여기서 더 붙일 일은 많이 없을 것이다.15.2. decltype(expression)
using 자료형-별칭 = decltype(표현식); |
decltype(50 + 400U)는 unsigned int를 반환한다. 함수의 매개변수와 반환 자료형에 사용할 수 있다.표현식은 실제로 실행되지는 않으며 컴파일 시간에 반환형만을 평가한다. 예를 들어서
decltype((int*)malloc(50000000))같은 무지막지한 식이라도 얌전히 int*를 반환할 것이다. 그리고 여기서 알 수 있듯이 표현식이 반드시 상수 표현식일 필요는 없다. 성능 문제가 있을까 싶지만 코드만 보고는 반환형이 무엇인지 알 수 없더라도, C++ 타입 시스템은 반드시 컴파일 시점에 모든 자료형을 확정짓는다.이것이 유용한 예로는 정수와 큰 정수의 연산, 실수와 정수 사이의 연산, 메모리 비용이 큰 연산을 하기 전에 자료형을 가져와서 미리 준비를 할때다. 서로 다른 정수 사이에는 더 바이트 수가 큰 정수로 승급하는 규칙이 있고, 실수와 정수 사이에는 실수로 변환되는 규칙이 있다. 비용이 큰 연산에는 직렬화, 문자열 포맷 등이 있다. 직렬화를 하려면 원본 자료형을 더 작은 자료형으로 변환하거나, 일정한 크기의 메모리에 변환된 값들을 쓰는 과정이 필요하다. 이때 실제로 변환 작업을 하기 전에 먼저 필요한 메모리를 할당하는 수가 있다. 표준 라이브러리에서 사용하는 예로는 시간 라이브러리
<chrono>에서 서로 다른 시간 단위를 서로 연산할 때 불필요한 시간 변환 작업을 줄이기 위해서 decltype과 using을 사용하여 반환형을 미리 가져온다.15.3. decltype(auto)
decltype(auto) 변수-식별자=값; |
auto는 조금 부족한 면이 있었다. auto 혼자서는 온전한 자료형을 얻을 수 없기 때문이다. 불필요한 복사가 발생하는 문제도 있다. 해결책으로는 const& 등의 한정자를 붙이는 방법이 있지만 반복적인 작업일 뿐이다. 이에 도입된 decltype(auto)는 값 범주(Value Category)까지 완벽한 자료형을 가져온다. 반환 자료형에 auto 대신에 사용하면 된다. 하지만 매개변수에는 사용할 수 없다.- <C++ 예제 보기>
#!syntax cpp int main() { Counter counter_v{ 300 }; constexpr Counter counter_c{ 500 }; // (1) `cnt1`은 size_t& counter_v.myNumber 필드의 참조 변수임. decltype(auto) cnt1 = counter_v.GetNumber(); // (2) `cnt2`는 const size_t& counter_c.myNumber 필드의 참조 변수임. decltype(auto) cnt2 = counter_c.GetNumber(); // 오류! decltype(auto)는 한정자를 붙일 수 없다. const decltype(auto)& cnt3 = counter_v.GetNumber(); }
decltype(auto)는 그 자체로 완성된 자료형을 가져오므로 형식 한정자를 더 붙일 수 없다. 원한다면 using으로 별칭을 만들고 한정자를 붙여야 한다.15.4. 후속 반환형
auto Indentifier(Parameters...){return 반환 값;}auto Indentifier(Parameters...) -> ReturnType{return 반환 값;}auto Indentifier(Parameters...) -> decltype(...){return 반환 값;}decltype(auto) Indentifier(Parameters...){return 반환 값;} |
함수의 반환 자료형을 직접 기입하는 대신에
auto 혹은 decltype(expr)C++11 예약어를 사용할 수 있다. auto와 decltype(auto)은 [18] 스스로 존재할 수 없는 자료형의 별칭이다. 그래서 auto와 decltype(auto)을 이용할 때는 함수의 구현부가 필요하다.auto를 사용한 경우 함수의 닫는 소괄호 맨 뒤쪽에 ->와 함께 반환 자료형을 적을 수 있다. 자료형의 한정자 때문에 완벽한 자료형을 명시할 필요가 있으나 자료형을 작성하기가 까다로우면 auto Add(auto&& t, auto&& u) -> decltype(t + u) 처럼 작성할 수 있다.#!syntax cpp
// 전역 변수
int i;
// 완벽한 자료형을 받아서 그대로 돌려주는 함수
decltype(auto) Translate(auto& value)
{
return value;
}
// 상동
auto TranslateV2(auto&& value) -> decltype(auto)
{
return value;
}
int main()
{
int j;
int& ref_j = j;
const int k = 1;
// (1) `result_0`는 int&
decltype(auto) result_0 = Translate(i);
// (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);
// (5) `result_4`는 int&&
decltype(auto) result_4 = TranslateV2(10);
// (6) `result_5`는 int&&
decltype(auto) result_5 = TranslateV2(std::move(j));
}
decltype(auto)을 함수의 반환 자료형으로 사용하면 함수는 값 범주(Value Category)를 최대한 준수한다. 반환하는 자료형 원본을 T라고 했을 때 반환하는 값이 이름없는 객체나 리터럴같은 prvalue면 T로 추론된다. 우측 참조자 형변환 따위의 xvalue라면 T&&로 추론된다. 클래스의 데이터 멤버나 함수 외부의 변수 혹은 전역 변수같이 이름이 있는 lvalue라면 T&로 추론된다.16. Ranged For-Loop
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리#algorithm|algorithm]] 부분을 참고하십시오.#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리#ranges|ranges]] 부분을 참고하십시오.#!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&도 사용할 수 있음.
// int&&는 사용할 수 없음.
for (int v : list)
{
// ...
}
상기 코드는 벡터에서 범위 for문을 실행하는 예시를 보여주고 있다.그런데 여기서 알 수 있듯이 범위
for문은 참조자를 사용할 수 있다. 어떻게 이게 가능한걸까?#!syntax cpp
// `r`의 자료형은 R, `r`은 완벽한 전달을 수행함.
// `v`도 완벽한 전달을 수행함.
for (auto&& v : r)
{
// 진행...
}
auto&& range = std::forward<R>(r);
auto begin = range.begin();
auto end = range.end();
for (; begin != end; (void)++begin)
{
auto&& v = *begin;
// 진행...
}
범위 for문은 상기 코드로 변환되어 실행된다. 이때 범위 for문에 전달한 범위 인스턴스의 begin, end 멤버 함수가 쓰인다. 만약 범위 인스턴스가 불변(const)이면 이에 해당하는 begin() const, end() const 멤버 함수가 필요하다.#!syntax cpp
struct SampleRange
{
using value_type = int;
struct Iterator
{
using value_type = SampleRange::value_type;
using iterator_category = input_iterator_tag;
friend bool operator==(const Iterator& lhs, const Iterator& rhs);
value_type operator*();
value_type operator*() const;
};
Iterator begin() /*const*/;
Iterator end() /*const*/;
};
범위 for문은 내부적으로 다른 코드로 치환되는 실행문임을 알아야 한다. 또한 문자 그대로 범위(Range)에 대해서만 실행할 수 있음을 알아야 한다. 여기서 범위란 begin, end 멤버 함수를 가지고 있으며, begin과 end가 반환하는 값이 서로 ==로 비교할 수 있으며, 이때 == 연산은 bool을 반환해야 한다.정확하게는 순회자를 반환하는
begin, end를 갖고 있어야 한다는 말이다. C++11 까지는 알고리즘 모듈 등에서 아무 제약없는 템플릿을 사용했다. 그러나 이는 여러가지 문제를 일으켰다. 알고리즘 함수에 아무거나 집어넣을 수 있어서 실수를 했을 때 나타나는 끔찍한 템플릿 오류 때문에 문제를 해결하기 매우 까다로웠다. 이후 C++ 표준에서 공통적으로 나타나는 범위(Range)라는 명세를 정확하게 제정했고, 이를 C++20에서는 제약조건 std::ranges::range<R>로도 추가했다. 순회자의 종류는 #에서 확인할 수 있다.17. 조건문 초기자 구문
초기자 구문은 실행문의 앞 부분에 별도의 문장을 삽입할 수 있는 기능이다. 해당 실행문이 실행되기 전에 앞서 실행되며, 초기자 구문 안에 선언된 변수나 인스턴스는 실행문에 따라오는 스코프 안에서만 나타난다.17.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문 밖에서는 보이지 않는 지역 변수임.
}
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;
}
}
상기 코드는 if문 초기자의 예시를 보여주고 있다.17.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문 초기자의 예시를 보여주고 있다.17.3. 범위 for문
범위for문 초기자 구문C++20#!syntax cpp
for (init-statement; item-decl : range)
{
// ...
}
C++20에서 도입된 범위 for 초기자 구문은 범위 for 문안에 또다른 문장과 ;를 넣어 작성할 수 있다.기존의
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);
}
}
}
상기 코드는 벡터의 원소를 한줄에 쉼표와 함께 출력하되, 마지막 원소는 쉼표를 붙이지 않고 한줄을 띄운다.18. 상수 표현식 (Constant Expression)
C++11 부터 도입된 상수 표현식, 또는 상수식은 실제 코드가 실행되는 실행 시점(런타임)이 아니라 컴파일 시점(컴파일 타임)으로 코드의 평가를 앞당길 수 있는 획기적인 기능이다. C++의 킬러 요소라고 말할 수 있는 핵심 기능이다.#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/상수 표현식#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/상수 표현식#|]] 부분을 참고하십시오.18.1. static_assert
|
정적인 표명문은
C++11에서 추가된 기능 중 하나로 전달받은 상수 표현식을 평가한 값이 거짓(false)이면 컴파일 오류를 띄우는 기능이다. 인자의 상수 표현식에는 정수를 포함하여 문맥 상 bool을 반환하는 표현식이 올 수 있다. 다시 말하면 bool로 암시적으로 변환할 수 있는 표현식이 올 수 있다[19].기존에 존재하던 자료형 관련 기능의 구멍을 메우는 역할을 한다. 원래는 SFINAE가 줄창 사용되었으나 이 기능으로 일부분 대체할 수 있다.
#!syntax cpp
#include <type_traits>;
#include <string>;
struct Flag
{
constexpr explicit Flag() = default;
constexpr explicit Flag(const bool& v) : value(v) {}
constexpr explicit Flag(bool&& v) : value(v) {}
// 다른 자료형은 못 받도록 생성자 삭제
template<typename T>
explicit Flag(const T&&) = delete;
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(0);
static_assert(1);
static_assert(True);
static_assert(Flag{ true });
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<Flag, char>); // false
}
상기 코드는 정적 표명문의 사용 예시를 보여주고 있다.한편 인자에 들어가는 미평가 문자열(Unevaluated String)들은
C++26부터는 컴파일 시간에만 존재하며, 더 이상 메모리에 존재하지 않는 값이 되었다.18.2. if constexpr
상수 조건 표현식 (Constexpr If)C++17상수 조건 표현식 내지는 상수 조건문은 반드시 컴파일 시간에 조건문을 실행하도록 지시하는 구문이다. 기존 조건문에도 상수 표현식을 쓸 수 있었으나, 그 조건문들 역시 런타임에 평가되며 다른 조건문과 다를 바 없는 분기 코드를 만들었다. 이제 상수 조건문을 쓰면 매크로와 템플릿과 마찬가지로 C++ 코드로만 보이며, 컴파일 시간에 모든 평가를 끝내고 실행 결과만을 최종 코드에 반영할 수 있다.
#!syntax cpp
if constexpr (false)
{
// 컴파일 결과에서 증발함.
}
else
{
// 처음부터 조건문이 없는 것처럼 컴파일함.
}
최종 결과만 반영된다는 말은 조건문으로 걸러진 코드는 아예 삭제된다는 뜻이다. 디버그 모드에서도 마찬가지로. 만약 쓸 수 있다면 사기적인 최적화가 가능하기 때문에 반드시 쓰는 것이 좋다. C++ 코드에서는 if 뒤에 constexpr을 붙이고 상수 표현식을 조건으로 쓰면 된다.#!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]인지 확인하는 함수를 보여주고 있다. 이 함수는 상수 시간에 평가되며 컴파일 시점에 값을 바로 알 수 있다.18.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 });
상기 코드는 클래스의 생성자의 예외 여부를 확인하는 예제다. 여기서 알 수 있는 사실은 자명한 클래스의 생성자는 모두 noexcept(예외를 던지지 않음)라는 것이다. 그런데 마지막 항목 결과가 다른데, 놀라울 것이 없다. `ArtificialPlacement`의 두번째 생성자에 noexcept가 없어서 기본적으로 예외를 던지는 것으로 표시되기 때문이다. 명백하게 아무 문제없는 코드이지만 noexcept를 다는 걸 까먹으면 이런 일이 생기기도 한다.분명 성능과 유지보수에 도움이 되는 기능이기는 하지만
noexcept를 모든 함수에 다는 건 Java의 try-catch 강제나 다름이 없다. 너무 귀찮다면 당장은 달지 말거나 혹은 생성자에만 다는 것도 괜찮다.19. 템플릿 (Template)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿#|]] 부분을 참고하십시오.20. 템플릿 제약조건 (Constraint)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿 제약조건#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿 제약조건#|]] 부분을 참고하십시오.C++에서 템플릿 매개변수에 대하여 조건을 달 수 있는 기능이 생겼다. 이 조건은 컴파일 시점에 평가되며 런타임엔 성능이 영향이 없도록 최적화된다.
20.1. 개념 (Concept)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/템플릿 제약조건#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/템플릿 제약조건#concept|concept]] 부분을 참고하십시오.21. 특성 (Attribute)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/특성#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/특성#|]] 부분을 참고하십시오.22. 람다 표현식 (Lambda Expression)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/람다 표현식#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/람다 표현식#|]] 부분을 참고하십시오.23. 메타 프로그래밍 (Meta Programming)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/메타 프로그래밍#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/메타 프로그래밍#|]] 부분을 참고하십시오.24. 언어 연결성 (Language Linkage)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#언어 연결성|언어 연결성]] 부분을 참고하십시오.25. 코루틴 (Coroutine)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/표준 라이브러리/coroutine#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/표준 라이브러리/coroutine#|]] 부분을 참고하십시오.26. 저장소 지속요건과 객체 수명
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#객체|객체]] 부분을 참고하십시오.저장소 지속요건 (Storage Duration)
C++ 문법에서 가장 마지막으로 살펴볼 내용은 객체의 수명에 대한 내용이다. 객체의 수명은 저장소 지속요건에 의해 결정된다. 값 범주론과 연관이 있다. 저장소 지속요건이란 객체가 어떻게 생성되고(할당) 사라지는지(파괴)를 결정하는 기준이다. 다시 말해 메모리가 얼마나 어떻게 어디에 언제 할당되는지, 언제 해제되는지를 판가름한다. 곧 프로그램의 성능과 효율성에 큰 영향을 준다.
저장소 지속요건은 네 종류가 존재한다. 자동(Automatic), 정적(Static), 동적(Dynamic), 스레드(Thread). 각각에 대한 자세한 설명은 후술한다. 한줄 요약을 하자면 지역 변수, 클래스의 비정적 데이터 멤버 등은 자동, 전역 변수 또는
static이 붙으면 정적, new로 할당하면 동적, thread_local이 붙으면 스레드이다.이 문단은 이론 위주의 내용으로서 C++ 명세 문서에 포함시키는 방안도 있었지만 C++의 주요 키워드에 대한 예제가 필요하기에 이 문서에 작성한다.
#!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=2e7f2895-0e1d-42bb-9599-c474cbb77cab|r20]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/명세?uuid=2e7f2895-0e1d-42bb-9599-c474cbb77cab#s-4|4번 문단]]}}}에서 가져왔습니다. [[https://namu.wiki/history/C++/명세?from=20|이전 역사 보러 가기]]}}}#!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=2e7f2895-0e1d-42bb-9599-c474cbb77cab|r20]]}}} 판{{{#!if paragraph != null
, [[https://namu.wiki/w/C++/명세?uuid=2e7f2895-0e1d-42bb-9599-c474cbb77cab#s-4|4번 문단]]}}} ([[https://namu.wiki/history/C++/명세?from=20|이전 역사]])
{{{#!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=|이전 역사]])}}}}}}}}}}}}}}}}}}}}}26.1. 동적 저장소 지속요건
동적 저장소 지속요건 (Dynamic Storage Duration)동적 저장소 지속요건은 프로그램의 처음 의도한 메모리와는 별도로 사용자가 생성한 객체가 가지는 지속요건이다. 보통 런타임에 동적 할당(Dynamic Allocation)한다고 말한다. 동적 할당한 경우 사용자가 알아서 직접 객체의 수명을 관리해야 한다. 헌데 동적 할당은 프로그램이 처음에 의도한 경로[22]에서 벗어나 프로그램 외부의 영향을 받을 수 있다. 예를 들면 운영체제나 기억장치의 어떤 사정 때문에 할당이 실패하고 오류가 발생할 수 있다. 그래서 사용자 마음대로 무책임하게 생성하는 건 현실적으로는 성능 문제 때문에 못한다.
동적 할당에는
new 연산자를 사용한 생성, malloc, calloc, realloc등의 C언어 방식의 생성등의 방법이 있다.컴파일러의 구현마다 다르지만 보통 동적 할당은 스택대신 힙(Heap) 영역에 이루어진다.
#!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 값을 반환하도록 만들 수도 있다.그럼 동적 할당을 왜 쓰며, 언제 쓸 수 있는지 알아보자.
#!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
#include <iostream>;
int* AllocateMemory()
{
int *ptr = new int(10); // 동적 할당
return ptr;
}
int main()
{
int *ptr = AllocateMemory();
// "The value of ptr is '10'." 출력
std::cout << "The value of ptr is '{}'." << *ptr << std::endl;
delete ptr; // `ptr`이 가리키는 메모리를 해제한다. 이제 `*ptr`은 사용하면 안된다.
}
동적 메모리 할당을 쓰면 이 상황을 타파할 수 있다. 동적 저장소 지속요건을 지닌 객체는 다른 객체와는 상관없이 스스로 수명을 가진다. 곧 객체의 수명이 컴파일러나 스코프 등 환경에 영향을 받지 않는다. 가령 함수 안에서 동적 할당한 객체는 함수의 스코프와는 상관없이 외부에서도 사용할 수 있다. 그리고 delete 연산자를 사용하면 new로 동적 할당한 메모리를 해제할 수 있다.동적 메모리 할당은 다른 메모리 할당 방식보다 주의가 요구된다. 특히 방대한 자료를 다루는 프로그램이라면 메모리를 잘 관리하는 것이 필수적이다. 바로 메모리 누수 때문인데 2025년 지금까지 이 문제로 수많은 게임과 프로그램이 고통받고 있다. 메모리 초과로 버그가 빈발하거나 먹통이 되어 버리는 경우가 잦다. 그래서 사용이 끝난 메모리는 반드시 풀어주어야 한다. 해제 시에 주의해야 할 점이 있는데, 이미 해제한 메모리를 재차 해제하려 하거나(Double-Free), 이미 해제한 메모리에 접근하려 할 경우(Use-After-Free) 치명적인 문제가 발생한다.
연산자 오버로딩을 통해
new, delete의 동작을 덮어씌울 수 있다. 이 경우 원래 new, delete 연산자는 ::operator new(...);, ::operator delete(...);로 사용할 수 있다.26.1.1. 호환성
호환성과 관련하여 언급할 점이 몇가지 있다. C언어에서 사용하는malloc, free, 등으로 C++의 객체를 건드리면 안된다. 왜냐하면 C++의 객체와 C의 메모리 공간은 엄연히 다른 존재이기 때문이다. 일단 C의 메모리 공간은 C++에서도 존재하며 똑같이 포인터로 표현할 수 있다. 하지만 new로 할당한 메모리 공간은 C++의 런타임에서 특수한 용도로 사용하겠다고 지정한 상태를 가진다 [23]. 다시 말해서 new는 바로 해당 메모리 공간에 어떤 객체가 들어있고, 객체의 수명(Lifetime)이 관리되며, 곧 생성자와 소멸자를 호출할거라고 선포한 상태란 것이다.따라서 C++의 객체를 C언어 방식으로 조작하면 생성자, 소멸자가 호출되지 않으므로 정말 특수한 상황이 아니면
malloc, free 등은 쓰지 말아야 한다. 객체 수명 관리가 안되므로 프로그램 동작이 꼬일 위험성이 높다.- <C++ 예제 보기>
#!syntax cpp #include <new>; #include <memory>; #include <span>; struct MyData { void ExecuteData() const {} int a; int b; int c; }; struct MyData_Copy { int a; int b; int c; }; struct Packet { [[nodiscard]] std::span<const char> Serialize() const noexcept { return std::span{ reinterpret_cast<const char*>(myData), mySize }; } void* myData; size_t mySize; }; int main() { MyData data{}; Packet packet1 { .myData = new (std::addressof(data)) MyData{}, .mySize = sizeof(MyData) }; // acq_data1_packet1는 data를 가리키는 포인터이다. MyData* acq_data1_packet1 = std::launder(reinterpret_cast<MyData*>(packet1.myData)); acq_data1_packet1->ExecuteData(); Packet packet2 { .myData = new MyData_Copy{}, // MyData과 똑같은 구성 .mySize = sizeof(MyData_Copy) }; // packet2.myData에는 MyData가 아닌 MyData_Copy가 들어있으므로 std::launder는 정의되지 않은 동작에 돌입한다. // 따라서 acq_data1_packet2의 값은 알 수 없다. MyData* acq_data1_packet2 = std::launder(reinterpret_cast<MyData*>(packet2.myData)); Packet packet3 { .myData = new MyData{}, .mySize = sizeof(MyData) }; delete packet3.myData; // packet3.myData는 해제된 메모리 이므로 std::launder는 정의되지 않은 동작에 돌입한다. // 따라서 acq_data1_packet3의 값은 알 수 없다. MyData* acq_data1_packet3 = std::launder(reinterpret_cast<MyData*>(packet3.myData)); }
std::launderC++17 , std::start_lifetime_asC++23 , std::start_lifetime_as_arrayC++23 라는 메모리 공간을 객체로 취급하는 함수를 제공한다. 여기서 std::launder는 실제로는 메모리 공간에 객체가 이미 있어야 하므로 조금 더 안정적인 reinterpret_cast를 함수의 모양으로 제공하는 꼴이다.마지막으로 백엔드에서 서로 다른 런타임을 사용할 때는 경우 메모리 해제는 할당한 프로그램이 사용하는 런타임 영역 내에서 이루어 져야 한다. GCC가 사용하는 libstdc++, LLVM이 사용하는 libc++, 그리고 MSVC가 사용하는 msvcp는 서로 다른 ABI를 가지고 있으며 따라서 할당 방법도 다르다. libstdc++에서 할당한 포인터를 msvcp가 해제하는 경우 힙 커럽션등의 문제가 생길 수 있다.
26.2. 정적인 저장소 지속요건
정적인 저장소 지속요건 (Static Storage Duration)정적인 저장소 지속요건을 가진 객체는 프로그램에서 객체의 특성이 영원히 변하지 않는다. Static은 직역하면 정적이라는 뜻이다. 정적이라는 단어는 Constant(상수), Immutable(불변)과는 다른 또다른 고정된 상태를 의미한다. 상수는 C++ 상수 표현식의 상수, 불변은 값이 변하지 않는
const를 말한다. Static은 객체의 특성이 변하지 않음을 말한다. 여기서 객체의 특성이란 객체 명세에 의하여 한줄로 정해진다:어떤 고정된 주소와 고정된 크기의 완성된 자료형을 가지고 있는 데이터 필드
객체의 자료형과 메모리 크기는 영원히 변하지 않는다. ➡️ 사용자가 결정해야 한다.
메모리 정렬은 영원히 변하지 않는다. ➡️ 사용자가 결정할 수 있지만 기본적으로 컴파일러가 결정한다.
객체가 위치한 메모리 주소가 영원히 변하지 않는다. ➡️ 객체의 주소는 컴파일러가 결정한다.
객체의 수명은 무한하다. ➡️ 객체의 메모리를 해제할 수 없다.
객체는 언제나 존재한다. ➡️ 프로그램이 시작될 때 할당되고, 프로그램이 끝날 때 해제된다.
메모리 정렬은 영원히 변하지 않는다. ➡️ 사용자가 결정할 수 있지만 기본적으로 컴파일러가 결정한다.
객체가 위치한 메모리 주소가 영원히 변하지 않는다. ➡️ 객체의 주소는 컴파일러가 결정한다.
객체의 수명은 무한하다. ➡️ 객체의 메모리를 해제할 수 없다.
객체는 언제나 존재한다. ➡️ 프로그램이 시작될 때 할당되고, 프로그램이 끝날 때 해제된다.
참고로 정적인 객체의 값은 변할 수 있는데, 왜냐하면 객체는 값을 가질 수 있다는 것이지, 값이 변하든 말든 상관이 없다. 값의 변화는
const 인가 아니냐로 정해진다. 정적인 객체는 프로그램이 시작할 때 기본값으로 초기화된다, 이론적으로는. [26]#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/명세#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/명세#언어 연결성|언어 연결성]] 부분을 참고하십시오.정적인 저장소 지속요건은 까다로운 점이 꽤나 많다. 여기서는 문법과 활용 사례만 살펴보겠다.
26.2.1. 전역 변수
전역 변수는 어떤 이름공간에도 속하지 않은 객체이다. 보통main 함수와 같이 쓰인다. 이미 많이 썼으니 익숙할 것이다.26.2.2. static 변수
static 자료형{{{#FFA3D2,#Violet ' 식별자'}}};static inline constexpr 자료형{{{#FFA3D2,#Violet ' 식별자'}}} = 초기값;C++11static inline 자료형{{{#FFA3D2,#Violet ' 변수-식별자'}}} = 초기값;C++17static inline constinit 자료형{{{#FFA3D2,#Violet ' 식별자'}}} = 초기값;C++20 |
변수의 자료형 앞에
static 지시자를 덧붙일 수 있다. static 변수는 값을 할당해주거나 기본값을 가질 수 있는 자료형이어야 한다.같은 이름을 가진
static 변수는 모두 같은 객체로 취급된다. 만약 자료형이 불일치하는데 같은 이름이 등장하면 컴파일 오류가 발생한다. 만약 여러 헤더에서 공통적으로 이용되는 어떤 static 변수가 있다고 해보자. 이 변수를 선언하는 공통 헤더를 만들어 쓸 수도 있다. 혹은 각 헤더에 선언부만 넣어놓을 수도 있다. 이러면 변수 하나를 이용하기 위해 무거운 헤더를 불러오지 않아도 된다. 헤더 삽입을 줄여 컴파일 시간 감소에 도움이 된다.- <C++ 예제 보기>
Header1.hpp#!syntax cpp static int my_global;
Header2.hpp#!syntax cpp static int my_global;
Header3.hpp#!syntax cpp static int my_global;
Main.cpp#!syntax cpp #include "Header1.hpp" #include "Header2.hpp" #include "Header3.hpp" // 문제없이 실행됨.
constexpr과 조합하면 프로그램 안에서 자주 쓰이는 값들을 모조리 미리 계산해놓을 수도 있다.static inline constexpr을 다 기입할 필요는 없고,static constexpr만 써도 된다.C++17부터는inline지시자를 붙이면 정적 변수를 선언한 즉시 값을 정의할 수 있다.C++20부터는constinit과 조합해서 유용하게 쓸 수 있다.constinit은 상수 시간 초기화만 수행하고 불변(const)이 아니다. 덕분에 활용도가 높아지고 성능에도 도움이 된다.static inline은 값 초기화를 바로 하는 건 좋으나, 런타임에 객체를 생성하고, 생성자에서 예외가 뜰 수가 있으며, 만약 외부 데이터나 DLL의 함수를 끌어다 쓸 때는 쓸 수가 없었다.constinit은 런타임에 객체 생성이 영향을 끼치지 않게 하면서 앞선 두 문제를 해결한다. 컴파일 시간에 객체를 생성하고, 아예 잘못된 값이 들어오면 컴파일이 실패하도록 작성할 수 있다.
#!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 변수가 클래스의 인스턴스이고, 초기값을 할당하지 않았다면 그 변수는 클래스의 기본 생성자를 호출한다 [27]. 때문에 기본 생성자가 없으면 컴파일 오류가 발생하니 주의해야 한다.#!syntax cpp
static inline constinit int* memory = nullptr;
int main()
{
memory = new int;
*memory = 100;
delete memory;
}
정적 포인터가 가리키는 메모리는 여전히 할당 및 해제가 가능한데, 포인터 변수 자체는 프로그램의 시작과 끝을 함께 하는 것이 맞다. 하지만 포인터가 정적인 상태인 것이지, 포인터가 가리키는 대상은 정적이든 아니든 상관이 없다.26.2.3. static 함수
[[특성]] static 반환-자료형{{{#FD8C6E ' 함수-식별자'}}}(매개변수-자료형1 매개변수1, 매개변수-자료형2 매개변수2, ...); |
함수의 반환 자료형 앞에
static 지시자를 덧붙여 해당 함수가 정적인 내부 연결을 가진 함수임을 나타낸다.정적 함수는 전방위 선언만 할 수 없고
inline처럼 구현도 해줘야 한다. 프로그램 내내 일정한 메모리 주소에 위치하며, 현재 해석 단위(헤더 또는 모듈)에서는 이름이 같은 함수이면 무조건 동일한 함수를 의미한다.- <C++ 예제 보기>
Header1.hpp#!syntax cpp static int GlobalFunction();
Header2.hpp#!syntax cpp static int GlobalFunction();
Header3.hpp#!syntax cpp static int GlobalFunction();
Main.cpp#!syntax cpp #include "Header1.hpp" #include "Header2.hpp" #include "Header3.hpp" static int GlobalFunction() { return 10; } // 문제없이 실행됨.
#!syntax cpp
static int GlobalFunction()
{
return 10;
}
Header1.hpp#!syntax cpp
static int GlobalFunction()
{
return 20;
}
Header2.hpp#!syntax cpp
static int GlobalFunction()
{
return 30;
}
Header3.hpp#!syntax cpp
#include "Header1.hpp"
#include "Header2.hpp"
#include "Header3.hpp"
int main()
{
// "GlobalFunction: 10" 출력
std::println("GlobalFunction: {}", GlobalFunction());
}
Main.cpp제일 먼저 삽입한 헤더의 함수 인스턴스가 우선한다. 정적 객체는 한번 생성되면 객체의 특성이 변하지 않는다. 함수는 특성상 사용자가 바꿀 수 있는 어떤 값이 없다. 때문에
static 함수는 반드시 정의를 같이 해야 한다. 그런데 사실은 함수의 "값"이 바뀔 수 있는 시나리오가 있는데 그건 바로 동일한 이름의 함수를 다시 정의하는 것이다. 이 경우 static 함수는 새로운 함수를 덮어씌우거나, 새롭게 함수 인스턴스를 생성하지도 않으며 나중에 정의된 함수 인스턴스를 전부 무시한다! 이 예제에서는 Header2.hpp와 Header3.hpp의 함수들이 무시되었다.C언어에서는 이름공간의 부재 때문에 어쩔 수 없이 쓰였으나 문제가 많은 기능이다. C++에서는 반드시 이름공간과 같이 쓰고, 혹은 상수 표현식 함수로 만들도록 하자.
26.2.3.1. 주요 용도
#!syntax cpp
#include <string_view>;
// 구현 내용이 없으면 링크 오류가 발생한다.
static char GetFirstCharacter(std::string_view str);
// 헤더를 여러번 삽입하더라도 GetFirstCharacter는 언제나 동일한 함수임이 보장된다.
// 내부 연결 객체는 현재 이름공간에 유일한 존재로 남으므로 식별자의 중복 선언 문제가 발생하지 않는다.
static char GetFirstCharacter(std::string_view str)
{
return str.front();
}
원래 C언어의 소스 구조에서는 여러 곳에서 중복 삽입될 수 있는 헤더는 중복된 객체 링킹 문제 때문에 오직 전방위 선언만을 사용해야했다. 이를 해결하기 위해 static 또는 extern을 소스 파일이 아니라 헤더 파일에서 함수와 변수를 정의하기 위해 사용해왔다. 덕분에 여러 곳에서 헤더를 삽입해도 링크 오류없이 사용할 수 있다. C++에선 이름공간과 병행하면서도 쓰인다.헌데 C++20에서 소개된 모듈에선
static 함수는 모듈 밖으로 내보낼 수 없다! 모듈 밖으로 객체를 내보내는 export 구문은 외부 연결로 만드는 데, 내부 연결인 객체는 허용하지 않는다. static 함수는 내부 연결이라서 링크 오류가 발생한다.26.2.4. static 지역 변수
#!syntax cpp
void Function()
{
static int StaticInteger1;
static inline int StaticInterger2 = 10;
static constexpr int Constant = 20;
static constinit int Inplace = 30;
}
// 컴파일 시점에 모든 static 변수 생성 및 초기화
int main()
{
Function();
}
26.2.5. 정적 멤버 함수
#!syntax cpp
struct Math
{
static constexpr double Lengthdir_x(double len, double degree);
static constexpr double Lengthdir_y(double len, double degree);
};
C++/문법/클래스 문서 참조.26.2.6. 정적 데이터 멤버
#!syntax cpp
struct Math
{
static constexpr double float = 3.14159237f;
};
메타 데이터, C++/문법/클래스 문서 참조.26.2.7. extern
- <C++ 예제 보기>
#!syntax cpp extern class GameObject;
정적인 외부 연결 (
external Linkage)static과 반대 기능이 아님을 알아두는 것이 좋다. extern 변수, extern 함수도 한번 정의되면 객체의 특성이 바뀌지 않는 정적인 객체이며, 단지 그 정의를 외부에서 가져올 수 있다는 뜻이다. 정적인 객체는 한번 할당되면 다시는 할당되지 않으므로, 외부에서 구현을 가져오면서 동작을 유지하려는 목적이다. 만약 정적이지 않다면 외부에서 구현한 코드가 바꿔치기 당할 위험성이 생긴다. 예를 들면 DLL로 가져온 외부 객체(변수, 함수)를 사용했을 때, 해당 객체를 이전에 같은 이름으로 불러왔더라도, 다음 번에 같은 동작을 할지 확실하지 않을 것이다.그래서인지 C++17에서
static 대신에 내부 연결만 적용할 수 있는 이름없는 이름공간이 도입되었는데, extern 대신에 외부 연결만 적용하는 기능은 없다.==== extern 변수 =====
여러가지 기능을 가진 예약어이지만 첫번째 기능은 객체 선언의 자료형 앞에
extern지시자를 붙여 정적인 외부 연결을 가진 객체임을 나타내는 기능이다#!syntax cpp
extern int playersNumber;
extern const int maxPlayersNumber;
헤더 파일#!syntax cpp
extern int playersNumber = 0;
extern const int maxPlayersNumber = 4;
소스 파일- <C++ 예제 보기>
Header.hpp#!syntax cpp extern int my_global;
Header.cpp#!syntax cpp #include "Header.hpp" int my_global = 4;
Main.cpp#!syntax cpp #include "Header.hpp" int main() { // "my_global: 4" 출력 std::println("my_global: {}", my_global); }
26.2.8. extern 함수
이 기능은 DLL(동적 라이브러리 연결) 기능으로 프로그램 외부에서 실행 시간(런타임)에 함수 따위를 불러올 때 사용한다.두번째 기능은 템플릿 실체화(Template Instantiation) 기능이다. 템플릿을 명시한 자료형 소스에서만 컴파일하도록 지시한다. 원래 템플릿은 모든 자료형 후보에 대해 컴파일을 시도하기에 컴파일 시간이 기하급수적으로 늘어날 수 있다. 이때 원하는 특수화 후보를 명시하면 중복된 컴파일 시도를 줄일 수 있다.
세번째로 C언어와 C++ 사이의 언어를 전환하는 기능도 있다.
extern "C", extern "C++" [28]과 같이 사용한다. 기본적으로 C++의 모든 객체에는 extern "C++"이 적용된다. 모든 이름공간, 클래스와 변수 앞에 보이지 않는 extern "C++"이 붙어있다고 생각하면 된다. 만약 extern "C"를 사용하면 함수 오버로딩 금지 등 C언어의 규칙을 따로 적용할 수 있다 [29]. 이 기능을 가장 많이 쓰는 사례는 ABI가 호환되는 라이브러리를 만들 때이다. 예를 들어 함수 void* myFunction(void* ptr, const void* cptr, int x)의 경우, 내부적으로 MSVC는 ?myFunction@@YAPEAXPEAXPEBXH@Z, Clang/GCC는 _Z10myFunctionPvPKvi 처럼 심볼이 변환되는데 이를 이름 변형(Name Mangling)이라고 한다. 여기서 C 방식을 쓰겠다는 extern "C" 선언을 통해 심볼 이름에 myFunction을 그대로 사용함으로써 dlsym()이나 다른 프로그래밍 언어에서 C++로 작성된 코드를 불러와 사용하는 것이 가능해진다.26.2.9. 정적인 객체의 주소
#!syntax cpp
import <cstdint>;
import <memory>;
import <string_view>;
struct MyClass
{
static inline constexpr std::string_view Name = "MyClass";
static std::string_view NotCtName1;
static inline std::string_view NotCtName2 = "MyClass";
constinit static inline std::string_view NotCtName3 = "MyClass";
};
int main()
{
MyClass instance1, instance2;
// 정적 데이터는 constexpr가 아니라도 컴파일 시간에 주소를 비교할 수 있다.
static_assert(std::addressof(instance1.NotCtName1) == std::addressof(instance2.NotCtName1));
static_assert(std::addressof(instance1.NotCtName2) == std::addressof(instance2.NotCtName2));
static_assert(std::addressof(instance1.NotCtName3) == std::addressof(instance2.NotCtName3));
static_assert(sizeof(MyClass) == 1);
}
정적 객체는 프로그램에서 주소가 불변하므로, 주소를 상수 시간에 비교할 수 있다.26.2.10. 익명 이름공간 (Unnamed Namespace)
#!if (문단 == null) == (앵커 == null)
를#!if 문단 != null & 앵커 == null
의 [[C++/문법/이름공간#s-|]]번 문단을#!if 문단 == null & 앵커 != null
의 [[C++/문법/이름공간#익명 이름공간|익명 이름공간]] 부분을 참고하십시오.26.3. 자동 저장소 지속요건
자동 저장소 지속요건 (Automatic Storage Duration)자동 저장소 지속요건이란 컴파일러가 처음부터 끝까지 객체의 메모리 관리를 책임진다는 뜻이다.
일반적으로 어떤 범위(스코프, Scope) 안에 선언한 필드, 객체는 자동 저장소 지속요건을 가진다. 예를 들어서 함수 안에 선언한 변수와 상수, 매개변수(Parameter), 클래스의 비정적 데이터 멤버가 있다.
자동 할당된 객체가 존재하는 지속시간(Duration)은 해당 블록이 끝나는 시점(범위, Scope)까지다. 가령 함수라면 중괄호 블록의 마지막 줄까지 실행된 이후, 함수의 호출 스택에서 빠져나가는 순간, 그 중괄호 블록에서 자동 할당된 객체들은 모두 해제된다. 그래서 함수 안에 변수를 마구잡이로 선언하더라도 함수가 실행될 때만 메모리를 차지한다.
#!syntax cpp
// (1) `paramater`는 매개변수이자 지역 변수로써 자동 저장소 지속요건을 가짐.
void Function(int paramater);
// (2) `paramater`는 매개변수이자 지역 변수로써 자동 저장소 지속요건을 가짐.
template<typename T> void Function(T parameter);
int main()
{
// (3) `local_variable`은 지역 변수로써 자동 저장소 지속요건을 가짐.
float local_variable;
// (4) `immutable`은 지역 변수(상수)로써 자동 저장소 지속요건을 가짐.
const double immutable;
// (5) `constant`는 지역 변수(컴파일 상수)로써 자동 저장소 지속요건을 가짐.
constexpr int constant;
// (6) `ghost`는 지역 변수(컴파일 상수)로써 자동 저장소 지속요건을 가짐.
/// 그런데 `inline constexpr` 상수는 최적화에 의해 통째로 치환될 수 있음. 마치 매크로 처럼.
inline constexpr bool ghost;
}
전역 범위가 아닌 중괄호 블록안에 선언한 변수 또는 함수의 매개변수는 자동으로 메모리가 할당되고, 자동으로 수명이 관리된다. 즉 어떤 함수 안에 존재하는 변수는 자동으로 메모리 할당과 해제를 해준다는 뜻이다. 가령 지금까지 살펴본 예제에서 main 함수 안에서 정의한 변수들도 자동으로 수명이 지속된다. 컴파일러의 구현마다 다르지만 보통 자동 할당은 운영체제의 스택 메모리에 저장된다.- <C++ 예제 보기>
#!syntax cpp #include <memory>; #include <iostream>; int Testment() { int y = 10; return y; } int main() { int x = Testment(); // (1) 10을 출력 std::cout << x << '\n'; // (2) `x`의 주소를 출력 std::cout << std::addressof(x) << '\n'; return 0; }
int y = 10;"과 "int x = Testment();"가 정수를 메모리에 할당하는 구문이다.먼저
y에 10을 생성하면, Testment 함수 안에서 정수[30] 크기 만큼의 메모리를 할당한다. 이후 y를 반환하면, Testment 안에 있던 y의 메모리는 바로 회수된다. 다음으로 x는 Testment 함수의 반환 값을 받아서 생성된다. x 역시 main이라는 함수 안에 있으므로 main 함수가 끝나는 순간 회수된다. 그런데 main 함수가 끝나는 경우는 프로그램의 종료를 의미하므로 프로그램이 사용했던 모든 메모리가 회수되는 것이다.#!syntax cpp
struct TestClass
{
// (1) `paramater`는 매개변수이자 지역 변수로써 자동 저장소 지속요건을 가짐.
void Method(int parameter);
// (2) `paramater`는 비정적 데이터 멤버이자 하위 객체(Subobject)로써 자동 저장소 지속요건을 가짐.
int dataMember;
// (3) `metaMember`는 정적 데이터 멤버라서 정적 저장소 지속요건을 가짐.
static int metaMember;
};
int main()
{
// (4) `instance1`의 `dataMember`는 자동 저장소 지속요건을 가짐.
TestClass instance1;
// (5) `instance2`의 `dataMember`는 정적 저장소 지속요건을 가짐.
static TestClass instance2;
// (6) `instance_ptr` 자체는 포인터이자 지역 변수로써 자동 저장소 지속요건을 가짐.
// 그런데 `*instance_ptr`로 접근할 수 있는, 메모리 주소 `instance_ptr`에 생성된 `TestClass`의 인스턴스는 동적 저장소 지속요건을 가짐.
// 따라서 `*instance_ptr`의 `dataMember`는 동적 저장소 지속요건을 가짐.
// 하지만 `*instance_ptr`의 `metaMember`는 정적 저장소 지속요건을 가짐.
TestClass* instance_ptr = new TestClass;
}
한편 클래스에는 특별한 규칙이 있다. 클래스의 데이터 멤버는 클래스의 인스턴스의 저장소 지속요건을 따라간다. 다시 말하면 클래스의 데이터는 클래스와 함께 생성되고 해제된다는 당연한 규칙이다.예제에서 정적 저장소 지속요건을 가진
`metaMember`는 동적 할당을 수행해도 여전히 정적 지속요건이 그대로이다. 왜냐하면 `metaMember`의 수명은 클래스의 인스턴스에 귀속되지 않고 클래스 자체에 귀속되기 때문이다. C++에는 메타 클래스[31]가 없으므로 클래스 자체는 프로그램이 시작할 때 정의되고, 프로그램이 끝날 때 사라진다. 정적 데이터 멤버도 마찬가지로 프로그램의 시작과 끝을 함께 한다.참고로
std::array<std::array<T, Length>> 혹은 T array[10][20] 같은 다차원 배열은 C++17 이후에서만 스스로 해제가 가능하므로 std::destroy(T*) 따위로 직접 해제 해줘야 메모리 누수가 생기지 않는다. 배열 문서를 보면 알겠지만 C언어의 정적 다차원 배열은 다루기 매우 까다롭다. 그러므로 std::vector<std::vector<T>> 등의 동적 배열이 더 편할 수 있다.26.4. 스레드 저장소 지속요건
스레드 저장소 지속요건 (Thread Storage Duration)thread_local을 자료형 앞에 붙여 변수 선언에 사용할 수 있다. 전역 변수 혹은 static 이나 extern 지역 변수에서만 사용할 수 있다. 프로그램에서 생성한 모든 스레드에 해당 변수의 메모리가 할당된다. 이렇게 만든 데이터는 각각의 스레드에서 따로 관리된다.- <C++ 예제 보기>
#!syntax cpp #include <vector>; #include <thread>; #include <chrono>; #include <print>; // 전역 범위에 선언되어 있지만, 실제로는 스레드 단위 지역 변수다. thread_local size_t threadID; thread_local size_t threadCount = 0; void Watcher(size_t id) { // 보이지 않는 threadID, threadCount 지역 변수가 선언되어 있다. threadID = id; using namespace std::chrono_literals; while (true) { if (::rand() % 10 == 0) { std::cout << "스레드 ID " << threadID << "에서 " << ++threadCount << "번째 보고" << std::endl; } std::this_thread::sleep_for(1s); } } int main() { std::vector<std::jthread> myThreads{}; myThreads.reserve(4); for (size_t i = 0; i < 4; ++i) { myThreads.emplace_back(Watcher, i); } while (true) { std::this_thread::yield(); } }
`threadCount`는 전역 변수처럼 보이지만, 실제로는 스레드 마다 따로 선언된 지역 변수다.27. RAII
자원 획득이 곧 초기화다 (Resource Acquisition Is Initialization)메모리 관리 기법 중 하나인 RAII에 대하여 알아보자. RAII는 자원[메모리]의 획득[할당]은 초기화[선언]라는 뜻이다. C++의 메모리 관리 분야에서의 주요 방법론 중 하나이다. RAII의 요점은 생성자에서 할당하고, 소멸자에서 해제하는 역할을 쥐어주는 것이다.
우리가 지금까지 C/C++을 쓰면서 변수의 할당과 해제에 대하여 깊게 생각해본 적이 있는가? 어셈블리어, 포트란이나 코볼 따위의 상대적 저급 언어보다 C언어에서 가장 발전되었다고 말할 수 있는 부분이 메모리 관리다 [35]. 메모리의 추상화 기능에 의하여 그냥 자료형과 식별자만 넣으면 컴파일러와 운영체제가 알아서 해준다. 그러나 동적 할당한 가진 메모리는 여전히 수동으로 관리해야 하며 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`가 동적 할당한 포인터를 가지면 어떻게 될까? 이를 해제해주지 않으면 메모리 누수가 발생한다.27.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 해제의 예시를 보여주고 있다.대부분의 경우 1번 방식으로 충분하다. 그런데 여기서 몇가지 위험 상황을 생각해볼 수 있다. 첫번째 사례는 NULL인 포인터를 한번 더 삭제하려고 하는 경우이다. 다행히도
delete 연산자는 NULL 포인터에 대해서는 아무 동작도 하지 않으므로 아무 문제가 없다. 두번째 사례는 소멸자 이후에 포인터가 외부에서 또 참조되는 경우다. 삭제한 변수를 참조하는 사태가 일어나면 포인터가 NULL이 아니면 어떤 미지의 정보가 읽힐 수 있다. [36] 그러므로 삭제한 자원은 바로 NULL로 만드는 게 좋다. C++ 벡터 문서에서 관련 예제를 소개하고 있다.허나 이조차도 다른 장소에서 자원이 삭제됐다는 사실을 알 수가 없다는 문제가 있다. 이에 대해서는 스마트 포인터를 참조하라.
27.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;
}
상기 코드는 팩토리 함수에서 객체를 생성하는 예시를 보여주고 있다. 예외가 발생하더라도 동적 할당한 메모리는 자동으로 해제된다.28. 둘러보기
||<:><-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] 주의해야할 점은 MSVC를 제외한 gcc와 clang에서는 module에 대한 기능을 아직 제대로 지원하지 않은 experimental한 기능이다. 사용에 중의 바란다.[2] bool은 'Boolean'의 줄임말이다.[호출] 연산자[4] 이를 인라이닝(Inlining)이라고 한다[헤더] [6] 예를 들어 extern 함수 또는 모듈에서 export된 함수[7] MSVC에서만[8] 이를 객체(Object)가 아니다라고 한다.[9] 참고로 플레이어 개체 변수를 참조자 없이 함수에 전달하면 플레이어 개체가 복사되므로 사용할 수 없다[10] 예를 들어 if (handle == NULL) 같은 경우[11] 이는 C++도 해당된다[12] 변수, 함수, 구조체 등[13] 이러면 메모리 주소를 얻을 수 있고 변수의 값을 바꿀 수는 있겠지만 성능 상의 이득은 사라진다.[14] 그러나 C++14 이하의 버전을 사용해야만 하는데 문자열 처리를 구현해야 하면 공부해서 나쁠 건 없다.[15] 최적화를 위해 프로그램의 문자열 리터럴을 한데 모은 문자열 풀(Pool)을 구성하고 가져다 쓰기도 한다[16] C언어 까지는
char[Size] 또는 여기서 연역된 char*였으나, 이는 실제로 수정할 수 없는 리터럴에 대한 오해를 사게 만들었다.[17] 또 하나의 방법은 템플릿에 대한 이해가 필요하다[18] 실제로는 템플릿이기 때문에[19] explicit 생성자 등의 이유로 직접 변환을 해야한다면 그리 해줘야 한다[20] 관련 내용은 튜플 참조[21] 혹은 숫자로 표현되는 원시 자료형[22] 결정적(Deterministic)이라고 한다[23] 런타임 오버헤드 때문에 상대적으로 느리기도 하지만[24] 런타임이다[25] 자료형 매개변수와 값 매개변수 둘 다[26] 이것 때문에 생기는 문제가 조금 골치아프다. 자세한 것은 후술[27] 기본 생성자는 default가 아니여도 된다[28] extern "C" { ...; }와 처럼 스코프를 지정할 수 있다[29] 연산자 오버로딩은 가능하다[30] 보통은 4바이트[31] 파이썬 같은 언어에서 볼 수 있는 클래스를 만드는 클래스[메모리] [할당] [선언] [35] 정확하게 말하자면 자동 저장소 지속요건 덕분으로, 메모리 관리에 대하여 씨름할 필요가 매우 줄었다[36] delete의 사례는 버퍼 오버플로우는 아니지만, 비슷한 하트블리드 참조