클래스(Class)
클래스(Class)
데이터와 그 데이터를 다루는 동작을 한데 묶어 새 타입을 만들 일이 많아요. C++에서는 그걸 **클래스(class)**로 표현해요. 클래스는 사용자 정의 타입으로, 선언 문법의 decl-specifier-seq에 나오는 class-specifier로 정의돼요.
출처: cppreference
본문
문법
클래스 지정자(class specifier)는 다음 문법을 가져요.
class-key 속성(optional) class-head-name final(optional) base-clause(optional) { member-specification } |
(1) | |
class-key 속성(optional) base-clause(optional) { member-specification } |
(2) |
| 자리 | 의미 |
|---|---|
| class-key | class, struct, union 중 하나. 키워드 class와 struct는 기본 멤버 접근과 기본 기반 클래스 접근을 제외하면 동일해요. union이면 그 선언은 공용체(union) 타입을 도입해요. |
| 속성(attr) | (C++11부터) 임의 개수의 속성, alignas 지정자를 포함할 수 있음 |
| class-head-name | 정의되는 클래스의 이름, 선택적으로 한정될 수 있음 |
final |
(C++11부터) 있으면 그 클래스를 상속할 수 없음 |
| base-clause | 하나 이상의 기반 클래스와 각각에 사용된 상속 방식의 목록 (파생 클래스 참고) |
| member-specification | 접근 지정자, 멤버 객체와 멤버 함수 선언·정의의 목록 (아래 참고) |
전방 선언(Forward declaration)
다음 형태의 선언
class-key 속성 식별자 ; |
은 이 스코프에서 나중에 정의될 클래스 타입을 선언해요. 정의가 나타날 때까지 이 클래스 이름은 **불완전 타입(incomplete type)**이에요. 이 덕분에 서로를 참조하는 클래스를 만들 수 있어요.
class Vector; // 전방 선언
class Matrix
{
// ...
friend Vector operator*(const Matrix&, const Vector&);
};
class Vector
{
// ...
friend Vector operator*(const Matrix&, const Vector&);
};
그리고 특정 소스 파일이 클래스에 대한 포인터와 참조만 사용한다면, #include 의존성을 줄이는 데도 도움이 돼요.
// MyStruct.h 안
#include <iosfwd> // std::ostream의 전방 선언 포함
struct MyStruct
{
int value;
friend std::ostream& operator<<(std::ostream& os, const S& s);
// 정의는 #include <ostream>을 사용하는 MyStruct.cpp 파일에 제공
};
전방 선언이 지역 스코프에 나오면, 같은 이름으로 이전에 선언된 클래스, 변수, 함수, 그리고 둘러싸는 스코프에 나타날 수 있는 다른 모든 선언을 가려요(hide).
struct s { int a; };
struct s; // 아무 것도 하지 않음 (s는 이 스코프에서 이미 정의됨)
void g()
{
struct s; // 새 지역 struct "s"의 전방 선언
// 이 블록이 끝날 때까지 전역 struct s를 가림
s* p; // 지역 struct s에 대한 포인터
struct s { char* p; }; // 지역 struct s의 정의
}
새 클래스 이름은 다른 선언의 일부로 나타나는 정교화된 타입 지정자(elaborated type specifier)로도 도입될 수 있어요. 다만 이름 검색이 같은 이름의 이전에 선언된 클래스를 찾지 못할 때만 그래요.
class U;
namespace ns
{
class Y f(class T p); // 함수 ns::f를 선언하고 ns::T와 ns::Y를 선언
class U f(); // U는 ::U를 가리킴
// T와 Y에 대한 포인터와 참조를 쓸 수 있음
Y* p;
T* q;
}
멤버 지정(Member specification)
멤버 지정, 즉 클래스 정의의 **본문(body)**은 다음 중 임의의 개수를 중괄호로 감싼 시퀀스예요.
속성(optional) decl-specifier-seq(optional) member-declarator-list(optional) ; |
| 자리 | 의미 |
|---|---|
| 속성(attr) | (C++11부터) 임의 개수의 속성 |
| decl-specifier-seq | 지정자들의 시퀀스. 생성자, 소멸자, 사용자 정의 타입 변환 함수의 선언에서만 선택적이에요. |
| member-declarator-list | init-declarator-list와 비슷하지만, 비트 필드 선언, 순수 지정자(pure-specifier), virt-specifier(override 또는 final, C++11부터)를 추가로 허용하고, direct-non-list-initialization 문법은 허용하지 않아요. |
이 선언은 정적·비정적 데이터 멤버와 멤버 함수, 멤버 typedef, 멤버 열거형, 중첩 클래스를 선언할 수 있어요. friend 선언일 수도 있어요.
class S
{
int d1; // 비정적 데이터 멤버
int a[10] = {1, 2}; // 초기화식을 가진 비정적 데이터 멤버 (C++11)
static const int d2 = 1; // 초기화식을 가진 정적 데이터 멤버
virtual void f1(int) = 0; // 순수 가상 멤버 함수
std::string d3, *d4, f2(int); // 데이터 멤버 두 개와 멤버 함수 하나
enum { NORTH, SOUTH, EAST, WEST };
struct NestedS
{
std::string s;
} d5, *d6;
typedef NestedS value_type, *pointer_type;
};
class M
{
std::size_t C;
std::vector<int> data;
public:
M(std::size_t R, std::size_t C) : C(C), data(R*C) {} // 생성자 정의
int operator()(std::size_t r, std::size_t c) const // 멤버 함수 정의
{
return data[r * C + c];
}
int& operator()(std::size_t r, std::size_t c) // 또 다른 멤버 함수 정의
{
return data[r * C + c];
}
};
class S
{
public:
S(); // public 생성자
S(const S&); // public 복사 생성자
virtual ~S(); // public 가상 소멸자
private:
int* ptr; // private 데이터 멤버
};
class Base
{
protected:
int d;
};
class Derived : public Base
{
public:
using Base::d; // Base의 protected 멤버 d를 Derived의 public 멤버로 만듦
using Base::Base; // 모든 기반의 생성자를 상속 (C++11)
};
template<typename T>
struct Foo
{
static_assert(std::is_floating_point<T>::value, "Foo<T>: T must be floating point");
};
struct S
{
template<typename T>
void f(T&& n);
template<class CharT>
struct NestedS
{
std::basic_string<CharT> s;
};
};
멤버 선언에는 별칭 선언(alias declaration)도 포함돼요. (C++11부터)
template<typename T>
struct identity { using type = T; };
멤버 클래스 템플릿의 추론 가이드(deduction guide)도 포함돼요. (C++17부터)
template<class CharT>
struct NestedS { std::basic_string<CharT> s; };
template<class CharT>
NestedS(std::basic_string<CharT>) -> NestedS<CharT>;
using-enum 선언도 포함돼요. (C++20부터)
enum class color { red, orange, yellow };
struct highlight { using enum color; };
지역 클래스(Local classes)
클래스 선언은 함수 본문 안에도 나타날 수 있는데, 그 경우 **지역 클래스(local class)**를 정의해요. 이런 클래스의 이름은 함수 스코프 안에서만 존재하고, 밖에서는 접근할 수 없어요.
- 지역 클래스의 멤버는 그 클래스의 정의에서만 선언될 수 있어요. 단, 중첩 클래스인 멤버는 그 클래스의 가장 가까운 둘러싸는 블록 스코프에서도 선언될 수 있어요.
- 지역 클래스 안에 중첩된 클래스도 지역 클래스예요.
- 지역 클래스는 정적 데이터 멤버를 가질 수 없어요.
- 지역 클래스의 멤버 함수는 링키지(linkage)가 없어요.
- 지역 클래스의 멤버 함수는 클래스 본문 안에서 완전히 정의되어야 해요.
- 클로저 타입(closure types, C++14부터)을 제외한 지역 클래스는 멤버 템플릿을 가질 수 없어요.
- 지역 클래스는 friend 템플릿을 가질 수 없어요.
- 지역 클래스는 클래스 정의 안에서 friend 함수를 정의할 수 없어요.
- 함수(멤버 함수 포함) 안의 지역 클래스는 그 둘러싸는 함수가 접근할 수 있는 것과 같은 이름에 접근할 수 있어요.
지역 클래스는 템플릿 인자로 쓸 수 없었어요. (C++11 이전)
#include <algorithm>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v{1, 2, 3};
struct Local
{
bool operator()(int n, int m)
{
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11부터
for (int n : v)
std::cout << n << ' ';
std::cout << '\n';
}
출력:
3 2 1
이 예제에서는 함수 안에서 정의한 지역 클래스 Local을 std::sort의 비교 함수 객체로 써서 벡터를 내림차순으로 정렬했어요.
결함 보고(Defect reports)
이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 1693 | C++98 | 멤버 선언이 비어 있을 수 없었음 | 빈 선언 허용 |
| CWG 1930 | C++98 | decl-specifier-seq이 저장 클래스 지정자나 cv 한정자를 포함하면 member-declarator-list가 비어 있을 수 있었음 | 목록은 비어 있으면 안 됨 |
| CWG 2890 | C++98 | 중첩 클래스의 멤버를 어디에 선언할 수 있는지가 불명확했음 | 명확히 함 |
더 알아보기
- 파생 클래스(derived class): 상속과 base-clause.
- 멤버 함수, 생성자, 소멸자: 클래스의 다양한 구성 요소.
- 공용체(union):
union키워드로 정의하는 특별한 종류의 클래스.