식별자

식별자 (Identifiers)

식별자(identifier)는 임의로 긴 숫자, 밑줄, 대문자·소문자 라틴 문자, 대부분의 유니코드 문자로 이루어진 이름이에요. 변수·함수·타입 등에 이름을 붙일 때 어떤 문자가 허용되는지, 어떤 이름은 쓰면 안 되는지 알아볼게요.

출처: cppreference

본문

문법

유효한 식별자의 첫 문자는 다음 중 하나여야 해요:

  • 대문자 라틴 문자 A–Z
  • 소문자 라틴 문자 a–z
  • 밑줄(_)
  • 유니코드 속성 XID_Start를 갖는 모든 유니코드 문자

유효한 식별자의 그 외 문자는 다음 중 하나여야 합니다:

  • 숫자 0–9
  • 대문자 라틴 문자 A–Z
  • 소문자 라틴 문자 a–z
  • 밑줄(_)
  • 유니코드 속성 XID_Continue를 갖는 모든 유니코드 문자

XID_Start와 XID_Continue 속성을 가진 문자 목록은 DerivedCoreProperties.txt에서 찾을 수 있어요.

식별자는 대소문자를 구분하며(소문자와 대문자는 서로 다른 문자), 모든 문자가 의미를 가져요. 모든 식별자는 정규화 형식 C(Normalization Form C)를 따라야 합니다.

참고: 대부분의 구현에서 유니코드 식별자 지원은 제한적이에요(예: gcc, 10 이하 버전).

선언에서의 사용

식별자는 객체, 참조, 함수, 열거자(enumerator), 타입, 클래스 멤버, 네임스페이스, 템플릿, 템플릿 특수화, 매개변수 팩(parameter pack, C++11부터), goto 레이블 등 다양한 엔터티에 이름을 붙이는 데 쓸 수 있어요. 다음 예외가 있죠:

  • 키워드(keyword)인 식별자는 다른 용도로 쓸 수 없어요. 키워드가 아닌 용도로 쓰일 수 있는 유일한 자리는 attribute-token이에요(예: [[private]]은 유효한 attribute).
  • 특정 연산자와 구분 기호의 대체 표기(alternative representation)인 식별자도 다른 용도로 쓸 수 없어요.
  • 특별한 의미를 가진 식별자(final, import, module(C++20부터), override)는 일반 식별자가 아니라 특정 문맥에서 명시적으로 사용돼요. 달리 명시되지 않는 한, 어떤 식별자가 특별한 의미를 갖는지에 대한 모호함은 그 토큰을 일반 식별자로 해석해서 해결됩니다. (C++11부터)

다음 형태의 토큰 또는 전처리 토큰으로 나타나는 식별자(사용자 정의 문자열 리터럴 operator"" id 같은 곳은 제외, C++11부터)는 예약돼 있어요:

  • 전역 네임스페이스에서 밑줄로 시작하는 식별자
  • 이중 밑줄을 포함하거나, 밑줄 뒤에 대문자가 오는 형태로 시작하는 식별자 — 다음은 제외:
    • 미리 정의된 매크로(언어 기능 테스트 매크로 포함, C++20부터)
    • std::_Exit, __func__(C++11부터)
    • 표준 라이브러리에 정의된 다음 매크로: C 스타일 I/O 라이브러리 매크로 _PRINTF_NAN_LEN_MAX(C++26부터), _IOFBF, _IOLBF, _IONBF
    • C 호환 매크로 __alignas_is_defined__alignof_is_defined(<stdalign.h>에 정의), __bool_true_false_are_defined(<stdbool.h>에 정의)
    • 라이브러리 기능 테스트 매크로(C++20부터)

여기서 "예약"이란 표준 라이브러리 헤더가 내부 용도로 그런 식별자를 #define하거나 선언할 수 있고, 컴파일러가 그런 종류의 비표준 식별자를 미리 정의할 수 있으며, name mangling 알고리즘이 이 식별자 중 일부가 사용되지 않는다고 가정할 수 있다는 뜻이에요. 프로그래머가 그런 식별자를 쓰면, 진단(diagnostic)이 없어도 ill-formed이 됩니다.

