{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[C++] The C++ Programming Language: 3. A Tour of C++: Abstraction Mechanism (1)

3. A Tour of C++: Abstraction Mechanisms


3.1 Introduction

  이 챕터에서는 여러분들에게 C++가 추상화와 자원 관리를 위해 지원하는 것들에 대해 간단하게 알려주는 데에 집중할 것입니다. 새로운 자료형을 정의하고 사용하는 방법을 약식으로 제공하죠. 특히, 기본 속성들, 구현 기법들, concrete 클래스추상(abstract) 클래스, 클래스 계층에 사용되는 언어 기능들에 대해 알아볼 것입니다. 템플릿은 자료형을 인자화(parameterizing)하고, (기타)자료형 및 알고리즘에 관련된 알고리즘을 위한 메커니즘입니다. 사용자가 정의하거나 내장된 자료형의 연산은 함수, 때로는 템플릿 함수(template functions)함수 객체(function objects)로 일반화되어 나타납니다. 이들은 객체지향 프로그래밍과 제네릭 프로그래밍을 지원하는 언어 기능들입니다.


3.2 Classes

  C++의 중앙에는 역시 클래스가 있습니다. 클래스는 프로그램의 코드 내에서 특정한 개념을 표현하는 사용자 정의 자료형입니다. (생략)

  기본 자료형, 연산자, 구문들 너머의 모든 언어의 기능들은 클래스를 좀 더 좋게 정의하고 쉽게 사용할 수 있도록 돕기 위해 존재합니다. "좀 더 좋게"라는 것은, 유지보수 하기에 좋고, 효율적이고, 우아하고, 쓰기 쉽고, 읽기 쉽고, 추론하기 쉽다는 의미입니다. 대부분의 프로그래밍 기법은 특정한 종류의 클래스들을 설계하고 구현하는 데에 의존합니다. 프로그래머의 요구와 취향은 매우 다양하죠. 곧, 클래스는 확장성을 위해 존재합니다. 중요한 세 가지 종류의 클래스를 살펴보도록 하죠:

  • Concrete 클래스
  • 추상(Abstract) 클래스
  • 클래스 계층 내의 클래스

  수많은 유용한 클래스들은 이 세 종류에 속하게 됩니다. 훨씬 더 많은 클래스들이 이 세 가지 유형의 변형으로 볼 수 있거나 이러한 기법들을 혼합해 구현된 것들로 볼 수 있습니다.


3.2.1 Concrete Type

  concrete 클래스의 기본 개념은 "마치 내장 자료형처럼" 행위한다는 것입니다. 예를 들어, 복소수 자료형과 무한 정밀도(infinite-precision) 정수는 마치 내장 자료형 int와 비슷하죠. 그들만의 의미와 연산의 집합이 다르다는 것만 빼면 말이죠. 비슷하게, vector와 string은 내장 자료형인 배열과 비슷합니다. 역시 이들이 좀 더 좋게 행위한다는 것만 빼면 말이죠.

  concrete 자료형 특성의 본질은, 해당 자료형의 표현이 정의의 일부라는 것입니다. vector와 같은 중요한 사례들에서, 그 표현은 어딘가에 저장된 하나 이상의 자료들의 포인터이지만 이는 concrete 클래스의 각 객체에 존재하죠. 이를 통해 시간과 공간을 효율적으로 활용할 수 있도록 만듭니다. 특히, 이는 다음을 할 수 있게 만듭니다:

정적 할당 메모리인 스택 및 다른 객체에 concrete 자료형의 객체를 놓을 수 있습니다.

객체를 직접 참조합니다.(포인터 및 간접 참조를 통하지 않습니다)

객체를 즉시, 완전히 초기화할 수 있습니다.

객체를 복사할 수 있습니다.

  표현은 private이 될 수 있고 멤버 함수로만 접근할 수 있도록 할 수 있습니다. 곧, 표현법이 바뀔 경우 사용자는 재컴파일을 해야 합니다. 이는 concrete 자료형이 내장 자료형처럼 행위하기 위해 지불해야 하는 비용입니다. 자주 바뀌지 않는 자료형, 충분히 명료하고 효율적으로 지역 변수를 제공해야 하는 부분에 사용하기 적절합니다. 유연성을 증대시키기 위해, concrete 자료형은 주요한 부분을 자유 공간(동적 메모리, 힙)에 유지시킬 수 있습니다. 그리고 클래스 객체 자체적으로 그들을 통해 접근하는 것이죠. 이것이 vector와 string이 구현된 방법입니다. 이들은 신중하게 제작된 인터페이스들과 함께 자원을 다룰 수 있죠.


3.2.1.1 An Arithmetic Type

고전적인 사용자 정의 수학적 자료형은 complex입니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class complex {
    double re, im; // representation: two doubles
public:
    complex(double r, double i) :re{r}, im{i} {} // construct complex from two scalars
    complex(double r) :re{r}, im{0} {} // construct complex from one scalar
    complex() :re{0}, im{0} {} // default complex: {0,0}
    double real() const { return re; }
    void real(double d) { re=d; }
    double imag() const { return im; }
    void imag(double d) { im=d; }
    complex& operator+=(complex z) { re+=z.re , im+=z.im; return ∗this; } // add to re and im
                                                                            // and return the result
    complex& operator−=(complex z) { re−=z.re , im−=z.im; return ∗this; }
    complex& operator∗=(complex); // defined out-of-class somewhere
    complex& operator/=(complex); // defined out-of-class somewhere
};
cs


이는 표준-라이브러리 complex를 간단화한 버전입니다. 클래스 정의 자체에는 표현에 접근하는 연산들만 포함합니다. 표현은 간단하고 관습적입니다. 현실적인 이유로, 포트란(Fortran)이 50년 전에 제공된 것들과 호환이 되어야 하기 때문입니다. 논리적인 요구사항 외에도, complex는 반드시 효율적이어야 합니다. 그렇지 않으면 사용되지 않을 것이기 때문입니다. 곧, 간단한 연산은 반드시 inline이어야 합니다. 간단한 연산(생성자, +=, imag() 등)은 생성된 기계 코드에서 함수 호출 없이 구현되어야 합니다. 아주 강력한 complex는 inlining을 적절하게 사용해 신중히 구현된 것입니다.

  생성자는 인자 없이 호출될 수 있는 기본 생성자입니다. complex()는 complex의 기본 생성자입니다. 기본 생성자를 정의함으로써 여러분은 자료형에 초기화되지 않은 변수가 있을 가능성을 제거할 수 있습니다.

  실수부와 허수부를 반환하는 함수에 명시되어 있는 const는 이 함수들이 각 객체를 변경하지 못하게 만듭니다.

  수많은 유용한 연산들은 complex의 표현에 직접 접근할 필요가 없습니다. 따라서 클래스 정의와 분리되어 정의될 수 있습니다:

1
2
3
4
5
complex operator+(complex a, complex b) { return a+=b; }
complex operator−(complex a, complex b) { return a−=b; }
complex operator−(complex a) { return {−a.real(), −a.imag()}; } // unary minus
complex operator∗(complex a, complex b) { return a∗=b; }
complex operator/(complex a, complex b) { return a/=b; }
cs

  이곳에서는 인자가 복사되어 값으로 전달된다는 사실을 이용해, 호출자의 복사본과 상관 없이 인자를 변경할 수 있고 그 결과를 반환할 수 있습니다.

  ==와 !=의 정의는 다음과 같습니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
bool operator==(complex a, complex b) // equal
{
    return a.real()==b.real() && a.imag()==b.imag();
}
 
bool operator!=(complex a, complex b) // not equal
{
    return !(a==b);
}
 
complex sqr t(complex);
 
// ...
cs


  클래스 colmplex는 다음과 같이 사용될 수 있죠:


  컴파일러는 complex를 수반하는 연산자를 적절한 함수 호출로 변환해줍니다. 가령, c!=b는 operator!=(c, b)로, 1/a는 operator/(complex{1},a)로 말이죠.

  사용자 정의 연산자(overloded operators)는 조심스럽고, 관습에 따라 사용되어야 합니다. 문법은 언어에 의해 고정되어 있으므로, 단항 연산자 /는 구현할 수 없습니다. 또한, 내장 자료형 연산자의 의미를 바꾸는 것 역시 불가능합니다. 가령 int의 +를 빼기로 바꿀 순 없죠.


3.2.1.2 A Container

  Container는 요소들의 컬렉션을 쥐고있는 객체입니다. 2.3.2에서 소개된 Vector는 container입니다. vector는 double의 container가 되기에도 불합리하지 않습니다: 쉽게 이야기해서, 불변을 설정하고, 범위 확인을 지원하고, size()를 제공해 각 요소들을 순회할 수 있도록 합니다. 하지만, 치명적인 결함이 하나 있죠: new를 이용해 요소를 할당하지만, 할당을 해제하지 않습니다. 이는 C++가 가비지 컬렉터를 위한 인터페이스를 정의하더라도 좋은 아이디어가 아닙니다. 가비지 컬렉터가 새 객체에 가용한, 미사용되는 메모리를 만들 수 있다는 보장이 없기 때문입니다. 몇몇 환경에서 여러분들은 컬렉터를 슬 수 없습니다. 가끔 여러분들은 객체의 소멸을 논리적 혹은 성능의 이유로 인해 엄격히 다뤄야할 때가 있습니다. 우리는 생성자로부터 할당된 메모리를 완전히 할당 해제할 수 있는 메커니즘이 필요합니다: 소멸자입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Vector {
private:
    double∗ elem; // elem points to an array of sz doubles
    int sz;
public:
    Vector(int s) :elem{new double[s]}, sz{s} // constructor: acquire resources
    {
        for (int i=0; i!=s; ++i) elem[i]=0; // initialize elements
    }
    ˜Vector() { delete[] elem; } // destructor: release resources
    double& operator[](int i);
    int size() const;
};
cs

  소멸자의 이름은 연산자 이름 앞에 ~를 붙이고, 클래스의 이름을 씁니다. (생략)

  

  생성자는 각 요소들을 적절히 할당하고 초기화합니다. 소멸자는 요소들을 소멸시킵니다. 이 handle-to-data model은 객체의 생명주기동안 크기가 변할 수 있는 자료를 관리할 때 사용합니다. 생성자로부터 자원을 얻고 소멸자로부터 자원을 소멸시키는 기법은 Resource Acquisition Is Initialization, RAII로도 알려져 있습니다. 클라이언트 코드에 드러난 new 연산자는 피해야만 합니다. 좀 더 잘 정의된 추상화에 숨기는 것이 더 좋죠. 비슷하게, 클라이언트 코드에 드러난 delete 연산자는 피해야 합니다. 이러한 new와 delete 키워드의 사용을 피하는 것은 좀 더 오류에 강하고 메모리 누수를 피할 수 있는 길이 됩니다.


3.2.1.3 Initializing Containers

(생략)


댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기