icon안동민 개발노트

생성자와 소멸자


생성자 (Constructor)

 생성자는 객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수입니다. 주로 객체의 초기화를 담당합니다.

 기본 생성자

 매개변수가 없는 생성자를 기본 생성자라고 합니다.

class MyClass {
public:
    MyClass() {
        std::cout << "Default constructor called" << std::endl;
    }
};
 
int main() {
    MyClass obj;  // 기본 생성자 호출
    return 0;
}

 매개변수가 있는 생성자

 객체 생성 시 초기값을 전달받아 초기화할 수 있습니다.

class Person {
private:
    std::string name;
    int age;
public:
    Person(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Person created: " << name << ", " << age << std::endl;
    }
};
 
int main() {
    Person p("Alice", 30);
    return 0;
}

 복사 생성자

 다른 객체를 이용하여 새로운 객체를 초기화할 때 사용됩니다.

class MyClass {
private:
    int* data;
public:
    MyClass(int value) : data(new int(value)) {}
    
    // 복사 생성자
    MyClass(const MyClass& other) : data(new int(*other.data)) {
        std::cout << "Copy constructor called" << std::endl;
    }
    
    ~MyClass() { delete data; }
};
 
int main() {
    MyClass obj1(42);
    MyClass obj2 = obj1;  // 복사 생성자 호출
    return 0;
}

 이동 생성자 (C++ 11)

 리소스의 소유권을 이전할 때 사용됩니다.

class MyClass {
private:
    int* data;
public:
    MyClass(int value) : data(new int(value)) {}
    
    // 이동 생성자
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "Move constructor called" << std::endl;
    }
    
    ~MyClass() { delete data; }
};
 
int main() {
    MyClass obj1(42);
    MyClass obj2 = std::move(obj1);  // 이동 생성자 호출
    return 0;
}

소멸자 (Destructor)

 소멸자는 객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수입니다. 주로 동적으로 할당된 리소스를 해제하는 데 사용됩니다.

class MyClass {
private:
    int* data;
public:
    MyClass() : data(new int(0)) {}
    ~MyClass() {
        delete data;
        std::cout << "Destructor called" << std::endl;
    }
};
 
int main() {
    {
        MyClass obj;
    }  // obj가 범위를 벗어나면 소멸자가 호출됩니다.
    return 0;
}

생성자 초기화 리스트

 생성자에서 멤버 변수를 초기화하는 효율적인 방법입니다.

class Rectangle {
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) : width(w), height(h) {
        std::cout << "Rectangle created with width " << width 
                  << " and height " << height << std::endl;
    }
};

위임 생성자 (C++ 11)

 한 생성자가 같은 클래스의 다른 생성자를 호출할 수 있습니다.

class Person {
private:
    std::string name;
    int age;
public:
    Person(const std::string& n, int a) : name(n), age(a) {}
    Person() : Person("John Doe", 0) {}  // 위임 생성자
    Person(const std::string& n) : Person(n, 0) {}  // 위임 생성자
};

명시적 생성자 (explicit)

 암시적 형변환을 방지하기 위해 사용됩니다.

class MyClass {
public:
    explicit MyClass(int x) {
        std::cout << "MyClass created with " << x << std::endl;
    }
};
 
int main() {
    MyClass obj1(10);  // OK
    // MyClass obj2 = 20;  // 컴파일 에러: 암시적 변환 불가
    return 0;
}

초기화 순서

 멤버 변수의 초기화 순서는 클래스에서 선언된 순서를 따릅니다, 초기화 리스트의 순서와는 무관합니다.

class MyClass {
private:
    int x;
    int y;
public:
    MyClass(int a, int b) : y(b), x(a) {  // x가 먼저 초기화됩니다.
        std::cout << "x: " << x << ", y: " << y << std::endl;
    }
};

가상 소멸자

 상속 관계에서 기본 클래스의 소멸자를 가상으로 선언하여 적절한 소멸자 호출을 보장합니다.

class Base {
public:
    virtual ~Base() {
        std::cout << "Base destructor" << std::endl;
    }
};
 
class Derived : public Base {
public:
    ~Derived() override {
        std::cout << "Derived destructor" << std::endl;
    }
};
 
int main() {
    Base* ptr = new Derived();
    delete ptr;  // 두 소멸자 모두 호출됩니다.
    return 0;
}

실습 : 메모리 관리 클래스 구현

 다음 요구사항을 만족하는 메모리 관리 클래스를 구현해보세요.

  • 동적으로 할당된 정수 배열을 관리
  • 복사 생성자, 이동 생성자, 소멸자 구현
  • 배열 크기 조정 기능 구현
#include <iostream>
#include <algorithm>
 
class DynamicArray {
private:
    int* data;
    size_t size;
 
public:
    // 생성자
    DynamicArray(size_t s = 0) : size(s), data(size ? new int[size]() : nullptr) {
        std::cout << "Constructor called, size: " << size << std::endl;
    }
 
    // 소멸자
    ~DynamicArray() {
        delete[] data;
        std::cout << "Destructor called" << std::endl;
    }
 
    // 복사 생성자
    DynamicArray(const DynamicArray& other) : size(other.size), data(size ? new int[size] : nullptr) {
        std::copy(other.data, other.data + size, data);
        std::cout << "Copy constructor called" << std::endl;
    }
 
    // 이동 생성자
    DynamicArray(DynamicArray&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
        std::cout << "Move constructor called" << std::endl;
    }
 
    // 배열 크기 조정 메서드
    void resize(size_t newSize) {
        int* newData = newSize ? new int[newSize]() : nullptr;
        size_t copySize = std::min(size, newSize);
        std::copy(data, data + copySize, newData);
        delete[] data;
        data = newData;
        size = newSize;
    }
 
    // 접근자 메서드
    int& operator[](size_t index) { return data[index]; }
    const int& operator[](size_t index) const { return data[index]; }
    size_t getSize() const { return size; }
};
 
int main() {
    DynamicArray arr1(5);
    for (size_t i = 0; i < arr1.getSize(); ++i) {
        arr1[i] = i * 10;
    }
 
    DynamicArray arr2 = arr1;  // 복사 생성자 호출
    DynamicArray arr3 = std::move(arr1);  // 이동 생성자 호출
 
    arr2.resize(10);  // 크기 조정
 
    std::cout << "arr2 contents after resize:" << std::endl;
    for (size_t i = 0; i < arr2.getSize(); ++i) {
        std::cout << arr2[i] << " ";
    }
    std::cout << std::endl;
 
    return 0;
}

연습 문제

  1. String 클래스를 구현하세요. 이 클래스는 문자열을 동적으로 할당하고 관리해야 합니다. 복사 생성자, 이동 생성자, 소멸자를 구현하고, 문자열 연결 연산자(+)를 오버로딩하세요.


참고 자료