프로그래밍 이야기

A Tour of C++ : 4장 클래스

원소랑 2019. 10. 11. 22:38

.

.

4. 클래스

4.1 소개

4~7 장은 C++의 추상화 지원, 자원 관리 훑어봄

- 4장 : 새로운 타입(사용자 정의 타입) 정의하고 사용방법 가볍게.

구체 클래스(Concrete class),

추상 클래스(abstract classes),

클래스 계층 구조(class hierarchies) 기본 속성, 구현 기법, 언어 기능

- 5장 : 생성자, 소멸자, 대입 연산 등. C++ 특별한 의미 연산자 설명. 객체의 생애 주기와 자원관리 지원 방법.

- 6장 : 타입 파라미터화, 타입 알고리즘 파라미터화= 템플릿(template). 템플릿 함수, 함수 객체로 일반화

- 7장 : 제네릭 프로그래밍 개념, 기법, 언어 기능.템플릿 인터페이스 표현, 설계 가이드 컨셉 정의, 사용 방법.

일반적이고 유연한 인터페이스 명시 = 가변 템플릿(variadic templates)

위 언어 기능들은 “객체지향 프로그래밍”, “제네릭 프로그래밍” 스타일 지원.

8~15장은 표준 라이브러리 예제와 사용법.

C++ 의 핵심 언어 기능 = 클래스.

기본적인 타입, 연산자, 구문 등의 모든 핵심적인 언어 기능은 더 나은 클래스를 정의하고 더 쉽게 사용하기 위해 존재한다.

세 가지 기본적인 클래스만 다룬다.

구체 클래스, 추상 클래스, 클래스 계층 구조 내의 클래스

위 세 종류가 아니면 간단한 변형이거나 조합 구현.

4.2 구체 타입 (Concrete classes)

기본 아이디어 = 마치 내장 타입처럼 동작. vector 나 string

특징 = 타입 정의의 일부로 메모리 표현이 존재. 시간, 공간 측면 최적화 구현 가능.

- 구체타입 객체가 정적 할당된 메모리인 스택, 다른 객체 안에 위치

- 포인터/참조 거치지 않고 객체 직접 참조

- 객체를 즉시/완벽히 초기화

- 객체 복사/이동

메모리 표현이 바뀌면 컴파일 다시. 유연성을 위해 구체타입 메모리 표현의 중요 부분은 동적 메모리(힙)에 저장. 접근할 수 있게. vector, string 이 세심하게 만들어진 인터페이스를 갖춘 자원 핸들로 생각할 수 있음.

4.2.1 산술 타입

대표적인 예 = complex

메모리 표현 = double 두 개. 메모리 표현 접근을 필요로 하는 연산만 포함. 단순하고 관례적. (오버로드된 오퍼레이터들) 간단 연산은 인라인(함수 호출 없이)

4.2.2 컨테이너

요소의 모음을 저장하는 객체. Vector, 생성자 소멸자를 정의해서 내부에 사용자 개입 없이 내장 타입처럼 사용. 생성자에서 요소를 할당하고 소멸자에서 해제. 자원 획득이 곧 초기화 (Resource Acquisition Is Initialization) RAII. 메모리 누수를 피할 수 있다.

4.2.3 컨테이너 초기화

초깃값 목록 생성자.

class Vector {

public;

Vector( std::initializer_list<double> );

}

Vector::Vector( std::initialize_list<double> lst) // 초깃값 목록 초기화

:elem{ new double[list.size()] }, sz{ static_cast<int>( lst.size() ) }

{

copy( lst.begin(), lst.end(), elem );

}

/////

Vector v1 = {1.23, 3.45, 6, 7.8 };

4.3 추상 타입 (Abstract Type)

사용자에게 상세 구현을 완전히 감춘다. 추상 타입을 지역 변수로 사용할 수 없다. 참조나 포인터로 접근.

예, Vector 의 추상화 버전 Container 클래스.

class Container {

public:

virtual double& oprator[](int) = 0;

virtual int size() const = 0;

virtual ~Container() {}

}

virtual 로 가상함수, virtual ~ = 0; 로 순수 가상 함수. 파생 클래스가 반드시 구현해야 함. 사용자는 구현을 전혀 모른 채로 Container 인터페이스를 사용. 다른 여러 클래스에 대한 인터페이스를 제공하는 클래스를 “다형 타입(Polymorphic type)” 이라고 함.

