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