static 멤버
static 멤버 (static members)
클래스 안에서 객체에 묶이지 않는 멤버를 선언하고 싶을 때 쓰는 게 static 키워드예요. 다만 static은 클래스 정의 바깥에서는 다른 의미를 가져요. 이 페이지에서는 클래스 안에서 쓰는 static 멤버를 중심으로, static 멤버 함수와 static 데이터 멤버의 선언·정의·사용 규칙을 정리할게요.
출처: cppreference
본문
클래스 정의 안에서 키워드 static은 클래스 인스턴스에 묶이지 않는 멤버를 선언해요. 클래스 정의 바깥에서는 의미가 달라지는데, 그 내용은 storage duration 문서에서 다뤄요.
문법
static 멤버의 선언은, 선언 지정자(declaration specifiers)에 키워드 static이 들어 있는 멤버 선언이에요. static 키워드는 보통 다른 지정자보다 앞에 와서(그래서 문법을 흔히 static 데이터 멤버, static 멤버 함수처럼 비공식적으로 표현해요) 어디에 붙어도 상관없어요.
어떤 static 데이터 멤버와 static 멤버 함수의 이름은, 그 클래스를 담고 있는 클래스의 이름과 같으면 안 돼요.
동작 방식 (Explanation)
클래스의 static 멤버는 클래스의 객체와 연결되지 않아요. 즉 static 또는 thread 저장 기간(thread storage duration, C++11부터)을 갖는 독립적인 변수거나, 일반 함수예요.
static 키워드는 클래스 정의 안에 있는 static 멤버의 선언에만 쓰이고, 그 static 멤버의 정의에는 쓰지 않아요:
class X { static int n; }; // 선언 ('static' 사용)
int X::n = 1; // 정의 ('static' 사용 안 함)
클래스 본문 안의 선언은 정의가 아니며, 멤버를 (void를 제외한) 불완전 타입으로 선언할 수 있어요. 이때 자기 자신이 선언된 타입도 포함돼요:
struct Foo;
struct S
{
static int a[]; // 선언, 불완전 타입
static Foo x; // 선언, 불완전 타입
static S s; // 선언, 불완전 타입 (자기 자신의 정의 안)
};
int S::a[10]; // 정의, 완전 타입
struct Foo {};
Foo S::x; // 정의, 완전 타입
S S::s; // 정의, 완전 타입
클래스 T의 static 멤버 m을 참조하는 데는 두 가지 형태를 써요. 한정 이름(qualified name) T::m, 또는 멤버 접근 표현식 E.m이나 E->m이에요(여기서 E는 각각 T나 T*로 평가되는 표현식). 같은 클래스 범위 안에서는 한정 없이 바로 쓸 수 있어요:
struct X
{
static void f(); // 선언
static int n; // 선언
};
X g() { return X(); } // X를 반환하는 어떤 함수
void f()
{
X::f(); // X::f는 static 멤버 함수의 한정 이름
g().f(); // g().f는 static 멤버 함수를 가리키는 멤버 접근 표현식
}
int X::n = 7; // 정의
void X::f() // 정의
{
n = 1; // 이 범위에서는 X::n을 그냥 n으로 접근 가능
}
static 멤버는 클래스 멤버 접근 규칙(private, protected, public)을 그대로 따르는 대상이 돼요.
static 멤버 함수
static 멤버 함수는 어떤 객체에도 연결되지 않아요. 호출할 때 this 포인터가 없어요.
static 멤버 함수는 virtual, const, volatile, 또는 ref-qualified일 수 없어요.
static 멤버 함수의 주소는 일반 함수 포인터에 저장할 수 있지만, 멤버 함수 포인터에는 저장할 수 없어요.
static 데이터 멤버
static 데이터 멤버는 어떤 객체에도 연결되지 않아요. 클래스의 객체가 하나도 정의되지 않았어도 존재해요. 키워드 thread_local을 쓰지 않는 한 static 데이터 멤버의 인스턴스는 프로그램 전체에 단 하나만 있고 static 저장 기간을 가져요. thread_local을 쓰면 스레드마다 하나씩, thread 저장 기간을 갖는 객체가 돼요(C++11부터).
static 데이터 멤버는 mutable일 수 없어요.
네임스페이스 범위에 있는 클래스의 static 데이터 멤버는, 그 클래스 자신이 외부 링크를 가질 때(무명 네임스페이스의 멤버가 아닐 때) 외부 링크를 가져요. 지역 클래스(함수 안에 정의된 클래스)와 무명 클래스, 그리고 무명 클래스의 멤버 클래스는 static 데이터 멤버를 가질 수 없어요.
static 데이터 멤버는 inline으로 선언할 수 있어요. inline static 데이터 멤버는 클래스 정의 안에서 정의할 수 있고 이니셜라이저를 지정할 수 있어요. 클래스 바깥의 정의가 필요 없어요: cpp struct X { inline static int fully_usable = 1; // 클래스 바깥 정의 불필요, ODR-사용 가능 inline static const std::string class_name{"X"}; // 마찬가지 static const int non_addressable = 1; // 비-inline 상수 참고: 값으로는 // 사용 가능하지만 ODR-사용은 불가 // static const std::string class_name{"X"}; // 이 형태의 비-정수 선언은 // 아예 허용되지 않음 }; |
(C++17부터) |
|---|
상수 static 멤버
정수 또는 열거 타입의 static 데이터 멤버가 const(그리고 volatile이 아님)로 선언되면, 모든 표현식이 상수 표현식인 이니셜라이저로 클래스 정의 안에서 바로 초기화할 수 있어요:
struct X
{
const static int n = 1;
const static int m{2}; // C++11부터
const static int k;
};
const int X::k = 3;
LiteralType의 static 데이터 멤버가 constexpr로 선언되면, 모든 표현식이 상수 표현식인 이니셜라이저로 클래스 정의 안에서 바로 초기화해야 해요: cpp struct X { constexpr static int arr[] = { 1, 2, 3 }; // OK constexpr static std::complex<double> n = {1, 2}; // OK constexpr static int k; // 오류: constexpr static은 이니셜라이저가 필요 }; |
(C++11부터) |
|---|
const 비-inline(C++17부터) static 데이터 멤버나 constexpr static 데이터 멤버(C++11~C++17)가 ODR-사용되면 네임스페이스 범위의 정의가 여전히 필요하지만, 그 정의에는 이니셜라이저가 있으면 안 돼요.
constexpr static 데이터 멤버는 암묵적으로 inline이라 네임스페이스 범위에서 다시 선언할 필요가 없어요. 이니셜라이저 없는 재선언(예전엔 필요했던)은 여전히 허용되지만 폐기(deprecated)됐어요. |
(C++17부터) |
|---|
struct X
{
static const int n = 1;
static constexpr int m = 4;
};
const int* p = &X::n, *q = &X::m; // X::n와 X::m가 ODR-사용됨
const int X::n; // ... 따라서 정의가 필요
constexpr int X::m; // ... (C++17에서는 X::m 제외)
키워드
static
결함 보고 (Defect reports)
다음 동작 변경 결함 보고는 과거에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 표준 | 발표 당시 동작 | 수정된 동작 |
|---|---|---|---|
| CWG 194 | C++98 | (static) 멤버 함수 이름이 클래스 이름과 같을 수 있었음 | 이름 제한 추가 (비-static 멤버 함수 포함) |
더 알아보기
static키워드는 클래스 바깥에서 변수의 저장 기간과 링크를 결정해요.- 참조는 C++23 표준(ISO/IEC 14882:2024) 11.4.9 Static members [class.static]에서 볼 수 있어요.