#21. [모던 C++] 특성(attribute)(C++11, C++14, C++17, C++20)
- (C++11~) 특성(attirbute)이 추가되어 컴파일러에게 부가 정보를 전달하는 방식을 표준화 했습니다.
- (C++14~) [[deprecated]]가 추가되어 소멸 예정인 것을 컴파일 경고로 알려줍니다.
- (C++17~) [[fallthrough]]가 추가되어
switch()
에서 의도적으로break
를 생략하여 다음case
로 제어를 이동시킬때 발생하는 컴파일 경고를 차단할 수 있습니다.- (C++17~) [[nodiscard]]가 추가되어 리턴값이나 타입을 무시하면 컴파일 경고로 알려줍니다.
- (C++17~) [[maybe_unused]]가 추가되어 사용되지 않은 개체의 컴파일 경고를 차단할 수 있습니다.
- (C++17~) 특성 네임스페이스가 추가되어
[[msvc::noinline]]
와 같이 사용할 수 있습니다.- (C++20~) [[nodiscard]]의 생성자 지원, [[nodiscard(“이유”)]]가 추가되었습니다.
- (C++20~) [[likely]], [[unlikely]]가 추가되어 컴파일러에게 최적화 힌트를 줄 수 있습니다.
- (C++20~) [[no_unique_address]]가 추가되어 아무 멤버 변수가 없는 개체의 크기를 최적화합니다.
개요
컴파일러에게 최적화 힌트나 경고 처리 힌트등 문법적으로 전달하기 어려운 정보를 컴파일러에 전달할때 기존에는 컴파일러 마다 자체적인 방식을 사용했습니다.(__attribute__()
, __declspec()
등)
C++11 부터는 특성(attirbute)이 추가되어 부가 정보 전달을 표준화 했습니다.
[[
특성명]]
와 같이 [[]]
사이에 사용할 특성을 기재하면 됩니다.
표준 특성
C++버전에 따라 다음의 표준 특성이 제공됩니다.
항목 | 내용 |
---|---|
[[noreturn]] (C++11~) | 함수가 항상 예외를 throw하거나 프로그램을 종료합니다. |
[[carries_dependency]] (C++11~) | (작성중) |
[[optimize_for_synchronized]] (C++11~) | (작성중) |
[[deprecated]], [[deprecated(“이유”)]] (C++14~) | 소멸 예정인 것을 컴파일 경고로 알려줍니다. |
[[fallthrough]] (C++17~) | case A: break; case B: case C: break; 와 같이 case B: 가 의도적으로 case C: 를 실행함을 알립니다. |
[[nodiscard]] (C++17~) [[nodiscard(“이유”)]] (C++20~) |
개체나 리턴값이 무시되면 안됩니다.(에러 코드 리턴하는 함수에 사용하면 좋습니다.) |
[[maybe_unused]] (C++17~) | 사용되지 않은 개체의 컴파일 경고를 막습니다. |
[[likely]], [[unlikely]] (C++20~) | if() 나 switch() 에서 자주 사용하는 코드 조각을 알려주어 최적화 힌트를 제공합니다. |
[[no_unique_address]] (C++20~) | 아무 멤버 변수가 없는 개체의 크기를 최적화합니다. |
assume("표현식") (C++23~) |
특정 상황에 표현식이 true 가 되도록 가정합니다. 컴파일러는 이 가정을 신뢰하고 이에 따른 최적화를 합니다.(가정이 거짓일때 동작은 정의되지 않았습니다.) |
[[noreturn]]
컴파일러에게 리턴하지 않는다는 최적화 힌트를 줍니다.
“리턴하지 않는다”가 좀 오해가 있는 표현인데요,
void f() {}
는 void
를 리턴하므로, 리턴하는 함수입니다.
리턴하지 않는 함수는 무작정 예외를 발생시키거나 abort() 하여 프로그램을 종료하는 함수를 말합니다.
1
2
3
4
5
6
7
// 함수가 항상 예외를 throw하거나 종료합니다.
// 컴파일러는 호출후 제어 연결을 할 필요가 없으므로, 이에 따른 최적화가 가능합니다.
[[noreturn]] void f_11() {throw "error";}
// 실제로는 리턴하므로 컴파일러는 경고를 발생합니다.
[[noreturn]] int g_11() {return 0;} // (X) 컴파일 경고. function declared 'noreturn' has a 'return' statement
[[noreturn]] void g_11() {} // (X) 컴파일 경고. 'noreturn' function does return
[[carries_dependency]]
(작성중)
[[optimize_for_synchronized]]
(작성중)
(C++14~) [[deprecated]], [[deprecated(“이유”)]]
소멸 예정인 것을 컴파일 경고로 알려줍니다. deprecated("이유")
를 이용하여 컴파일 경고시 소멸 이유를 표시할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace [[deprecated]] MyLib {} // 네임스페이스
class [[deprecated]] MyClass { // 클래스, 구조체, 공용체
[[deprecated]] int m_Val; // 멤버 변수
[[deprecated]] void f() {} // 멤버 함수
};
template<typename T>
class [[deprecated]] A {}; // 템플릿
using MyType [[deprecated]] = int; // using
[[deprecated]] typedef int YourType; // typedef
[[deprecated]] int val; // 변수
#if 201703L <= __cplusplus // C++17~
[[deprecated]] auto [a, b] = std::make_pair(1, 3.14); // 구조화된 바인딩
#endif
[[deprecated]] void f() {} // 함수
enum [[deprecated]] MyEnum {MyVal [[deprecated]]}; // 열거형, 열거자
enum class [[deprecated]] YourEnum {YourVal [[deprecated]]}; // 범위 있는 열거형, 열거자
(C++17~) [[fallthrough]]
switch()
에서 break
를 생략하면 다음 case
로 제어가 이동하는데, 이를 fall through
라고 합니다. 보통 컴파일러가 경고를 표시하는데(GCC의 경우 -Wimplicit-fallthrough
옵션이 필요합니다.), 이를 무시하기 위해 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
int val{0};
switch (val) {
case 0:
val = 0;
break;
case 1:
val = 1;
[[fallthrough]]; // 의도한 fall through이니 컴파일러에게 경고하지 말라고 알려줍니다.
case 2:
val = 2;
break;
}
(C++17~) [[nodiscard]]
예외 상황이 발생했을때 오류 코드를 리턴하면, 호출한 곳에서 리턴값을 확인하고 예외 처리를 해야 하는데, 강제성이 없어 오류 코드 검사를 빼먹을 수 있었는데요(예외 메카니즘(try-catch, throw와 스택 풀기, terminate) 참고),
C++17 부터는 [[nodiscard]]가 추가되어 리턴값을 무시하면 컴파일 경고로 알려줍니다.
1
2
3
4
5
6
7
enum class Error_11 {Ok, Fail};
// 리턴값을 무시하면 안됩니다.
[[nodiscard]] Error_11 GetLastError_17() {return Error_11::Ok;}
// GetLastError_17(); // (X) 컴파일 경고. 리턴값을 무시하면 안됩니다.
Error_11 error = GetLastError_17(); // (O)
또한 리턴값외에 특정 타입에 [[nodiscard]]를 주어 해당 타입을 무시하면 경고로 알려줍니다.
1
2
3
4
5
6
7
// Error_17 타입이 리턴되면 무시하면 안됩니다.
enum class [[nodiscard]] Error_17 {Ok, Fail};
Error_17 GetForcedError_17() {return Error_17::Ok;}
// GetForcedError_17(); // (X) 컴파일 경고. Error_17 타입이 리턴되면 무시하면 안됩니다.
Error_17 error = GetForcedError_17(); // (O)
(C++20~) [[nodiscard]]의 생성자 지원, [[nodiscard(“이유”)]]가 추가되었습니다.
(C++17~) [[maybe_unused]]
기존에는 이름이 없는 인자를 사용할 경우 주석을 이용하여 컴파일 경고를 우회했는데요(인자(매개변수, Parameter) 참고),
C++17 부터는 [[maybe_unused]]가 추가되어 사용되지 않은 개체의 컴파일 경고를 차단할 수 있습니다.
특히 디버그 모드에서만 사용하는 개체라면, 릴리즈 모드에서는 사용하지 않으므로 컴파일러가 경고로 알려주는데, 이 경우에 유용하게 사용할 수 있습니다.
[[deprecated]]와 동일하게 사용하며, 네임스페이스는 지원하지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// namespace [[maybe_unused]] MyLib {} // (X) 컴파일 경고. 네임스페이스는 지원하지 않습니다.
class [[maybe_unused]] MyClass { // 클래스, 구조체, 공용체
[[deprecated]] int m_Val; // 멤버 변수
[[deprecated]] void f() {} // 멤버 함수
};
template<typename T>
class [[maybe_unused]] A {}; // 템플릿
using MyType [[maybe_unused]] = int; // using
[[maybe_unused]] typedef int YourType; // typedef
[[maybe_unused]] int val; // 변수
[[maybe_unused]] auto [a, b] = std::make_pair(1, 3.14); // 구조화된 바인딩
[[maybe_unused]] void f() {} // 함수
void g([[maybe_unused]] int a, [[maybe_unused]] int b) {} // 함수 인자
enum [[maybe_unused]] MyEnum {MyVal [[maybe_unused]]}; // 열거형, 열거자
enum class [[maybe_unused]] YourEnum {YourVal [[maybe_unused]]}; // 범위 있는 열거형, 열거자
(C++17~) 특성 네임스페이스
C++17 부터는 특성 네임스페이스가 추가되어 [[msvc::noinline]]
와 같이 사용할 수 있습니다.
(C++20~) [[nodiscard]]의 생성자 지원, [[nodiscard(“이유”)]]
기존에는 [[nodiscard]]가 생성자를 지원하지 않았지만, C++20 부터는 생성자를 지원합니다. 생성된 개체가 사용되지 않으면 컴파일 경고로 알려줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
class T_20 {
public:
[[nodiscard]] T_20(int, char) {}
static void f(T_20) {}
};
// T{10, 'a'}; // (X) 컴파일 경고. 생성되어 만들어진 개체를 무시하면 안됩니다.
T_20 a{10, 'a'}; // a 변수에서 사용
T_20 b = T_20{10, 'a'}; // b 변수에서 사용
a = T_20{10, 'a'}; // a 변수에서 사용
T_20::f(T_20{10, 'a'}); // f 함수에 인자로 전달해서 사용
또한 [[nodiscard(“이유”)]]를 이용하여 설명을 추가할 수 있습니다.
(C++20~) [[likely]], [[unlikely]]
if()
나 switch()
에서 자주 사용하는 코드 조각을 알려주어 컴파일러에게 최적화 힌트를 제공합니다.
1
2
3
4
5
6
7
8
9
int val{0};
if (val < 0) [[likely]] {} // if가 참일때가 자주 실행되니 최적화를 해주세요.
else {}
switch(val) {
case 0: break;
[[likely]] case 1: break; // case 1인 경우가 자주 실행되니 최적화를 해주세요.
[[unlikely]]case 2: break; // case 2인 경우는 드물게 실행되니 최적화를 해주세요.
}
(C++20~) [[no_unique_address]]
기존에는 빈 클래스는 1byte이고, 다른 개체에 포함될 경우 해당 크기가 유지된다고 말씀드렸는데요(빈 클래스와 자식 개체의 크기 참고),
C++20 부터는 [[no_unique_address]]가 추가되어 아무 멤버 변수가 없는 개체의 크기를 최적화합니다.
[[no_unique_address]]를 사용하면, 빈 클래스인 경우 메모리를 차지하지 않도록 해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Empty {}; // 빈 클래스는 강제로 1byte
EXPECT_TRUE(sizeof(Empty) == 1);
class Composite {
int m_X;
Empty m_Empty; // 1byte 이지만 3byte 패딩됨
};
EXPECT_TRUE(sizeof(Composite) == sizeof(int) + sizeof(int));
class Composite_20 {
int m_X;
[[no_unique_address]] Empty m_Empty; // 0byte
};
EXPECT_TRUE(sizeof(Composite_20) == sizeof(int));
댓글남기기