데이터 멤버
데이터 멤버 (Data members)
객체가 실제로 담는 데이터를 비정적 데이터 멤버(non-static data member)라고 불러요. 클래스의 멤버 명세(member specification) 안에 선언돼요.
출처: cppreference
본문
비정적 데이터 멤버는 클래스의 멤버 명세에 선언돼요.
class S
{
int n; // non-static data member
int& r; // non-static data member of reference type
int a[2] = {1, 2}; // non-static data member with default member initializer (C++11)
std::string s, *ps; // two non-static data members
struct NestedS
{
std::string s;
} d5; // non-static data member of nested type
char bit : 2; // two-bit bitfield
};
간단한 선언은 모두 허용되지만, 다음은 제외돼요:
extern과register저장 클래스 지정자는 허용되지 않아요.
| 참고 | (since) |
|---|---|
thread_local 저장 클래스 지정자는 허용되지 않는다 (단 정적 데이터 멤버에는 허용됨). |
(since C++11) |
- 불완전 타입, 추상 클래스 타입, 그리고 그 배열은 허용되지 않아요: 특히 클래스
C는 타입C의 비정적 데이터 멤버를 가질 수 없지만, 타입C&(C에 대한 참조)나C*(C에 대한 포인터)의 비정적 데이터 멤버는 가질 수 있어요. - 사용자 선언 생성자가 하나라도 있으면 비정적 데이터 멤버는 클래스의 이름과 같은 이름을 가질 수 없어요.
| 참고 | (since) |
|---|---|
자리표시자 타입 지정자(즉 auto, decltype(auto)(since C++14), 추론 대상인 클래스 템플릿 이름(since C++17), 제한된 자리표시자(since C++20))는 비정적 데이터 멤버 선언에 쓸 수 없다 (단 클래스 정의에서 초기화되는 정적 데이터 멤버에는 허용됨). |
(since C++11) |
추가로 비트 필드(bit-field) 선언이 허용돼요.
배치 (Layout)
어떤 클래스 C의 객체가 생성되면, 비참조 타입의 각 비정적 데이터 멤버는 C의 객체 표현(object representation)의 어떤 부분에 할당돼요. 참조 멤버가 어떤 저장 공간을 차지하는지는 구현이 정의하지만, 그 저장 기간은 그들이 멤버인 객체와 같아요.
| 규칙 | (since) |
|---|---|
| 비유니온 클래스 타입에 대해, 접근 지정자로 구분되지 않은(until C++11)같은 멤버 접근을 가진(since C++11) 비-제로 크기(since C++20) 멤버는 항상 나중에 선언된 멤버가 클래스 객체 안에서 더 높은 주소를 갖도록 할당된다. 접근 지정자로 구분된(until C++11)다른 접근 제어를 가진(since C++11) 멤버는 불특정 순서로 할당된다 (컴파일러는 그것들을 함께 묶을 수 있음). | (until C++23) |
| 비유니온 클래스 타입에 대해, 비-제로 크기 멤버는 항상 나중에 선언된 멤버가 클래스 객체 안에서 더 높은 주소를 갖도록 할당된다. 멤버의 접근 제어가 여전히 standard-layout 속성에 영향을 준다는 점을 참고 (아래 참고). | (since C++23) |
정렬 요구 사항은 멤버 사이 또는 클래스의 마지막 멤버 뒤에 패딩을 필요로 할 수 있어요.
Standard-layout
| 연결 | (since) |
|---|---|
| 모든 비정적 데이터 멤버가 같은 접근 제어를 갖고 특정 다른 조건을 충족하는 클래스는 standard-layout 클래스 알려져 있다 (요구 사항 목록은 standard-layout class 참고). | (until C++11)(since C++11) |
두 standard-layout 비유니온 클래스 타입의 공통 초기 시퀀스(common initial sequence) 는, 각 클래스의 첫 번째 그런 엔터티부터 시작해 선언 순서대로 비정적 데이터 멤버와 비트 필드의 가장 긴 시퀀스인데, 다음이 성립해요:
| 참고 | (since) |
|---|---|
__has_cpp_attribute(no_unique_address)가 0이 아니면 어느 엔터티도 [[no_unique_address]] 속성으로 선언되지 않는다. |
(since C++20) |
- 대응하는 엔터티가 배치 호환(layout-compatible) 타입을 가짐
- 대응하는 엔터티가 같은 정렬 요구 사항을 가짐
- 둘 다 같은 너비의 비트 필드이거나 둘 다 비트 필드가 아님
struct A { int a; char b; };
struct B { const int b1; volatile char b2; };
// A and B's common initial sequence is A.a, A.b and B.b1, B.b2
struct C { int c; unsigned : 0; char b; };
// A and C's common initial sequence is A.a and C.c
struct D { int d; char b : 4; };
// A and D's common initial sequence is A.a and D.d
struct E { unsigned int e; char b; };
// A and E's common initial sequence is empty
두 standard-layout 비유니온 클래스 타입은 cv 한정자를 무시하고 같은 타입이거나, 배치 호환 열거형(즉 같은 기본 타입을 가진 열거형)이거나, 그 공통 초기 시퀀스가 모든 비정적 데이터 멤버와 비트 필드로 구성되면 배치 호환(layout-compatible)이라고 불러요 (위 예에서 A와 B는 배치 호환).
두 standard-layout 유니온은 같은 수의 비정적 데이터 멤버를 갖고 대응하는 비정적 데이터 멤버(어떤 순서로든)가 배치 호환 타입을 가지면 배치 호환이라고 불러요.
standard-layout 타입은 다음과 같은 특별한 속성을 가져요:
- 비유니온 클래스 타입
T1의 활성 멤버를 가진 standard-layout 유니온에서,m이T1과T2의 공통 초기 시퀀스의 일부라면 다른 유니온 멤버의 비유니온 클래스 타입T2의 비정적 데이터 멤버m을 읽는 것이 허용돼요 (비-volatile glvalue를 통해 volatile 멤버를 읽는 것은 undefined라는 점 제외). - standard-layout 클래스 타입의 객체에 대한 포인터는 (비정적 데이터 멤버가 있으면) 첫 번째 비정적 비비트 필드 데이터 멤버에 대한, (있으면) 그 외에 어떤 기반 클래스 부분 객체에 대한 포인터로
reinterpret_cast될 수 있고 그 반대도 가능해요. 즉 standard-layout 타입의 첫 번째 데이터 멤버 앞에는 패딩이 허용되지 않아요. 그러한 캐스트 결과에는 엄격한 앨리어싱 규칙이 여전히 적용된다는 점을 참고해요. offsetof매크로를 사용해 standard-layout 클래스의 시작에서 임의 멤버의 오프셋을 결정할 수 있어요.
멤버 초기화 (Member initialization)
비정적 데이터 멤버는 두 가지 방법 중 하나로 초기화될 수 있어요:
- 생성자의 멤버 초기화 목록(member initializer list)에서.
struct S
{
int n;
std::string s;
S() : n(7) {} // direct-initializes n, default-initializes s
};
- 클래스 정의에서 기본 멤버 초기화식(default member initializer)으로.
| 참고 | (since) |
|---|---|
| 기본 멤버 초기화식은 비트 필드 멤버에 허용되지 않는다. | (until C++20)(since C++11) |
배열 타입의 멤버는 멤버 초기화식으로부터 크기를 추론할 수 없어요:
struct X
{
int a[] = {1, 2, 3}; // error
int b[3] = {1, 2, 3}; // OK
};
기본 멤버 초기화식은 둘러싸는 클래스의 defaulted 기본 생성자 또는 그 생성자의 예외 명세의 암시적 정의를 일으키는 것이 허용되지 않아요:
struct node
{
node* p = new node; // error: use of implicit or defaulted node::node()
};
참조 멤버는 기본 멤버 초기화식에서 임시에 바인딩될 수 없어요 (멤버 초기화 목록에도 같은 규칙이 존재함):
struct A
{
A() = default; // OK
A(int v) : v(v) {} // OK
const int& v = 42; // OK
};
A a1; // error: ill-formed binding of temporary to reference
A a2(1); // OK (default member initializer ignored because v appears in a constructor)
// however a2.v is a dangling reference
| 참고 | (since) |
|---|---|
| 참조 멤버가 기본 멤버 초기화식에서 초기화되는 경우(until C++20)멤버가 기본 멤버 초기화식을 가질 경우(since C++20) 그 초기화식의 잠재적으로 평가되는 부분 표현식이 그 기본 멤버 초기화식을 사용할 집합체 초기화이면 프로그램은 ill-formed이다. | (since C++17) |
사용 (Usage)
비정적 데이터 멤버나 비정적 멤버 함수의 이름은 다음 세 가지 상황에서만 나타날 수 있어요:
- 클래스 멤버 접근 표현식의 일부로, 그 클래스가 이 멤버를 갖거나 이 멤버를 가진 클래스에서 파생된 경우. 여기에는
this가 허용된 어떤 문맥(멤버 함수 본문, 멤버 초기화 목록, 클래스 내 기본 멤버 초기화식)에서 비정적 멤버 이름이 쓰일 때 나타나는 암시적this->멤버 접근 표현식이 포함돼요.
struct S
{
int m;
int n;
int x = m; // OK: implicit this-> allowed in default initializers (C++11)
S(int i) : m(i), n(m) // OK: implicit this-> allowed in member initializer lists
{
this->f(); // explicit member access expression
f(); // implicit this-> allowed in member function bodies
}
void f();
};
- 비정적 멤버에 대한 포인터를 만들기 위해.
struct S
{
int m;
void f();
};
int S::*p = &S::m; // OK: use of m to make a pointer to member
void (S::*fp)() = &S::f; // OK: use of f to make a pointer to member
- (데이터 멤버에만, 멤버 함수에는 아님) 미평가 피연산자(unevaluated operands)에서 사용될 때.
struct S
{
int m;
static const std::size_t sz = sizeof m; // OK: m in unevaluated operand
};
std::size_t j = sizeof(S::m + 42); // OK: even though there is no "this" object for m
참고: 이런 사용은 N2253의 CWG 이슈 613 해결을 통해 허용되며, 일부 컴파일러(예: clang)는 이것을 C++11의 변경으로 취급해요.
참고 (Notes)
| Feature-test 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_nsdmi |
200809L | (C++11) | Non-static data member initializers |
__cpp_aggregate_nsdmi |
201304L | (C++14) | Aggregate classes with default member initializers |
더 알아보기 (Learn more)
- 클래스 — 데이터 멤버와 멤버 함수를 묶는 타입.
- 정적 멤버 — 클래스에 종속되지만 객체별로는 존재하지 않는 멤버.
- 비정적 멤버 함수 — 객체에 대해 호출되는 멤버 함수.
std::is_standard_layout(C++11) — 타입이 standard-layout 타입인지 검사하는 클래스 템플릿.offsetof— standard-layout 타입의 시작에서 지정된 멤버까지의 바이트 오프셋을 얻는 함수 매크로.