16. 클래스
클래스는 기본 제공 타입처럼 편리하게 쓰일 수 있는 새로운 타입을 생성하는 수단이다.
16.2 클래스 기초
클래스를 아주 간단히 요약하면 다음과 같다.
클래스는 사용자 정의 타입이다.
클래스는 멤버의 집합으로 구성된다. 가장 흔한 종류는 멤버 변수와 멤버 함수이다.
멤버 함수는 초기화, 복사, 이동, 소멸의 의미를 정의할 수 있다.
개체에 대해서는 . 과 -> 를 이용해 멤버에 접근할 수 있다.
+, !, [] 등의 연산자는 어떤 클래스도 오버라이딩 할 수 있다.
클래스는 멤버가 포함된 네임스페이스이다.
public 멤버는 클래스의 인터페이스를 제공하고, private 멤버는 구현 세부 사항을 제공한다.
struct 는 public 멤버가 기본인 클래스이다.
16.2.1 멤버 함수
클래스 정의 안에 있는 함수를 멤버 함수라고 부르며, 멤버 함수 내에서는 개체를 명시적으로 가리키지 않고 멤버 이름을 사용할 수 있다.
16.2.2 기본 복사
개체는 기본적으로 복사 될 수 있다. -> 특히 클래스 개체는 그것의 클랫스 개체의 사본으로 초기화 할 수 있다.
16.2.4 class 와 struct
class 와 struct 의 차이점은 다음과 같다.
class 의 기본 접근제어자는 private
struct 의 기본 접근제어자는 public
때문에 아래 코드는 같은 의미를 가지고 있다.
위 두가지 정의는 서로 바꾸어 사용할 수 있지만, 그래도 한 가지 스타일을 고수하는 편이 대개는 바람직하다.
어떤 스타일을 고수하는지는 보통은 환경 또는 개인의 선택에 달려있다. -> 필자는 '단순한 데이터 구조' 에 대해서는 struct -> 어떤 개체가 불변 속성을 가지는 것이 적합하다고 생각될 때 class 를 사용한다. (불변 속성을 가지는 것에 적합 : 생성자에서 맴버변수를 초기화 해야할 때?)
16.2.5 생성자
클래스 개체 생성 시 호출되는 함수를 생성자라고 부른다. -> 생성자는 개체의 적절한 초기화를 보장하므로써 멤버 함수의 구현을 대폭 단순화해준다.
생성자는 클래스 초기화를 정의하는 것이므로, {} 표기를 사용할 수도 있다.
개체 생성 시 () 보다는 {} 를 사용한 초기화가 권장되는데 이유는 다음과 같다.
통일된 초기화 방식
Narrowing Conversion 방지
모호성 방지
{} 는 함수 사용과 개체 초기화 사이의 모호성을 방지한다. -> {} 는 개체 초기화에서 사용이 가능하다.
생성자는 통상적인 함수가 준수하는 똑같은 오버로딩 규칙을 준수한다.
16.2.6 explicit 생성자
생성자 호출에서 암시적 형변환이 많은 경우 상당한 혼란과 오류의 원인이 될 수 있다.
아래 코드는 15 와 Date 사이에 명확한 논리적 연결 관계가 존재하지 않는다.
다행스럽게도 explicit 생성자를 통해서 암시적 변환으로 생성자를 호출하지 않게 할 수 있다.
아래 코드를 살펴보면, = 사용한 복사 초기화에서는 explicit 생성자를 사용할 수 없다.
기본적으로 단일 인자로 초기화 될 수 있는 생성자는 explicit 로 선언되어야 한다. -> 그렇지 않다면 그렇지 않은 이유를 문서화 해 놓는 것이 바람직하다.
16.2.7 클래스 내 초기화 식
여러 개의 생성자를 사용할 때 초기화 로직이 반복될 수 있다. 이를 해결하기 위해서 다음의 방법을 사용할 수 있다.
기본 인자를 도입
공통적인 초기화 부분을 처리하는 생성자에게 넘기는 방식
멤버 변수에 초기화 식을 추가하는 방식
아래 코드에서 '멤버 변수에 초기화 식을 추가하는 방식 ' 을 구현해 보았는데, 각 생성자가 직접 초기화하지 않아도 d,m,y 는 초기화된다.
16.2.8 클래스 내 함수 정의
클래스 내 정의된 멤버 함수는 인라인 멤버 함수로 정의 된다. -> 즉, 클래스 내 멤버 함수 정의는 작고, 거의 변경되지 않으며, 빈번히 사용되는 함수를 위한 것이다. -> 인라인 멤버 함수로 정의 될 시 함수 호출 비용이 발생하지 않는다.
컴파일러가 멤버 함수의 내용이 적다고 생각되면 인라인으로 아니라면 그냥 멤버 함수의 형태로 사용한다. (선택적이다)
다음의 코드는 동일하게 동작한다.
16.2.9 가변성
16.2.9.1 상수 멤버 함수
const 를 이용해 멤버 함수가 멤버 변수의 값을 변환하지 않도록 선언할 수 있다. -> 멤버 변수를 변환한다면 컴파일 에러가 발생한다.
16.2.9.2 물리적 및 논리적 상수성 (logical constness)
경우에 따라서 멤버 함수가 논리적으로는 const 지만, 여전히 멤버의 값을 변경할 필요가 있을 때가 있다. -> 즉, 사용자에게는 함수가 개체의 상태를 변경하지 않는 것처럼 보이지만, 사용자가 직접적으로 인지할 수 없는 어떤 세부사항이 갱신될 수 있다. -> 이를 가리켜 '논리적 상수성'(logical constness) 이라고 부른다.
이런 표현은 상대적으로 비용이 많이 드는 연산일 수 있다. -> 따라서 Date 의 값이 변경되지만 않는다면 사본을 하나 만들어놓고, 반복되는 요청에 대해 이 사본만 반환하는 방식이 타당할 수 있다.
아래의 코드를 보자
사용자 관점에서 보면 string_rep 은 Date 의 상태를 변경하지 않는다.
반면, mutable 선언을 통해서 아래 cache_valied, cache 는 string_rep() 에서 변경될 수 있다.
16.2.9.3 mutable
멤버 변수를 mutable 로 선언할 수 있는데, 이는 해당 멤버가 const 개체 내에서도 수정될 수 있다는 것을 의미한다.
16.2.9.4 간접 참조를 통한 가변성
어떤 멤버 변수를 mutable 로 선언하는 것은 소규모 개체 표현의 작은 부분에 대해서만 변경이 허용 될 때 가장 적합하다.
좀 더 복잡한 경우에는 변경되는 데이터를 별도에 개체에 넣고 간접적으로 접근하는 방식이 다루기 편하다.
위 코드는 간접 참조를 사용 시 아래와 같이 변경된다.
16.2.10 자기 참조
클래스의 연산들이 연결되도록 하고 싶다면 개체에 대한 참조자를 리턴하면 된다.
아래 코드를 살펴보자
함수 내에서 자기 참조를 리턴하기 위해서는 this* 를 사용할 수 있다. -> this 는 현재 개체의 포인터이고, -> this* 는 현재 개체의 참조이다.
this* 는 다음과 같은 특징을 갖는다.
this 는 우변 값으로 간주되므로, this 의 주소를 얻는다든지 this 에 대입한다는 것은 불가능하다.
대부분 this 의 사용은 암시적이다.
16.2.11 멤버 접근
포인터 변수는 -> 를 통해 멤버(변수, 함수) 에 접근할 수 있고,
참조 변수는 . 를 통해 멤버에 접근할 수 있다.
16.2.12 static 멤버
클래스의 일부이면서, 해당 클래스 개체가 아닌 변수를 static 멤버 라고 부른다.
통상적인 비 static 멤버처럼 개체당 하나씩 사본이 있는 것이 아니라,
static 멤버는 해당 클래스에 정확히 하나의 사본이 존재한다.
마찬가지로 클래스 멤버 접근을 필요로 하는 함수지만, 특정 개체에 대해 호출될 필요가 없는 함수는 static 멤버 함수라고 부른다.
16.2.13 멤버 타입
타입과 타입 별칭은 클래스의 멤버가 될 수 있다.
16.3 구체 클래스
사용자가 구체 타입을 정의할 수 있는 타입을 구체 타입(concrete type), 구체 클래스(concrete class) 라고 부른다. -> 이는 추상 클래스, 인터페이스와 반대되는 개념이다.
구체 클래스는 다음과 같은 특징을 가지고 있다. -> 아래와 같은 특징 덕분에 구체 클래스는 쉬운 개념이 되었고, 컴파일러는 필요한 최적의 코드를 쉽게 생성해 줄 수 있게 되었다.
개체를 스택, 정적 할당 메모리, 다른 개체 내부에 둘 수 있다.
개체를 복사하거나 이동할 수 있다.
이름을 가진 개체를 직접적으로 참조할 수 있다.
성능이 중요한 타입에서는 구체 클래스를 우선 사용하기를 권장한다. -> 컴파일러가 최적의 코드를 쉽게 생성해줄 수 있기에 성능이 좋다.
이렇듯 사용자 정의 타입(클래스, 구조체) 의 정의와 효율적인 사용을 적극적으로 지원하자는 것이 초창지 C++ 의 명확한 목표였다!
-> 늘 그렇듯 평범한 것이 복잡하고 수준 높은 것보다 훨씬 중요하다!
이런 관점에서 더 나은 Date 클래스를 만들어보자
다음은 Date 가 어떻게 사용되는지 보여주는 예제이다.
열거형 클래스의 사용을 통해 명시적으로 값을 표현해주었다는 것을 주의깊게 보자
Last updated