데이터가 없으니 생성자도 없. 소멸자는 virtual로, 파생 클래스가 구현하도록. 슈퍼클래스 Container 의 파생 클래스, 서브클래스인 Vector_container 는 멤버를 오버라이드(override) 해서 구현. 소멸자는 암묵적으로 호출.

4.4 가상 함수

다형화된 슈퍼클래스의 메소드를 호출할 때, 어떤 오버라이드된 구체 메소드가 호출될지를 찾으려면?

가상함수의 이름 -> 함수 포인터로 구성 테이블 인덱스로 변환, 가상 함수 테이블(virtual function table, vtbl) 포함해야 함. 가상함수 포함 객체마다 포인터 하나 필요, 클래스마다 vtbl 하나 필요.

4.5 클래스 계층 구조

파생 구조대로 클래스 집합 표현한 것. 화살표로 상속 관계를 표현.

새로운 파생 클래스를 만들 때 데이터 멤버, 연산 을 추가해서 유연성을 얻을 순 있지만, 혼란과 엉터리 설계로 이어질 수도 있다.

4.5.1 계층 구조의 장점

이점 두 가지

- 인터페이스 상속 : 기반 클래스를 사용하는 곳에서 파생 클래스를 대신 사용 가능 (Container, Shape 등)

- 구현 상속 : 파생 클래스 구현이 단순해짐 (Smiley에서 Circle의 생성자와 Circle::draw()를 사용 )

객체 생성 시에는 생성자를 이용해 (기반 클래스부터) Bottom up 생성. 객체 소멸시에는 소멸자를 이용해 (파생 클래스부터) Top down 소멸.

4.5.2 계층 구조 탐색

특정 파생클래스에서만 제공하는 멤버를 써야할 때.

dynamic_cast 를 이용해서 특정 파생클래스가 맞는지 포인터 체크. 아니라면 nullptr 반환.

참조의 경우 dynamic_cast 로 bad_cast 예외를 던진다. try-catch 필요.

dynamic_cast 자제하는 코드가 더 깨끗하지만, 감춰진 타입 정보를 복원해야만 하는 경우도 있다.

dynamic_cast 연산을 ‘~의 종류’(is kind of 혹은 is,’ 연산이라고 함.

4.5.3 자원 누수 피하기

세 가지 실수의 위험

- Simley 구현자가 mouth 를 delete 하는데 실패할 수 있다

- read_shape() 의 사용자가 반환된 포인터를 delete 하는데 실패할 수 있다.

- Shape 의 포인터를 저장하는 컨테이너의 소유자가 컨테이너에 포함된 객체를 delete 하는 데 실패할 수 있다.

예전 방식의 포인터’를 이용한 소유권 관리는 위험함. new 결과를 ‘벌거벗은 포인터’에 대합하면 문제 유발.

객체를 삭제할 필요가 있다면 Plain pointer 대신 unique_ptr(표준 라이브러리) 사용.

unique_ptr 이 스코프를 벗어날 때 관리되는 객체를 delete 한다.

void user()

{

vector<unique_ptr<Shape>> v;

while( cin )

v.push_back( read_shape(cin) );

draw_all( v );

rotate_all( v, 45 );

} // 모든 Shape 가 암묵적으로 소멸됨.

4.6 조언

[2] 구체 타입은 가장 간단한 종류의 클래스. 가능한 복잡한 클래스나 평점한 데이터 구조보다 구체 타입을 사용. 4.2

[4] 성능이 중요한 곳은 클래스 계층구조보다 구체 클래스. 4.2

[6] 클래스 메모리 표현에 직접 접근하는 함수는 반드시 멤버 함수로. 4.2.1

[7] 연산자는 주로 관례적 사용 방식 흉내 내는 데 사용. 4.2.1

[8] 대칭적 연산(클래스와 클래스간 연산)은 멤버가 아닌 함수로.

[11] Plain new/delete 연산을 피하자. 4.2.2

[13] 컨테이너 클래스에는 초깃값 목록 생성자를 만들자. 4.2.3

[16] 일반적으로 추상클래스에서는 생성자 불필요

[20] 클래스 계층 구조를 설계할 때, 구현 상속과 인터페이스 상속을 구별하자. 4.5.1

[24] new/delete 실수를 피하기 위해 unique_ptr, shared_ptr 사용하자. 4.5.3

​.

.

728x90
반응형