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

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

 3.2.2 Abstract Types

  complex와 Vector같은 자료형 concrete 자료형이라 합니다. 그들의 표현이 그들의 정의 일부분이기 때문입니다. 곧, 내장 자료형과 닮았죠. 대조적으로, abstract 자료형은 구현의 세부사항으로부터 사용자를 완전히 격리한 것을 말합니다. 이는 표현으로부터 인터페이스를 분리하고 실질적인 지역 변수를 포기함으로써 이룰 수 있습니다.  따라서 우리는 abstract 자료형의 표현에 대해서 아무것도 알 수 없습니다. 반드시 자유 공간에 객체를 할당하고 참조나 포인터를 통해서 그들에게 접근해야 합니다.

  먼저, 우리는 우리의 Vector 클래스를 더 추상화된 버전으로 설계하기 위해 Container라는 클래스의 인터페이스를 정의합니다:

1
2
3
4
5
6
class Container {
public:
    virtual double& operator[](int) = 0; // pure virtual function
    virtual int size() const = 0; // const member function (§3.2.1.1)
    virtual ˜Container() {} // destructor (§3.2.1.2)
};
cs


이 클래스는 차후에 정의될 특정 컨테이너의 순수한 인터페이스입니다. virtual은 "이 클래스로부터 유도된 클래스에서 재정의될 수도 있다"는 의미입니다. (생략) =0은 해당 함수가 pure virtual임을 의미합니다. Container로부터 도출된 클래스는 반드시 이 함수를 정의해야 하죠. 

(생략)


3.2.3 Virtual Functions

  Container의 사용 예를 봅시다:

1
2
3
4
5
6
void use(Container& c)
{
    const int sz = c.size();
    for (int i=0; i!=sz; ++i)
    cout << c[i] << '\n';
}
cs

1
2
3
4
5
void g()
{
    Vector_container vc = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    use(vc);
}
cs

1
2
3
4
5
void h()
{
    List_container lc = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    use(lc);
}
cs

  use()의 c[i]는 어떻게 올바른 operator[]()로 귀결될까요? h()가 use()를 호출할 때, List_container의 operator[]()는 반드시 호출되어야 합니다. 이를 달성하기 위해, Container 객체는 런타임 때 호출할 함수를 올바르게 선택하기 위한 정보를 반드시 포함합니다. 보통은 컴파일러가 가상 함수를, 함수 포인터 테이블의 인덱스로 바꿔주면서 이를 구현하게 됩니다. 이 테이블은 virtual function table이라고 불리며, 간단하게 vtbl이라고도 합니다. 가상 함수들이 있는 각 클래스들은 각자의 가상 함수들을 식별하기 위한 vtbl을 지니고 있습니다. 그림으로 표현하면 다음과 같죠:


  vtbl에 있는 함수는 객체의 크기와 그 자료의 레이아웃을 모르더라도 호출자가 올바르게 사용될 수 있도록 합니다. 호출자의 구현은 오직 Container의 vtbl의 포인터 위치와 각 가상 함수의 인덱스만 알면 됩니다. 가상 호출의 메커니즘은 "보통의 함수 호출(normal function call)" 메커니즘만큼 효율적으로 만들 수 있습니다. 단 하나의 포인터와 가상 함수들을 저장할 vtbl 공간만 마련해주면 되죠.


3.2.4 Class Hierarchies

(생략)


3.3. Copy and Move

  기본적으로 객체는 복사될 수 있습니다. 이는 사용자 정의 자료형과 내장 자료형에 해당되는 이야기입니다. 복사의 기본적 의미는 memberwise copy입니다: 각 멤버에 대해 복사하는 것이죠. 예를 들어 3.2.1.1의 complex를 참고해보죠:

1
2
3
4
5
6
7
void test(complex z1)
{
    complex z2 {z1}; // copy initialization
    complex z3;
    z3 = z2; // copy assignment
    // ...
}
cs

  할당과 초기화로 인해 z1, z2, z3은 같은 값을 갖게 되었습니다. 우리가 클래스를 설계할 때,우리는 반드시 객체가 복사가 될 것인지, 어떻게 될 것인지에 대해 고려해 보아야 합니다. 간단한 concrete 자료형의 경우, memberwise 복사는 보통 올바른 복사의 의미와 동일합니다. Vector와 같이 복잡한 concrete 자료형의 경우, memberwise 복사는 올바른 복사의 의미가 아닐 수도 있으며, abstract 자료형의 경우 거의 대부분 아닙니다.


3.3.1 Copying Containers

  클래스가 resource handle인 경우, 즉 포인터를 통해서 접근되는 객체를 담당하는 경우, 기본 memberwise 복사는 곧 재난과 같습니다. memberwise 복사는 resource handle의 불변성을 해칠 것입니다. 가령, 기본 복사는 원본과 똑같은 요소를 참조하고 있는 Vector의 복사를 내놓을 것입니다:

1
2
3
4
5
6
void bad_copy(Vector v1)
{
    Vector v2 = v1; // copy v1’s representation into v2
    v1[0] = 2; // v2[0] is now also 2!
    v2[1] = 3; // v1[1] is now also 3!
}
cs

  v1이 4개의 요소를 지녔다고 생각해보죠. 결과를 그림으로 나타내면 다음과 같을 것입니다:


  다행히도, Vector가 소멸자를 지녔다는 사실은, 기본(memberwise) 복사의 의미가 올바르지 않고 컴파일러는 이러한 부분에 대해 최소한의 경고를 주어야 한다는 강력한 힌트를 제공합니다. 우리는 더 좋은 복사의 의미를 정의할 필요가 있습니다.

  클래스의 객체를 복사하는 것은 두 멤버로 정의됩니다: 복사 생성자(copy constructor)와 복사 대입(copy assignment)입니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Vector {
private:
    double∗ elem; // elem points to an array of sz doubles
    int sz;
public:
    Vector(int s); // constructor: establish invariant, acquire resources
    ˜Vector() { delete[] elem; } // destructor: release resources
    Vector(const Vector& a); // copy constr uctor
    Vector& operator=(const Vector& a); // copy assignment
    double& operator[](int i);
    const double& operator[](int i) const;
    int size() const;
};
cs

  Vector를 위한 복사 생성자의 적절한 정의는 필요한 요소들 수만큼의 공간을 할당하고 각 요소들을 복사함으로써 각각의 요소 복사본을 복사된 Vector가 지니게 하는 것입니다.

1
2
3
4
5
6
7
Vector::Vector(const Vector& a) // copy constructor
    :elem{new double[sz]}, // allocate space for elements
    sz{a.sz}
{
    for (int i=0; i!=sz; ++i) // copy elements
        elem[i] = a.elem[i];
}
cs

  v2=v1의 결과는 다음과 같이 표현될 것입니다:


  물론, 복사 대입 연산자 역시 필요할 것입니다:

1
2
3
4
5
6
7
8
9
10
Vector& Vector::operator=(const Vector& a) // copy assignment
{
    double∗ p = new double[a.sz];
    for (int i=0; i!=a.sz; ++i)
        p[i] = a.elem[i];
    delete[] elem; // delete old elements
    elem = p;
    sz = a.sz;
    return ∗this;
}
cs

  this는 멤버 함수에서 사전에 정의되어 있으며, 멤버 함수를 호출한 객체를 가리킵니다. 

  클래스 X를 위한 복사 생성자와 복사 대입 연산자는 보통 const X& 타입의 인자를 받기 위해서 선언됩니다.

댓글

이 블로그의 인기 게시물

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

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

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