추가로, 번역 단위에서 특정 이름을 #define하거나 #undef하는 것은 정의되지 않은 동작(undefined behavior)이에요. 자세한 내용은 예약 매크로 이름을 참고하세요.

좀비 식별자 (Zombie identifiers)

C++14부터 C++ 표준 라이브러리에서 제거된 식별자가 있어요. 이들은 좀비 이름(zombie name) 목록에 나옵니다. 다만 이런 식별자들은 특정 문맥에서 이전 표준화를 위해 여전히 예약되어 있어요. 제거된 멤버 함수 이름은 함수류 매크로 이름으로 쓸 수 없고, 다른 제거된 멤버 이름은 이식 가능한 코드에서 객체류 매크로 이름으로 쓸 수 없어요.

표현식에서의 사용

변수, 함수, 개념의 특수화(concept, C++20부터), 열거자를 가리키는 식별자는 표현식으로 쓰일 수 있어요. 그 식별자만으로 이루어진 표현식의 결과는 그 식별자가 가리키는 엔터티예요. 식별자가 함수, 변수, 템플릿 매개변수 객체(C++20부터), 데이터 멤버를 가리키면 표현식의 값 범주(value category)는 lvalue이고, 그 외에는(C++11 이전 rvalue, C++11부터 prvalue)입니다. 예를 들어 열거자는 rvalue(C++11 이전)/prvalue(C++11부터) 표현식이고, 개념의 특수화는 bool prvalue(C++20부터)예요.

타입

식별자 표현식의 타입은 그 식별자가 가리키는 엔터티의 타입과 같아요. 다음 예외가 있습니다:

  • 한정되지 않은 식별자가 가리키는 엔터티가 지역 엔터티이고, 그 식별자가 나타나는 선언 영역에서 비평가 피연산자(unevaluated operand) 밖에서 이름을 붙이면 중간에 나타나는 람다 표현식이 그 값을 복사로 캡처하게 되는 경우, 표현식의 타입은 가장 안쪽의 그런 중간 람다 표현식의 클로저 객체에 그런 캡처용으로 선언되는 비정적 데이터 멤버를 가리키는 클래스 멤버 접근 표현식의 타입이 돼요.
void f()
{
    float x, &r = x;
    [=]
    {
        decltype(x) y1;      // y1 has type float
        decltype((x)) y2 = y1; // y2 has type float const& because this lambda
                               // is not mutable and x is an lvalue
        decltype(r) r1 = y1; // r1 has type float&
        decltype((r)) r2 = y2; // r2 has type float const&
    };
}
  • 가리키는 엔터티가 타입 T인 템플릿 매개변수의 템플릿 매개변수 객체(template parameter object)라면, 표현식의 타입은 const T예요. (C++20부터)

한정되지 않은 식별자 (Unqualified identifiers)

적절히 선언된 식별자 외에도, 다음과 같은 것들이 같은 역할로 표현식에 쓰일 수 있어요:

  • 함수 표기법의 오버로드된 연산자 이름(예: operator+, operator new)
  • 사용자 정의 변환 함수 이름(예: operator bool)
  • 사용자 정의 리터럴 연산자 이름(예: operator"" _km, C++11부터)
  • 인자 목록이 뒤따르는 템플릿 이름(예: MyTemplate<int>)
  • ~ 뒤에 클래스 이름(예: ~MyClass)
  • ~ 뒤에 decltype 지정자(예: ~decltype(str), C++11부터)
  • ~ 뒤에 팩 인덱싱 지정자(예: ~pack...[0], C++26부터)

이런 것들은 식별자와 함께 '한정되지 않은 식별자 표현식(unqualified identifier expression)'이라고 불러요.

한정된 식별자 (Qualified identifiers)

