생성자와 소멸자

2025. 3. 28. 00:00Programming Languages/C++

5.3 생성자와 소멸자

5.3.1 생성자(Constructor)

생성자는 객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수로, 객체를 초기화하는 역할을 합니다.

기본 생성자(Default Constructor)

class Time {
private:
    int hour;
    int minute;
    int second;
    
public:
    // 기본 생성자
    Time() {
        hour = 0;
        minute = 0;
        second = 0;
    }
    
    void display() const {
        std::cout << hour << ":" << minute << ":" << second << std::endl;
    }
};

int main() {
    Time t;  // 기본 생성자 호출
    t.display();  // 출력: 0:0:0
    
    return 0;
}

매개변수가 있는 생성자(Parameterized Constructor)

class Time {
private:
    int hour;
    int minute;
    int second;
    
public:
    // 기본 생성자
    Time() {
        hour = 0;
        minute = 0;
        second = 0;
    }
    
    // 매개변수가 있는 생성자
    Time(int h, int m, int s) {
        hour = h;
        minute = m;
        second = s;
    }
    
    void display() const {
        std::cout << hour << ":" << minute << ":" << second << std::endl;
    }
};

int main() {
    Time t1;  // 기본 생성자 호출
    Time t2(10, 30, 45);  // 매개변수가 있는 생성자 호출
    
    t1.display();  // 출력: 0:0:0
    t2.display();  // 출력: 10:30:45
    
    return 0;
}

생성자 오버로딩(Constructor Overloading)

생성자도 함수처럼 오버로딩이 가능합니다:

class Box {
private:
    double length;
    double width;
    double height;
    
public:
    // 기본 생성자
    Box() {
        length = 1.0;
        width = 1.0;
        height = 1.0;
    }
    
    // 하나의 매개변수를 가진 생성자 (세 변의 길이가 모두 같은 큐브)
    Box(double side) {
        length = width = height = side;
    }
    
    // 세 개의 매개변수를 가진 생성자
    Box(double l, double w, double h) {
        length = l;
        width = w;
        height = h;
    }
    
    double getVolume() const {
        return length * width * height;
    }
};

int main() {
    Box box1;               // 기본 생성자 호출
    Box box2(5.0);          // 하나의 매개변수를 가진 생성자 호출
    Box box3(2.0, 3.0, 4.0); // 세 개의 매개변수를 가진 생성자 호출
    
    std::cout << "box1 부피: " << box1.getVolume() << std::endl;  // 1.0
    std::cout << "box2 부피: " << box2.getVolume() << std::endl;  // 125.0
    std::cout << "box3 부피: " << box3.getVolume() << std::endl;  // 24.0
    
    return 0;
}

5.3.2 멤버 초기화 리스트(Member Initializer List)

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

class Point {
private:
    int x, y;
    const int id;  // 상수 멤버
    
public:
    // 멤버 초기화 리스트 사용
    Point(int xVal, int yVal, int idVal) : x(xVal), y(yVal), id(idVal) {
        // 생성자 본문
    }
    
    void display() const {
        std::cout << "Point #" << id << ": (" << x << ", " << y << ")" << std::endl;
    }
};

멤버 초기화 리스트를 사용해야 하는 경우:

  1. 상수(const) 멤버 초기화
  2. 참조(reference) 멤버 초기화
  3. 생성자가 없는 클래스의 객체 멤버 초기화
  4. 부모 클래스의 특정 생성자 호출

5.3.3 소멸자(Destructor)

소멸자는 객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수로, 객체가 사용한 자원을 정리하는 역할을 합니다.

#include <iostream>
#include <string>

class Resource {
private:
    std::string name;
    
public:
    // 생성자
    Resource(const std::string& n) : name(n) {
        std::cout << "리소스 " << name << " 생성됨" << std::endl;
    }
    
    // 소멸자 (물결표 ~ 사용)
    ~Resource() {
        std::cout << "리소스 " << name << " 해제됨" << std::endl;
    }
};

void useResource() {
    Resource r1("Local");  // 지역 객체 생성
    std::cout << "함수 내부" << std::endl;
    // 함수가 종료되면 r1의 소멸자가 자동으로 호출됨
}

int main() {
    std::cout << "main 함수 시작" << std::endl;
    
    Resource r2("Global");  // 전역 객체 생성
    useResource();
    
    std::cout << "main 함수 종료" << std::endl;
    // main 함수가 종료되면 r2의 소멸자가 자동으로 호출됨
    
    return 0;
}

