티스토리 뷰

추가되는 사항

    • 명시적으로 초기화, 소멸 함수를 정의하지 않고 class의 객체가 생성, 소멸할 때 호출되는 함수를 활용한다.

    • Raw pointer를 다루는 class가 복사될 때 pointer 해제의 문제를 해결한다.

    • 같은 class의 값으로 선언과 초기화를 동시에 할 때 호출되는 복사 생성자를 정의한다.

목표

    • Constructor와 destructor를 정의한다.

    • 객체의 복사 과정을 이해한다.

    • Copy constructor를 정의한다.

디자인

    • 다음 생성자가 추가된다.
      • vector()
      • vector(const vector& v)

    • 소멸자가 추가된다.
      • ~vector()

    • 다음 멤버 함수가 삭제된다.
      • initialize()
      • finalize()


생성자와 소멸자

class Object {
    private:
    int data;

    public:
    Object() {
        std::cout << "Object() called" << std::endl;
    }

    ~Object() {
        stc::cout << "~Object() called" << std::endl;
};

int main() {
    std::cout << "Pre-initialize" << std::endl;
    Object o;
    std::cout << "Initialized" << std::end;

    return 0;
}

실행결과

Pre-initialize
Object() called
Initialized
~Object() called
    • 생성자는 객체가 초기화될 때 호출되고 소멸자는 스코프가 종료될 때(닫는 중괄호를 만날 때) 호출된다.

    • if문 for문 또는 스코프 내에 사용자가 임의로 추가한 스코프 또한 소멸자가 호출되는 시점이다.
      int main() {
          /* blah ... */
          {
              Object o; // Constructor called
          } // Destructor called
          /* blah ... */
      }
      


복사 생성자의 필요성

class Object { private: int* data; public: Object() { data = new int[10]; } ~Object() { delete[] data; } }; int main() { Object o1; Object o2 = o1; } // Error: double free

    • 각 class는 미리 정의된 복사 생성자가 없다면 컴파일러에 의해 추가되는 기본 복사 생성자에 의존한다.
      기본 복사 생성자는 다음과 같이 구현되어있다.
      class Object() {
          private:
          int* data;
          int i1;
          char c1;
          int i2;
          float f1;
          char c2;
      
          public:
          Object(const Object& o) {
              data = o.data;
              i1 = o.i1;
              c1 = o.c1;
              i2 = o.i2;
              f1 = o.f1;
              c2 = o.c2;
          }
      };
      

      이렇듯 모든 멤버 변수를 복사할 객체로부터 있는 그대로 복사하므로 raw pointer는 그대로의 주소만 복사된다.
      따라서 raw pointer를 다루는 class는 기본 복사 생성자를 그대로 사용할 경우 소멸자가 같은 포인터를 해제하여 double free 에러가 발생한다.

    • Raw pointer를 다루는 class는 다음과 같이 복사 생성자를 정의할 수 있다.
      class Object() {
          /* 중략 */
          public:
          Object(const Object& o) {
              data = new int[10];
              std::copy(o.data, o.data + 10, data);
          }
      };
      

      이 경우 더 이상 복사된 객체끼리 같은 포인터를 가리키지 않고 각자 할당된 공간을 갖게 되어 따로 소멸자가 호출되어도 문제가 없다.

    • 복사 생성자의 인자값은 const 참조값인데, 복사자체는 인자값을 수정할 수 없어야하므로 const로 정의되며 참조는 호출시 모든 멤버 변수가 복사되어 연산이 두 번 이루어지지 않게 주소만 넘기기 위함이다. 이는 복사 생성자의 규칙이므로 지키도록 한다.


구현

#include <iostream>
#include <algorithm>

constexpr int INITIAL_SIZE = 10;

class vector {
    private:
    int* data;
    int capacity;
    int length;
    
    bool ensure_capacity(int to_add) const {
        return length + to_add < capacity;
    }
    
    void increase_capacity() {
        auto tmp = data;
        data = new int[capacity * 2];
        std::copy(tmp, tmp + length, data);
        delete[] tmp;
        capacity *= 2;
    }
    
    public:
    vector() {
        data = new int[INITIAL_SIZE];
        capacity = INITIAL_SIZE;
        length = 0;
    }
    
    vector(const vector& v) {
        data = new int[v.capacity];
        capacity = v.capacity;
        length = v.length;
        std::copy(v.data, v.data + v.length, data);
    }
    
    ~vector() {
        delete[] data;
    }
    
    void add(int element) {
        if (!ensure_capacity(1))
            increase_capacity();
        
        *(data + length++) = element;
    }
    
    int get(int index) const {
        return *(data + index);
    }
    
    int set(int index, int element) {
        auto tmp = *(data + index);
        *(data + index) = element;
        return tmp;
    }
    
    int remove(int index) {
        auto tmp = *(data + index);
        auto tail_length = length - index - 1;
        auto tail = new int[tail_length];
        std::copy(data + index + 1, data + length, tail);
        std::copy(tail, tail + tail_length, data + index);
        delete[] tail;
        length--;

        return tmp;
    }
    
    void print() const {
        std::cout << '{';
        for (int i = 0; i < length; i++)
            std::cout << *(data + i) << ((i < length - 1) ? ", " : "");
        std::cout << '}' << std::endl;
    }
};


댓글
댓글쓰기 폼