한정된 식별자 표현식은 한정되지 않은 식별자 표현식 앞에 스코프 결정 연산자 ::를 붙이고, 선택적으로 스코프 결정 연산자로 구분된 다음 것들의 시퀀스가 이어지는 형태예요:

  • 네임스페이스 이름
  • 클래스 이름
  • 열거형 이름 / 클래스나 열거형 타입을 나타내는 decltype 지정자(C++11부터)
  • 클래스나 열거형 타입을 나타내는 팩 인덱싱 지정자, 클래스·열거형 타입이나 네임스페이스를 지정하는 splice 지정자, 그리고 뒤에 템플릿 인자 목록이 와서 함께 클래스·열거형 타입을 나타내는 클래스 템플릿이나 별칭 템플릿을 지정하는 splice 지정자(C++26부터)

예를 들어 std::string::npos는 네임스페이스 std의 클래스 string 안 정적 멤버 npos를 가리키는 표현식이에요. ::tolower는 전역 네임스페이스의 함수 tolower를 가리키고, ::std::cout은 최상위 네임스페이스인 std의 전역 변수 cout을 가리켜요. boost::signals2::connection은 네임스페이스 boost 안에 선언된 네임스페이스 signals2에 선언된 타입 connection을 가리킵니다.

의존 템플릿 이름을 명확히 하기 위해 필요하면 한정된 식별자에 template 키워드가 나타날 수 있어요. 한정된 식별자의 이름 조회에 대한 자세한 내용은 한정 조회(qualified lookup)를 참고하세요.

암시적 멤버 접근 변환

식별자 표현식 E가 어떤 클래스 C의 비정적 비타입 멤버를 가리키고, 다음 조건이 모두 만족되면 E는 클래스 멤버 접근 표현식 this->E로 변환돼요:

  • E가 멤버 접근 연산자의 오른쪽 피연산자가 아님
  • E가 한정된 식별자 표현식이라면, E가 주소 연산자의 괄호를 씌우지 않은 피연산자가 아님
  • 다음 중 하나를 만족: E가 잠재적으로 평가됨(potentially evaluated), CE의 가장 안쪽 둘러싼 클래스, CE의 가장 안쪽 둘러싼 클래스의 기반 클래스

이 변환은 템플릿 정의 문맥에서는 적용되지 않아요(의존 이름 참고).

struct X
{
    int x;
};

struct B
{
    int b;
};

struct D : B
{
    X d;
    
    void func()
    {
        d;   // OK, will be transformed into this->d
        b;   // OK, will be transformed into this->b
        x;   // Error: this->x is ill-formed
        
        d.x; // OK, will be transformed into this->d.x
             // instead of d.this->x or this->d.this->x
    }
};

이름 (Names)

이름(name)은 다음 중 하나를 사용해서 엔터티를 가리키는 것입니다:

  • 식별자
  • 함수 표기법의 오버로드된 연산자 이름(operator+, operator new)
  • 사용자 정의 변환 함수 이름(operator bool)
  • 사용자 정의 리터럴 연산자 이름(operator"" _km, C++11부터)
  • 인자 목록이 뒤따르는 템플릿 이름(MyTemplate<int>)

모든 이름은 선언(declaration)에 의해 프로그램에 도입돼요. 둘 이상의 번역 단위에서 쓰인 이름은 연결(linkage)에 따라 같은 엔터티를 가리킬 수도, 다른 엔터티를 가리킬 수도 있어요.

컴파일러가 프로그램에서 모르는 이름을 만나면, 이름 조회(name lookup)로 그 이름을 도입한 선언과 연결해요. 템플릿 선언·정의 안의 의존 이름은 예외인데, 그런 이름에 대해서는 컴파일러가 이름이 타입인지 템플릿인지 다른 엔터티인지 결정해야 하며 명시적 명확화(explicit disambiguation)가 필요할 수 있어요.

더 알아보기 (Learn more)