광고


[C++11/14/17] Standard attributes TR1/C++11/C++14/C++17


0. 서문


C++11부터 타입, 오브젝트, 각종 코드들(함수, 코드블럭 등등)에 대하여 컴파일러에게 여러 힌트를 줄 수 있는 attribute가 정식 도입되었다.
일종의 메타데이터인 힌트들로부터 컴파일러는 코드 흐름 제어의 변경, 컴파일 경고를 제공하거나 제거하는 등등의 특정한 동작들을 수행할 수 있게 된다.

Standard attribute의 전형적인 syntax는 다음과 같다.
(C#의 attributes와 다르게 [[ ... ]]로 브라켓을 2개씩 써야함을 혼동하지 말아야 한다)

  1. // C++11부터...
  2. // attribute argument-list는 comma seperate 형태로 늘어 쓸 수 있다.
  3. [[ attribute argument-list ]]
  4.  
  5. // C++17부터...
  6. // namespace와 attribute argument-list를 같이 쓸 수 있는 형태
  7. // ex) [[using CC: opt(1), debug]] 이는 [[CC::opt(1), CC::debug]]와 동일하다.
  8. [[ using attribute-namespace : attribute argument-list ]]


1. [[noreturn]] - C++11

함수 선언에서만 사용 가능, [[noreturn]] attribute는 얼핏 보기에 컴파일러에게 어떠한 값도 반환하지 않겠다고 힌트를 주는 것처럼 보이지만, 전혀 그렇지 않다.

이 attribute는 컴파일러에게 코드 control flow상 caller에게 돌아가지 않음을 의미하는 것이다.
즉, 실행된 이후 caller에게 control이 넘어오지 않는 것이다.

이를 통해 컴파일러는 다음과 같은 다양한 최적화들을 할 수 있게 된다.
  • 호출에 관련된 임시 상태들을 저장하고 불러올 필요가 없음.
  • caller에게 돌아오지 않기에, 불필요한 블럭 내 하위 코드들을 dead-code로써 지워버릴 수가 있음.

[[noreturn]]으로 선언된 함수가 어떠한 값(void라도) 반환하려 할 경우의 결과는 미정(undefined)이다.
(VS2017의 경우 debug 모드에서는 별 탈 없었지만, release 모드로 컴파일될 경우 프로그램 동작이 중지되었다)

아래 예제코드는 전형적인 모습에 대해 살펴볼 수 있는 기본 예제들이다.

  1. // return 문이 수행되지 않는다
  2. [[noreturn]] void f()
  3. {
  4.     throw "error";
  5.     // OK
  6. }
  7.  
  8. // 경우에 따라 return 문이 수행될 수 있으며, 미정의 결과가 나올 수 있다.
  9. // VS2017의 경우 release 모드에서 프로그램 동작이 중지되었다.
  10. [[noreturn]] void q(int i)
  11. {
  12.     // 인자 i가 0보다 같거나 작을 경우 미정의 결과
  13.     if (> 0)
  14.         throw "positive";
  15. }

그리고, 코드 흐름상의 이해를 돕기 위한 예제는 다음과 같다.
위에서 설명했듯이 noreturn 함수 호출 이후의 하위 코드들은 실행되지 않기에 컴파일러는 코드를 날려버릴 수 있다.

  1. [[noreturn]] void f()
  2. {
  3.     // blah...blah...blah
  4. }
  5.  
  6. void g()
  7. {
  8.     f();
  9.  
  10.     // f() 수행이후 caller에게 흐름이 넘어오지 않으므로, 하위 코드들은 모두 dead-code
  11.     // unreachable:
  12.     // 따라서, 컴파일러는 이를 생성해 내지 않는다.    
  13.     std::cout << "No! That's impossible" << std::endl;
  14. }

지금까지의 내용을 이해했다면, 다음의 표준 함수들이 [[noreturn]]으로 선언되어 있는 것에 대해 동의할 수 있을 것이다.
  • std::_Exit, std::exit, std::quick_exit, std::abort, std::terminate
  • std::rethrow_exception, std::throw_with_nested, std::nested_exception::rethrow_nested

이것을 일반적인 개발에서 써먹을 데가 어디가 있을까...
Customized assertion 함수 만들 때... 음...그리고... 음... 아직은 더 생각이 나진 않는다.


2. [[deprecated]] / [[deprecated("reason"]] - C++14

사용은 가능하나, 가급적 쓰지 말기를 권고할 때 붙이는 atttribute이다.
제목에서 알 수 있듯이, "reason"을 붙일 수도 안 붙일 수도 있다.

이 attribute는 다음 항목들에 대해 사용이 가능하다.
  • 클래스 선언
  • typedef 선언
  • 변수 / non-static 데이터 멤버 선언
  • 함수 선언
  • 열거형 선언
  • 템플릿 특수화 선언

내용에 대해 특별할 것이 없으므로, 예제로 마무리~

  1. // 컴파일시 아래와 같은 컴파일 note/warning이 발생한다.
  2.  
  3. // note: 'f' 선언을 참조하십시요
  4. [[deprecated]] void f(int i)
  5. {
  6.     if (> 0)
  7.         throw "positive";
  8. }
  9.  
  10. // note: 'g' 선언을 참조하십시요
  11. [[deprecated("Will be removed in the next version")]] void q(int i)
  12. {
  13.     if (> 0)
  14.         throw "positive";
  15. }
  16.  
  17. int main()
  18. {
  19.     // warning C4996: 'f': deprecated로 선언되었습니다.
  20.     f(1);
  21.  
  22.     // warning C4996: 'g': Will be removed in the next version
  23.     g(1);
  24. }


3. [[fallthrough]] - C++17

switch 문에서 이미 오랫동안 fallthrough 방식을 써 왔지만, 이를 좀 더 명확하게 컴파일러에게 알림으로써 컴파일 경고가 나오지 않도록 하는 attribute 이다.

다만, [[fallthrough]] 사용시 다음 사항에 대해 주의해서 사용해야 한다.
  • 다음 case/default 레이블 바로 직전에 위치시킬 것

즉, switch 문의 마지막 case/default(의 끝)에서는 [[fallthrough]] attribute를 사용할 수가 없다.
예제를 보면, 뭔 말을 하는 것인지 쉽게 이해할 수 있을 것이다.

  1. void f(int n)
  2. {
  3.     void g(), h(), i();
  4.  
  5.     switch (n)
  6.     {
  7.         case 1:
  8.         case 2:
  9.             g();
  10.         [[fallthrough]];
  11.         // no warning on fallthrough
  12.         case 3:
  13.             h();
  14.         // compiler may warn on fallthrough
  15.         case 4:
  16.             i();
  17.         // ill-­formed, not before a case label
  18.         [[fallthrough]];
  19.     }
  20. }


4. [[nodiscard]] - C++17

참고 : VS2017 15.2에서는 정상적으로 동작하지 않는다.

[[nodiscard]] attribute를 사용하게 되면 컴파일러에게 값이 버려질 경우 컴파일 경고를 내 달라는 힌트를 준다.
즉, 이 값은 반드시 사용되어야 하니, 그렇지 않을 경우(discarded-expression) 경고를 내줘~ 라는 것이다.

[[nodiscard]]는 다음에서 사용이 가능하다.
  • 함수 선언
  • 열거형 선언
  • 클래스 선언

예를 들어, 함수가 [[nodiscard]]로 선언되거나, 함수가 nodiscard로 선언된 열거형이나 클래스를 값으로 반환할 경우 컴파일러는 컴파일 경고를 발생시킨다. 다음 예제를 살펴 보자.

  1. struct [[nodiscard]] X
  2. {
  3.     int i;
  4. };
  5.  
  6. X foo()
  7. {
  8.     return { 42 };
  9. }
  10.  
  11. X x { 42 };
  12. X& foo2() { return x; }
  13.  
  14. [[nodiscard]] int bar()
  15. {
  16.     return 3;
  17. }
  18.  
  19. int main()
  20. {
  21.     // warning: ignoring returned value of type 'X', declared with attribute nodiscard
  22.     foo();
  23.  
  24.     // value가 아닌 reference이기에 컴파일 경고가 발생하지 않는다.
  25.     foo2();
  26.  
  27.     // warning: ignoring return value of 'int bar()', declared with attribute nodiscard
  28.     auto i = (bar()55);
  29.  
  30.     // void로 강제 타입 캐스팅을 할 경우엔 컴파일 경고(unused variable 'b')가 발생하지 않는다.
  31.     (void)bar();
  32.  
  33.     return 0;
  34. }

18라인 : foo() 함수는 [[nodiscard]]로 선언된 struct X를 값으로 반환하는 데 그것이 버려졌기에 경고 발생.
25라인 : foo2() 함수는 [[nodiscard]]로 선언되었지만, struct X를 참조로 반환하기에 컴파일 경고가 발생하지 않았다.
28라인 : bar() 함수는 [[nodiscard]]로 선언된 int 값을 반환하는데, 그것이 대입되지 않고 버려졌다고 판단되어 경고 발생.
31라인 : (void)로 강제 캐스팅할 경우에는 경고가 발생하지 않는다.


5. [[maybe_unused]] - C++17

참고 : VS2017 15.2에서는 정상적으로 동작하지 않는다.

기본적으로 [[nodiscard]]의 반대되는 기능을 수행한다 보면 된다.

대부분의 C++ 컴파일러들에는 이미 오래전부터 선언해 놓고 사용하지 않는 변수에 대해 컴파일 경고를 제공해 왔다.
하지만, 미사용 변수에 [[maybe_unused]] attribute를 붙이면, 미사용에도 불구하고 컴파일러 경고가 발생하지 않게 된다.

[[maybe_unused]]는 다음에서 사용이 가능하다.
  • 함수 선언
  • 열거형 / enumerator 선언
  • 클래스 선언
  • typedef 선언
  • 변수 / non-static 데이터 멤버 선언

코드를 짜다보면, 아래와 같은 상황은 심심치 않게 만날 수 있다.

  1. #include <cassert>
  2.  
  3. [[maybe_unused]] void ff(bool thing1, bool thing2)
  4. {
  5.     [[maybe_unused]] bool b = thing1 && thing2;
  6.  
  7.     // assert는 release 모드로 컴파일시 compiled-out 되므로, 윗 라인의 변수 b는 미사용이다.
  8.     // [[maybe_unused]] attribute를 붙였기에 컴파일 경고가 발생하지 않는다.
  9.     assert(b);
  10. }
  11.  
  12. int main()
  13. {
  14.     ff(truetrue);
  15.  
  16.     return 0;
  17. }


6. 결론

이번에 소개한 Standard attributes들은 사실 모른다고 해도 코드 작성에 크게 무리가 없다고 해도 무방하다.
하지만, 조금 더 적확하게 컴파일러에게 힌트를 던질 수 있고, [[deprecated]]같은 경우 라이브러리 제공자에겐 유용할 수 있다.

이 내용에 대해 심각하게 받아들이거나 꼭 학습해야 된다고 생각하진 않지만, 알아둬서 손해볼 건 없으니까 말이다.


1 2 3 4 5 6 7 8 9 10 다음