출력 결과:

main 함수 시작
리소스 Global 생성됨
리소스 Local 생성됨
함수 내부
리소스 Local 해제됨
main 함수 종료
리소스 Global 해제됨

5.3.4 복사 생성자(Copy Constructor)

복사 생성자는 같은 클래스의 다른 객체를 기반으로 새 객체를 생성할 때 호출됩니다:

#include <iostream>
#include <string>

class Person {
private:
    std::string* name;
    int age;
    
public:
    // 일반 생성자
    Person(const std::string& n, int a) : age(a) {
        name = new std::string(n);  // 동적 메모리 할당
        std::cout << "일반 생성자 호출됨" << std::endl;
    }
    
    // 복사 생성자
    Person(const Person& other) : age(other.age) {
        name = new std::string(*other.name);  // 깊은 복사
        std::cout << "복사 생성자 호출됨" << std::endl;
    }
    
    // 소멸자
    ~Person() {
        delete name;  // 동적 할당된 메모리 해제
        std::cout << "소멸자 호출됨" << std::endl;
    }
    
    void display() const {
        std::cout << "이름: " << *name << ", 나이: " << age << std::endl;
    }
    
    void changeName(const std::string& n) {
        *name = n;
    }
};

int main() {
    Person person1("홍길동", 25);
    person1.display();
    
    // 복사 생성자 호출
    Person person2 = person1;  // 또는 Person person2(person1);
    person2.display();
    
    // person2의 이름 변경이 person1에 영향을 주지 않음 (깊은 복사)
    person2.changeName("김철수");
    
    person1.display();
    person2.display();
    
    return 0;
}

얕은 복사 vs 깊은 복사:

  • 얕은 복사(Shallow Copy): 포인터 변수의 주소값만 복사 (기본 복사 생성자)
  • 깊은 복사(Deep Copy): 포인터가 가리키는 실제 데이터까지 복사 (사용자 정의 복사 생성자)

동적 메모리 할당을 사용하는 클래스는 자원 관리를 위해 깊은 복사를 구현하는 것이 중요합니다.

5.3.5 이동 생성자(Move Constructor) (C++11)

이동 생성자는 자원의 소유권을 이전할 때 사용되며, 불필요한 복사를 방지하여 성능을 향상시킵니다:

#include <iostream>
#include <vector>
#include <string>

class Resource {
private:
    std::string name;
    std::vector<int>* data;
    
public:
    // 일반 생성자
    Resource(const std::string& n, int size) : name(n) {
        data = new std::vector<int>(size, 0);
        std::cout << "리소스 " << name << " 생성됨" << std::endl;
    }
    
    // 복사 생성자
    Resource(const Resource& other) : name(other.name) {
        data = new std::vector<int>(*other.data);
        std::cout << "리소스 " << name << " 복사됨" << std::endl;
    }
    
    // 이동 생성자
    Resource(Resource&& other) noexcept : name(std::move(other.name)) {
        data = other.data;      // 자원 이동 (포인터 복사)
        other.data = nullptr;   // 원본 포인터 무효화
        std::cout << "리소스 " << name << " 이동됨" << std::endl;
    }
    
    // 소멸자
    ~Resource() {
        delete data;
        std::cout << "리소스 " << name << " 해제됨" << std::endl;
    }
    
    void display() const {
        std::cout << "리소스 이름: " << name << std::endl;
        if (data) {
            std::cout << "데이터 크기: " << data->size() << std::endl;
        } else {
            std::cout << "데이터 없음 (이동됨)" << std::endl;
        }
    }
};

// 임시 객체를 반환하는 함수
Resource createResource(const std::string& name, int size) {
    return Resource(name, size);
}

int main() {
    // 이동 생성자 사용 예 1: 임시 객체에서 초기화
    Resource r1 = createResource("TempResource", 1000);
    r1.display();
    
    // 이동 생성자 사용 예 2: std::move 사용
    Resource r2("OriginalResource", 2000);
    Resource r3 = std::move(r2);  // r2에서 r3로 자원 이동
    
    r2.display();  // r2는 이제 빈 상태
    r3.display();  // r3가 자원을 가짐
    
    return 0;
}

'Programming Languages > C++' 카테고리의 다른 글

상속(Inheritance)  (0) 2025.03.28
객체 생성과 수명 주기  (0) 2025.03.28
캡슐화와 접근 지정자  (0) 2025.03.28
객체 지향 프로그래밍  (0) 2025.03.27
챕터4. 실습문제  (0) 2025.03.27