Priv's Blog

클래스 본문

Dev. Study Note/C++

클래스

Priv 2023. 12. 27. 15:37


 

 

1. 클래스

데이터와 연산을 분리하면 데이터를 다양한 방식으로 활용할 수 있다는 장점이 있습니다.

클래스는 사용자가 메모리 표현에 접근하지 못하게 하여 타입을 더 쉽게 사용하고, 데이터를 일관되게 사용하여 메모리 표현을 보다 쉽게 개선하기 위해 활용됩니다.

이를 위해서는 모두가 접근해 사용할 수 있는 타입의 인터페이스와 외부에서는 접근할 수 없도록 제한하는 타입의 구현을 분리해야 하는데, 여기서 클래스가 등장합니다.

클래스는 일련의 멤버를 포함하며, 데이터나 함수, 타입을 멤버로 가질 수 있습니다.

인터페이스는 외부에서의 접근을 허용하므로 public으로 정의되며, private 멤버는 인터페이스를 통해서만 접근이 가능합니다.

class Vector {
public:
    Vector(int s) : elem {new double[s]}, sz{s} {}
    double& operator[](int i) { return elem[i]; }
    int size() { return sz; }
private:
    double* elem;
    int sz;
};
Vector v(6);

 


 

2. 클래스와 구조체

C++에서 사용되는 클래스와 구조체는 근본적인 차이가 없습니다.

구조체(struct)는 멤버가 기본적으로 public인 클래스일 뿐입니다.

즉, 구조체에도 생성자를 포함한 멤버 함수를 정의할 수 있다는 것입니다.

 


 

3. 공용체

공용체(union)는 모든 멤버가 같은 메모리 주소에 할당되는 구조체입니다.

공용체의 크기는 가장 큰 멤버의 크기와 같기 때문에 공용체의 멤버 중 실제로 값을 가지는 멤버는 1개입니다.

struct Entry {
    string name;
    Type t;
    Node* p;
    int i;
};

void f(Entry* pe) {
    if (pe -> t == num) {
        cout << pe -> i;
    }
}

위 코드를 보면, 멤버 p와 i는 동시에 사용되지 않기 때문에 메모리 낭비가 발생합니다.

이러한 경우에 공용체를 사용하면 메모리를 절약할 수 있습니다.

union Value {
    Node *p;
    int i;
};

struct Entry {
    string name;
    Type t;
    Value v;
};

void f(Entry* pe) {
    if (pe -> t == num) {
        cout << pe -> v.i;
    }
}

다만 공용체가 어떤 종류의 값을 가지는지 언어 차원에서 확인하지 않기 때문에 프로그래머가 직접 관리해야 합니다.

공용체와 유사한 기능을 제공하는 variant라는 표준 라이브러리 타입이 존재합니다.

이를 사용하면 위와 동일한 기능을 보다 편리하게 구현할 수 있습니다.

대부분의 경우 union보다는 variant를 사용하는 것이 더 안전합니다.

 


 

4. 구체 클래스 (구체 타입)

구체 클래스는 '마치 내장 타입처럼' 동작하는 것을 기본으로 하는 클래스입니다.

복소수 타입, 무한 정밀도 정수는 동작 방식과 연산자 집합을 포함한다는 것을 제외하면 int 내장 타입과 매우 유사합니다.

vector와 string도 더 나은 방식으로 동작한다는 점을 제외하면 내장 배열과 별 차이가 없습니다.

구체 타입은 타입 정의의 일부로 메모리 표현이 존재한다는 것이 중요한 특징입니다.

즉, 해당 타입의 정의에 따라 메모리에 어떻게 표현되는지를 명확하게 정의할 수 있습니다.

컴파일 단계에서 변수가 객체가 어떤 타입인지를 정확하게 알 수 있기 때문에 그에 맞는 적합한 메모리 레이아웃을 결정할 수 있게 되는 겁니다.

이러한 특징은 시공간 측면에서 최적화에 유리하다는 장점이 있습니다.

 

4.1. 산술 타입

complex는 대표적인 사용자 정의 산술 타입입니다.

class complex {
private:
    double re, im;
public:
    complex(double r, double i) : re{r}, im{i} {}
    complex(double r) : re{r}, im{0} {}
    complex() : re{0}, im{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;
    }

    complex& operator *= (complex);
    complex& operator /= (complex);
};

위 코드는 단순화한 complex 클래스입니다.

사용법은 다음과 같습니다.

void f (complex z) {
    complex a {2.3};
    complex b {1 / a};
    complex c {a + z * complex{1, 2.3}};
    
    if (c != b) {
        c = -(b / a) + 2 * b;
    }
}

컴파일러는 complex가 관련된 코드를 적절한 함수 호출로 변환합니다.

c != b는 operator != (c, b)를 의미하며, 1 / a는 operator / (complex{1}, a)를 의미합니다.

사용자 정의 연산자는 주의해서 관례에 맞게 사용해야 합니다.

단항 ' / '는 정의할 수 없으며, 내장 타입의 연산자는 다른 의미로 변경할 수 없습니다.

즉, int에 대한 + 연산자가 뺄셈을 계산하도록 만들 수는 없습니다.

 

4.2. 소멸자

소멸자는 생성자가 할당한 메모리가 해제됨을 보장하는 메커니즘을 의미하며, ~ 연산자를 붙여서 나타냅니다.

C++에도 GC(Garbage Collector)가 존재하긴 하지만, 사용되지 않는 메모리를 새로운 객체가 쓸 수 있게 보장하지는 않기 때문에 직접 메모리를 해제하는 것이 권장됩니다.

class Vector {
private:
    double* elem;
    int sz;
public:
    Vector(int s) : elem{new double[s]}, sz{s} {
        for (int i = 0; i != s; ++i) {
            elem[i] = 0;
        }
    }
    
    ~Vector() { 
        delete[] elem;
    }
    
    double& operator[](int i);
    int size() const;
};

Vector의 생성자는 new 연산자를 이용해 메모리를 할당하고, 소멸자는 delete[] 연산자를 사용해 메모리를 해제하는 정리 작업을 수행합니다.

여기서 delete는 개별적인 객체의 해제를, delete[]는 배열을 해제함을 의미합니다.

이러한 모든 작업은 Vector 사용자의 개입 없이 수행됩니다.

이 때문에 사용자는 Vector를 내장 타입을 만들고 사용하듯이 다룰 수 있습니다.

생성자는 요소를 할당하고, Vector의 멤버를 초기화합니다.

소멸자는 요소를 해제하고, 메모리에서 사용하지 않는 객체를 제거하여 다른 객체가 메모리를 받을 수 있도록 정리합니다.

이처럼 데이터의 크기가 객체의 생애에 걸쳐 변화하는 경우에는 데이터 핸들 모델을 이용해 데이터를 관리하는 것이 일반적입니다.

생성자에서 자원을 획득, 소멸자에서 자원을 해제하는 기법을 '자원 획득이 곧 초기화'(RAII: Resource acquisition is initialization)이라고 부릅니다.

이러한 기법을 사용하면 할당을 수행하는 일반적인 코드를 잘 정의된 추상화 구현 속에 파묻어 버리는 일을 피할 수 있습니다.

 


 


수고하셨습니다!


 

'Dev. Study Note > C++' 카테고리의 다른 글

2D Array Row, Column  (0) 2024.01.23
friend 키워드  (0) 2024.01.10
구조화된 바인딩  (0) 2023.12.24
C++의 하드웨어 대응  (0) 2023.12.17
auto 타입을 사용할 '특별한 이유'  (0) 2023.12.17
Comments