본문 바로가기

진리는어디에/C++

[C++20] 어트리뷰트(Attribute)

attribute는 C++11 부터 지원되는 기능으로써 자료형, 객체, 코드등에 대하여 속성(attribute)을 설명하는 단일화된 표준 문법을 제공한다. 예를 들어 GNU와 IBM에서는 __attribute__(...))가 있었고, MS 비주얼 스튜디오에는 __declspec()이 제공하던 기능들이 C++11 부터 언어 자체에 포함 된 것이다.

attribute는 컴파일러에 추가 정보를 전달하는 것으로써 자료형, 객체, 코드등에 대하여 속성을 지정하여 구현된 내용이 속성과 부합하지 않는다면 경고 또는 에러를 출력한다. 컴파일 타임에 코드의 내용을 분석해서 attribute와 맞지 않는다면 컴파일 에러 또는 경고를 발생 시키므로 주석보다는 가능하다면 attribute를 사용하도록 하자.

atturbute는 대부분 C++전반에 걸쳐 사용가능 하지만 예를 들어 [[expect_true]] 같은 if문에서만 사용가능한 특수한 attribute도 있다.

문법

[[ attribute-list ]] // C++11
[[ using attribute-namespace : attribute-list ]] // C++17
  • attribute-list는 콤마(,)로 구분 된 0개 또는 이상의 attribute 리스트다.
    [[attr]] [[attr1, attr2, attr3(args)]] [[namespace::attr(args)]] alignas specifier
  • attubute-list는 팩확장을 의미하는 ... 으로 끝날 수 있다.
  • attribute는 [[noreturn]]과 같이 단순 attribute로 사용 될 수 있다.
  • attribute는 [[gnu::unused]] 와 같이 네임스페이스와 함께 사용 될 수 있다.
  • attribute는 [[deprecated("reason")]] 과 같이 인자와 함께 사용 될 수 있다.
  • attribute는 네임스페이스와 인자를 동시에 사용 할 수 있다.

주요 attribute 소개

아래에서 언급되는 attrubute들은 필자가 개발을하면서 종종 주석대신 종종 사용하는 것들이다. 외에도 더 많은 attribute들이 있으니 레퍼런스 사이트를 참조 하자.

noreturn (C++11)

함수 내에 return문이 없다는 것을 의미한다. if 와 같은 분기를 이용해 함수가 실행 중간에 리턴 될수 없음을 의미 한다. 함수 내에서 return문을 발견하면 컴파일러는 "warning C4645: 'noreturn'으로 선언된 함수에 return 문이 있습니다." 경고를 띄운다. 함수 중간에 로직에 의해 리턴 되는 경우가 없어야 하는 경우 명시적으로 사용 할 수 있으며, 중간에 빠져 나가야 하는 경우 throw를 이용한다.

[[ noreturn ]]
void foo(int i)
{
    if (i > 0) {
        return;
    }
}

deprecated (C++14)

단순히 [[deprecated]] 또는 [[deprecated("reason"]] 문자열을 인자로 사용 할 수 있다. 더 이상 사용하지 않는 것을 권장할 때 사용.  deprecated 속성으로 정의된 함수를 호출하게 되면 "error C4996: 'DoNotUseThisFunction': DO NOT USE THIS" 에러를 발생 시킨다.

[[deprecated("DO NOT USE THIS")]]
void DoNotUseThisFunction()
{
}

int main()
{
    DoNotUseThisFunction();
}

maybe_unused (C++17)

아마도 사용하지 않는...컴파일러가 사용되지 않는 변수, 함수 등 각종 C++코드들에 대해 아무런 경고를 띄우지 않는다. 아래 코드에서 maybe_unused 어트리뷰트를 사용하지 않았다면 "warning C4101: 'i' :참조되지 않은 지역 변수입니다." 경고를 띄운다.

int main()
{
    [[maybe_unused]]
    int i;
    return 0;
}

fallthrough (C++17)

fallthrough는 사전적 의미로는 완료(실현) 되지 못하다 라는 뜻이다. switch ~ case 문에서 이전 케이스 라벨에서 break를 사용하지 않아 다음 케이스가 평가되는 경우 그것이 의도임을 컴파일러에게 알려 경고를 발생 시키지 않게 한다.

void g() {}
void h() {}
void i() {}

void f(int n) 
{
    
    switch (n) 
    {
        case 1:
        case 2:
            g();
            [[fallthrough]];
        case 3: // 아무런 경고가 뜨지 않는다.
            h();
        case 4: // case 3에서 바로 case 4를 실행하게 되므로 경고를 발생 시킨다.
            if(n < 3) 
            {
                i();
                [[fallthrough]]; // OK
            }
            else 
            {
                return;
            }
        case 5:
            while (false) 
            {
                [[fallthrough]]; // ill-formed: next statement is not part of the same iteration
            }
        case 6:
            [[fallthrough]]; // 다음 케이스가 없으므로 오류
    }
}

※ 위 코드의 fallthrough 에러를 모두 수정한다고 해도 g(), h(), i() 함수에 대해 undefined reference 에러가 발생하지만 함수를 실행하고자 하는 목적의 코드가 아니므로 무시하고 넘어

likely, unlikely (C++20)

switch ~ case 문 또는 if와 같은 분기문을 사용 할 때 컴파일러에게 자주 타게 되는 분기를 지정하여 가장 적절한 최적화를 할 수 있도록 한다.

constexpr double pow(double x, long long n) noexcept 
{
    // n은 대부분의 경우 0 보다 큰 경우가 많음
    if (n > 0) [[likely]]
    {
        return x * pow(x, n - 1);
    }
    else [[unlikely]]
    {
        return 1;
    }
}

부록 1. 참고

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!