본문 바로가기
GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~

[C/C++ 프로그래밍 : 중급] 3. 생성자와 소멸자

by GDNGY 2023. 5. 17.

Chapter 3. 생성자와 소멸자

객체의 생명주기에 필수적인 이 두 기능을 이해하면, 메모리 관리를 효과적으로 할 수 있습니다. 즉, 이는 안정성과 성능을 위해 필수적인 개념입니다. 이 장에서는 생성자와 소멸자의 기본 구조부터 동적 메모리 관리에 대한 설명과 예제를 통한 사용법을 배웁니다.

 

반응형

 


[Chapter 3. 생성자와 소멸자]

 

3.1. 생성자와 소멸자 이해하기

3.1.1. 생성자란 무엇인가

3.1.2. 소멸자란 무엇인가

3.1.3. 생성자와 소멸자의 역할

 

3.2. 생성자의 선언과 구현

3.2.1. 기본 생성자와 매개변수가 있는 생성자

3.2.2. 복사 생성자

3.2.3. 위임 생성자

 

3.3. 소멸자의 선언과 구현

3.3.1. 소멸자의 기본 구조

3.3.2. 소멸자에서의 메모리 해제

 

3.4. 생성자와 소멸자의 호출 시점과 순서

3.4.1. 객체 생성과 소멸의 순서

3.4.2. 생성자와 소멸자의 호출 시점

 

3.5. 클래스의 동적 메모리 할당

3.5.1. new와 delete 연산자 이해하기

3.5.2. 생성자와 소멸자에서의 동적 메모리 관리

 


3.1. 생성자와 소멸자 이해하기

생성자(Constructor)와 소멸자(Destructor)는 C++에서 클래스를 사용할 때 중요한 개념입니다. 생성자는 클래스의 객체가 생성될 때 호출되는 함수로, 초기값을 설정하는 등의 작업을 수행합니다. 다양한 형태의 생성자를 제공함으로써 다양한 상황에 대응할 수 있습니다. 소멸자는 객체가 메모리에서 해제될 때 호출되는 함수로, 객체의 생명 주기가 끝나기 전에 마무리 작업을 수행합니다. 이를 통해 메모리 누수와 같은 문제를 예방하게 됩니다. 이들은 클래스의 안전성과 효율성을 보장하는 핵심 구성 요소입니다.

3.1.1. 생성자란 무엇인가

C++에서, 생성자는 클래스의 인스턴스가 생성될 때 자동으로 호출되는 특별한 함수입니다. C언어와는 달리, 이는 C++에서의 클래스와 객체지향 프로그래밍의 핵심 구성요소입니다. 클래스를 정의할 때, 우리는 생성자를 이용해 객체가 어떻게 초기화되는지 결정할 수 있습니다.

 

생성자는 보통 클래스와 동일한 이름을 가지며, 리턴 타입을 명시하지 않습니다. 그 이유는 생성자가 생성하는 것이 클래스의 인스턴스이기 때문입니다. 다음은 C++에서 생성자를 사용한 간단한 예시입니다:

 

[예제]

class MyClass {
public:
    MyClass() {    // 이것이 생성자입니다.
        std::cout << "객체가 생성되었습니다!" << std::endl;
    }
};

이 코드에서, MyClass()가 생성자입니다. 이 클래스의 객체를 생성할 때마다, 생성자는 자동으로 호출되어 "객체가 생성되었습니다!"라는 메시지를 출력합니다.

 

[예제]

int main() {
    MyClass obj; // "객체가 생성되었습니다!" 출력
    return 0;
}

다양한 형태의 생성자가 존재합니다. 그중 대표적인 것이 디폴트 생성자(Default Constructor), 매개변수를 가진 생성자(Parameterized Constructor), 복사 생성자(Copy Constructor) 등이 있습니다.

 

디폴트 생성자는 매개변수가 없는 생성자로, 클래스 내에서 정의하지 않아도 컴파일러가 자동으로 제공합니다. 그러나 사용자가 디폴트 생성자 외의 생성자를 정의한 경우에는 컴파일러가 디폴트 생성자를 제공하지 않습니다.

 

매개변수를 가진 생성자는 객체 생성 시 초기값을 전달하는 데 사용됩니다. 이를 통해 각 객체는 독립적인 상태를 가질 수 있습니다.

 

[예제]

class Student {
public:
    Student(std::string n) {  // 매개변수를 가진 생성자입니다.
        name = n;
    }
private:
    std::string name;
};

 

복사 생성자는 객체가 다른 객체로부터 초기화될 때 호출됩니다. 이는 주로 함수의 인자로 객체가 전달될 때나, 함수에서 객체를 반환할 때 사용됩니다.

[예제]

class Student {
public:
    Student(const Student &s) { // 복사 생성자입니다.
        name = s.name;
    }
private:
    std::string name;
};

 

이런 방식으로, 생성자는 C++에서 클래스의 객체가 생성되고 초기화되는 방식을 제어하는 데 필수적입니다. 이해하고 올바르게 사용하는 것이 중요합니다. 이를 통해 안전하고 효율적인 프로그램을 작성할 수 있습니다.

 

3.1.2. 소멸자란 무엇인가

C++에서 소멸자는 클래스의 객체가 메모리에서 제거될 때 자동으로 호출되는 특별한 함수입니다. 이는 클래스와 객체의 생명주기를 관리하는 데 중요한 역할을 합니다.

 

소멸자는 생성자와 같이 클래스와 동일한 이름을 가지지만, 그 앞에 '~' 표시가 붙습니다. 소멸자는 매개변수를 가질 수 없으며, 한 클래스에 대해서는 단 하나만 존재할 수 있습니다. 다음은 C++에서 소멸자를 사용한 간단한 예시입니다:

 

[예제]

class MyClass {
public:
    MyClass() {    // 이것이 생성자입니다.
        std::cout << "객체가 생성되었습니다!" << std::endl;
    }
    ~MyClass() {    // 이것이 소멸자입니다.
        std::cout << "객체가 소멸되었습니다!" << std::endl;
    }
};

 

이 코드에서 ~MyClass()가 소멸자입니다. 이 클래스의 객체가 메모리에서 제거될 때마다, 소멸자는 자동으로 호출되어 "객체가 소멸되었습니다!"라는 메시지를 출력합니다.

[예제]

int main() {
    MyClass obj; // "객체가 생성되었습니다!" 출력
    return 0;    // "객체가 소멸되었습니다!" 출력
}

 

보통 소멸자는 객체가 가지고 있던 자원을 해제하는 데 사용됩니다. 예를 들어, 객체 내부에서 동적 메모리 할당을 사용했다면, 그 메모리를 소멸자에서 해제해야 합니다. 이렇게 하지 않으면 메모리 누수가 발생할 수 있습니다.

 

[예제]

class MyClass {
public:
    MyClass() {
        data = new int[100]; // 동적 메모리 할당
    }
    ~MyClass() {
        delete [] data; // 메모리 해제
    }
private:
    int* data;
};

 

위의 코드에서, MyClass 객체는 생성될 때 100개의 정수를 저장할 수 있는 메모리를 할당하고, 객체가 소멸될 때 그 메모리를 해제합니다. 이렇게 소멸자를 이용해 동적으로 할당된 메모리를 관리함으로써, 메모리 누수를 예방할 수 있습니다.

 

이처럼 소멸자는 C++에서 클래스의 객체가 제거되는 방식을 제어하는 데 필수적입니다. 이해하고 올바르게 사용하는 것이 중요합니다. 이를 통해 안전하고 효율적인 프로그램을 작성할 수 있습니다.

 

3.1.3. 생성자와 소멸자의 역할

생성자와 소멸자는 객체의 생명주기를 관리하는 데 중요한 역할을 합니다. 이들은 객체가 생성될 때와 소멸될 때 자동으로 호출되며, 그 과정에서 필요한 설정이나 해제 작업을 수행합니다.

 

생성자의 주요 역할은 다음과 같습니다:

 

  • 객체 초기화 : 객체가 생성될 때, 멤버 변수들은 생성자에서 초기화됩니다. 이를 통해 객체는 안전한 초기 상태에서 시작할 수 있습니다.
  • 리소스 할당 : 동적 메모리, 파일, 네트워크 등, 객체가 필요로 하는 리소스를 생성자에서 할당받을 수 있습니다.

 

다음은 생성자의 역할을 보여주는 예제입니다:

 

[예제]

class MyClass {
public:
    MyClass(int val) {
        data = new int[val];  // 동적 메모리 할당
        for(int i=0; i<val; i++)
            data[i] = i;  // 초기화
    }

private:
    int* data;
};

이 코드에서, 생성자는 동적으로 메모리를 할당받고 초기화 작업을 수행합니다.

 

반면, 소멸자의 주요 역할은 다음과 같습니다:

 

  • 리소스 해제 : 생성자에서 할당받은 리소스는 소멸자에서 해제되어야 합니다. 그렇지 않으면 메모리 누수와 같은 문제가 발생할 수 있습니다.
  • 마무리 작업 : 객체가 더 이상 필요하지 않을 때, 필요한 마무리 작업을 수행합니다.

 

다음은 소멸자의 역할을 보여주는 예제입니다:

 

[예제]

class MyClass {
public:
    MyClass(int val) {
        data = new int[val];  
        for(int i=0; i<val; i++)
            data[i] = i; 
    }
    ~MyClass() {
        delete[] data;  // 메모리 해제
    }

private:
    int* data;
};

 

이 코드에서, 소멸자는 생성자에서 할당받은 메모리를 해제합니다.

 

이렇게, 생성자와 소멸자는 각각 객체의 생성과 소멸 시점에 특정 작업을 수행하는 역할을 합니다. 이들은 객체의 안전성과 효율성을 보장하므로, 올바른 사용이 중요합니다.

 

3.2. 생성자의 선언과 구현

C++에서 생성자는 클래스와 동일한 이름을 가지는 특별한 함수입니다. 이는 클래스의 객체가 생성될 때 자동으로 호출됩니다. 생성자의 주목적은 클래스의 멤버 변수를 초기화하는 것입니다. 생성자는 인자를 가질 수 있으며, 디폴트 생성자라면 인자 없이도 호출될 수 있습니다. 생성자의 선언과 구현은 클래스 내부 혹은 외부에서 할 수 있습니다.

[예제]

class MyClass { // 클래스 선언
public:
    MyClass();  // 생성자 선언
};

MyClass::MyClass() { // 생성자 구현
    // 초기화 코드
}

 

이 예시에서, 생성자는 MyClass::MyClass()를 통해 외부에서 구현되었습니다.

 

3.2.1. 기본 생성자와 매개변수가 있는 생성자

C++에서 생성자는 두 가지 주요 형태가 있습니다: 기본 생성자와 매개변수가 있는 생성자입니다.

 

기본 생성자는 매개변수가 없는 생성자를 말합니다. 클래스를 선언할 때 생성자를 별도로 정의하지 않으면, 컴파일러는 자동으로 기본 생성자를 제공합니다. 그러나 한 번이라도 생성자를 사용자가 직접 정의하면, 컴파일러는 자동 생성자를 제공하지 않습니다. 기본 생성자는 다음과 같이 사용됩니다:

 

[예제]

class MyClass {
public:
    MyClass() { // 기본 생성자
        std::cout << "MyClass 객체가 생성되었습니다." << std::endl;
    }
};

int main() {
    MyClass obj; // 기본 생성자 호출
    return 0;
}

 

매개변수가 있는 생성자는 인자를 받아 객체 초기화에 활용합니다. 이를 사용하면 객체 생성 시 필요한 값을 제공할 수 있어 유연성이 증가합니다. 예를 들어, 다음 코드는 정수 값을 받는 생성자를 선언하고 구현하였습니다:

 

[예제]

class MyClass {
public:
    MyClass(int a) { // 매개변수가 있는 생성자
        this->a = a;
        std::cout << "MyClass 객체가 생성되었으며, 값은 " << a << "입니다." << std::endl;
    }



private:
    int a;
};

int main() {
    MyClass obj(5); // 매개변수가 있는 생성자 호출, a = 5로 초기화
    return 0;
}

 

이 예제에서 MyClass 객체는 생성자에 전달된 값 5로 초기화됩니다. 매개변수가 있는 생성자를 사용하면 객체의 초기 상태를 제어하는 데 효과적입니다.

 

둘 중 어떤 생성자를 사용하든 그 목적은 같습니다: 객체의 안정적인 초기화를 보장하는 것입니다. 다만 매개변수가 있는 생성자는 더 많은 제어력과 유연성을 제공합니다. 따라서 개발자는 객체의 특성과 필요에 따라 적절한 생성자를 선택해야 합니다.

 

3.2.3. 위임 생성자

위임 생성자는 한 생성자가 다른 생성자에게 일부 또는 전체 초기화 작업을 위임하는 C++11의 기능입니다. 이를 통해 중복 코드를 줄이고, 초기화 로직을 한 곳에서 관리할 수 있습니다.

 

기본적인 위임 생성자 사용법은 다음과 같습니다. 생성자가 다른 생성자를 호출하려면, 멤버 초기화 리스트에서 그 생성자를 호출합니다. 다음 코드는 위임 생성자의 예를 보여줍니다.

 

[예제]

class MyClass {
public:
    MyClass() : MyClass(0, 0) { // 기본 생성자가 두 매개변수 생성자를 호출
        std::cout << "기본 생성자 호출" << std::endl;
    }

    MyClass(int a, int b) : a(a), b(b) { // 두 매개변수 생성자
        std::cout << "두 매개변수 생성자 호출, a = " << a << ", b = " << b << std::endl;
    }

private:
    int a, b;
};

int main() {
    MyClass obj; // 기본 생성자 호출, 이후 두 매개변수 생성자 호출
    return 0;
}

 

이 코드에서 기본 생성자는 두 매개변수 생성자를 호출하여 a와 b를 0으로 초기화합니다. 따라서 MyClass obj;를 실행하면 두 생성자 모두 호출되고, 두 매개변수 생성자에서 멤버 변수 초기화가 이루어집니다.

 

위임 생성자는 코드의 중복을 줄이고 가독성을 향상시키는 효과적인 방법입니다. 그러나 반드시 한 생성자가 다른 생성자를 호출해야 하는 것은 아니며, 상황에 따라 적절한 방식을 선택해야 합니다. 또한 생성자 간의 무한 루프 호출에 주의해야 합니다. 즉, 생성자 A가 생성자 B를 호출하고, 생성자 B가 다시 생성자 A를 호출하는 상황을 피해야 합니다. 이러한 상황은 컴파일 오류를 유발하며, 이를 방지하기 위해선 생성자 호출 구조를 잘 설계해야 합니다.

 

3.3. 소멸자의 선언과 구현

소멸자는 객체의 생명주기가 끝날 때 호출되며, 주로 동적 할당된 메모리를 해제하거나, 파일 닫기 등의 정리 작업에 사용됩니다. 생성자와 달리 소멸자는 매개변수가 없으며, 클래스당 하나만 존재합니다. 소멸자는 클래스 이름 앞에 '~'를 붙여 선언합니다. 예를 들면, ~MyClass()가 될 것입니다. 소멸자의 호출은 개발자가 직접 하는 것이 아니라, 객체가 소멸될 때 자동으로 이루어집니다. 

3.3.1. 소멸자의 기본 구조

소멸자는 객체의 생명 주기가 끝날 때 자동으로 호출되는 특수한 멤버 함수입니다. 소멸자는 클래스의 이름 앞에 '~' 기호를 붙여 선언하며, 어떠한 인자도 받지 않고 반환 값도 없습니다. 즉, 클래스 내에는 오직 하나의 소멸자만 존재할 수 있습니다. 

 

C++에서 소멸자의 주된 역할은 객체가 동적으로 할당한 메모리나 자원을 해제하는 것입니다. 이는 메모리 누수를 방지하고 프로그램의 안정성을 높이는 데 기여합니다.

 

[예제]

class MyClass {
private:
    int* data; // 동적 메모리를 가리키는 포인터

public:
    MyClass(int size) {
        data = new int[size]; // 메모리 동적 할당
    }

    ~MyClass() {
        delete[] data; // 동적으로 할당한 메모리 해제
    }
};

int main() {
    MyClass obj(10); // 객체 생성, 소멸자는 main 함수가 끝나면서 자동 호출됩니다.
    return 0;
}

 

위 코드에서 MyClass 객체를 생성할 때 생성자가 동적 메모리를 할당하고, main 함수가 끝나면서 소멸자가 그 메모리를 해제합니다. 이처럼 소멸자는 주로 자원을 정리하는 용도로 사용되며, 객체가 사용한 메모리를 안전하게 회수하는데 중요한 역할을 합니다. 

 

소멸자는 사용자가 직접 호출할 수 없습니다. 대신 객체가 스코프를 벗어나거나 delete 연산자를 사용하여 명시적으로 객체를 삭제할 때 자동으로 호출됩니다. 이러한 특성 때문에 소멸자는 객체의 생명 주기를 관리하는데 핵심적인 역할을 수행합니다. 

 

3.3.2. 소멸자에서의 메모리 해제

C++에서 동적 메모리를 사용하는 프로그램은 종종 생성자에서 메모리를 할당하고, 소멸자에서 이 메모리를 해제하는 패턴을 따릅니다. 이를 통해 클래스는 자신이 사용하는 리소스를 적절히 관리하게 되며, 이는 메모리 누수를 방지하는 데 매우 중요합니다.

 

소멸자에서 메모리를 해제하는 것은 다음과 같은 방식으로 이루어집니다. 먼저, 생성자에서 new 연산자를 사용하여 동적 메모리를 할당합니다. 그런 다음 소멸자에서 delete 연산자를 사용하여 이 메모리를 해제합니다.

 

아래의 예제 코드를 살펴봅시다.

 

[예제]

class MyClass {
private:
    int* data;

public:
    MyClass(int size) {
        data = new int[size]; // 동적 메모리 할당
    }

    ~MyClass() {
        delete[] data; // 동적으로 할당한 메모리 해제
    }
};

int main() {
    MyClass obj(10); // 객체 생성, 소멸자는 main 함수가 끝나면서 자동 호출됩니다.
    return 0;
}

 

위의 코드에서, MyClass 객체의 생성자는 new 연산자를 사용하여 메모리를 할당하고, 소멸자는 delete 연산자를 사용하여 할당된 메모리를 해제합니다. 이렇게 소멸자에서 메모리를 해제하면, 객체가 사용했던 메모리를 안전하게 반환할 수 있습니다.

 

하지만 이러한 패턴은 주의가 필요합니다. 만약 동적 메모리를 가리키는 포인터를 복사하거나, 소멸자가 호출되기 전에 포인터를 삭제하면 문제가 발생할 수 있습니다. 이러한 이유로, C++에서는 스마트 포인터와 같은 메모리 관리 도구를 제공하며, 이를 사용하면 메모리 관리가 더욱 편리해집니다.

 

소멸자는 프로그램의 안정성과 효율성을 위해 중요한 역할을 수행합니다. 메모리를 적절히 관리하려면 소멸자에서 할당한 메모리를 반드시 해제해야 합니다. 이는 프로그래밍의 핵심 원칙 중 하나이며, 이를 준수함으로써 메모리 누수와 같은 문제를 방지할 수 있습니다.

 

3.4. 생성자와 소멸자의 호출 시점과 순서

C++에서 생성자는 객체가 생성될 때 자동으로 호출되며, 소멸자는 객체의 수명이 끝나면 호출됩니다. 생성자는 객체의 초기화를 담당하고, 소멸자는 객체의 메모리 해제를 처리합니다. 이런 이유로 생성자와 소멸자의 호출 순서는 중요합니다. 만약 객체가 다른 객체를 포함하고 있다면, 포함된 객체의 생성자가 먼저 호출되고, 그 다음에그다음에 포함하는 객체의 생성자가 호출됩니다. 반면 소멸자는 반대 순서로 호출됩니다. 즉, 포함하는 객체의 소멸자가 먼저 호출되고, 그다음에 포함된 객체의 소멸자가 호출됩니다.

3.4.1. 객체 생성과 소멸의 순서

C++에서 객체 생성과 소멸의 순서를 이해하는 것은 매우 중요합니다. 이 순서는 생성자와 소멸자가 호출되는 순서를 결정합니다.

 

객체가 생성될 때, 생성자는 필드를 초기화하고 필요한 설정을 수행합니다. 객체의 수명이 끝나면, 소멸자는 필요한 청소 작업을 수행합니다. 그런데 만약 한 객체가 다른 객체를 포함하고 있다면 어떻게 될까요? 이 경우 생성자와 소멸자의 호출 순서는 어떻게 되는지 이해해야 합니다.

 

먼저, 객체가 다른 객체를 포함하고 있는 경우, 포함된 객체의 생성자가 먼저 호출됩니다. 그 다음에 포함하는 객체의 생성자가 호출됩니다. 이렇게 함으로써 포함하는 객체가 생성될 때, 그 안에 있는 모든 객체들도 이미 생성된 상태가 됩니다.

 

이제 C++ 코드 예제를 통해 이를 확인해봅시다:

 

[예제]

#include<iostream>

class Inner {
public:
    Inner() { std::cout << "Inner 생성자 호출\n"; }
    ~Inner() { std::cout << "Inner 소멸자 호출\n"; }
};

class Outer {
public:
    Outer() { std::cout << "Outer 생성자 호출\n"; }
    ~Outer() { std::cout << "Outer 소멸자 호출\n"; }
private:
    Inner inner;
};

int main() {
    Outer outer;
    return 0;
}

 

이 프로그램을 실행하면, 생성자와 소멸자의 호출 순서를 쉽게 확인할 수 있습니다.

 

"Inner 생성자 호출"

"Outer 생성자 호출"

"Outer 소멸자 호출"

"Inner 소멸자 호출"

 

소멸자의 호출 순서는 반대입니다. 포함하는 객체의 소멸자가 먼저 호출되고, 그다음에 포함된 객체의 소멸자가 호출됩니다. 이렇게 하면, 포함하는 객체가 소멸될 때 그 안에 있는 모든 객체들도 이미 소멸된 상태가 됩니다.

 

이 순서는 프로그램의 정확성과 안정성을 보장하기 위해 중요합니다. 객체가 올바르게 생성되지 않으면, 그 객체를 사용하는 코드에서 예기치 않은 동작을 일으킬 수 있습니다. 비슷하게, 객체가 올바르게 소멸되지 않으면, 메모리 누수와 같은 문제를 일으킬 수 있습니다. 따라서 생성자와 소멸자의 호출 순서를 이해하고 이를 올바르게 관리하는 것은 중요한 프로그래밍 스킬입니다.

 

3.4.2. 생성자와 소멸자의 호출 시점

객체 지향 프로그래밍에서 객체의 생명주기는 매우 중요한 개념입니다. 객체의 생명주기는 객체가 메모리에서 할당되어 사용되고, 마침내 메모리에서 해제되는 시점을 의미합니다. 이러한 객체의 생명주기는 생성자와 소멸자의 호출 시점에 의해 결정됩니다.

 

생성자는 객체가 메모리에서 처음 생성될 때 호출됩니다. 생성자의 주요 역할은 객체를 올바르게 초기화하는 것입니다. 초기화는 데이터 멤버를 설정하거나, 객체가 필요로 하는 자원을 할당하는 등의 작업을 포함합니다.

 

C++에서는 다음과 같은 코드로 생성자를 확인할 수 있습니다:

 

[예제]

#include<iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 객체가 생성되었습니다.\n"; }
};

int main() {
    MyClass obj;
    return 0;
}

 

이 프로그램을 실행하면 "MyClass 객체가 생성되었습니다."라는 메시지가 출력되는 것을 볼 수 있습니다. 이 메시지는 MyClass 객체의 생성자가 호출될 때 출력됩니다.

 

반면에, 소멸자는 객체의 생명주기가 끝날 때, 즉 객체가 메모리에서 해제될 때 호출됩니다. 소멸자는 객체가 사용하던 자원을 정리하는 역할을 합니다. 이러한 자원은 메모리뿐만 아니라 파일, 네트워크 연결 등과 같은 외부 자원일 수도 있습니다.

 

다음은 소멸자가 호출되는 시점을 보여주는 C++ 코드입니다:

 

[예제]

#include<iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass 객체가 생성되었습니다.\n"; }
    ~MyClass() { std::cout << "MyClass 객체가 소멸되었습니다.\n"; }
};

int main() {
    {
        MyClass obj;
    } // obj의 범위를 벗어나면 소멸자가 호출됨
    return 0;
}

 

이 프로그램을 실행하면, 먼저 "MyClass 객체가 생성되었습니다."라는 메시지가 출력되고, 다음으로 "MyClass 객체가 소멸되었습니다."라는 메시지가 출력됩니다. 이 두 번째 메시지는 MyClass 객체의 소멸자가 호출될 때 출력됩니다.

 

생성자와 소멸자의 호출 시점을 이해하는 것은 객체 지향 프로그래밍의 중요한 부분입니다. 이를 통해 객체의 생명주기를 제어하고, 필요한 자원을 올바르게 관리할 수 있습니다.

 

3.5. 클래스의 동적 메모리 할당

객체는 동적으로 생성될 수 있습니다. 이는 'new' 키워드를 통해 객체를 힙 메모리에 할당함으로써 이루어집니다. 동적으로 생성된 객체는 프로그램이 끝날 때나 'delete'를 통해 명시적으로 메모리를 해제할 때까지 메모리에 남아 있습니다. 클래스에 동적 메모리를 할당하면 클래스의 객체들이 메모리를 독립적으로 사용할 수 있어, 데이터 관리가 더욱 유연해집니다. 동적 할당의 중요한 부분은 반드시 할당된 메모리를 해제해야 함입니다. 이는 메모리 누수를 방지하기 위함입니다.

3.5.1. new와 delete 연산자 이해하기

C++에서는 'new'와 'delete'라는 두 가지 특별한 연산자를 제공합니다. 이들은 동적 메모리 할당에 있어서 중요한 역할을 합니다. 동적 메모리 할당이란, 프로그램이 실행 중인 동안에 메모리를 필요에 따라 할당하거나 해제하는 것을 말합니다. 이를 통해 우리는 효율적인 메모리 관리와 더 유연한 프로그래밍을 할 수 있습니다.

 

new 연산자

C++에서 'new' 연산자는 동적 메모리 할당에 사용되며, 객체 또는 기본 데이터 타입을 동적으로 할당할 수 있습니다. 이 연산자는 메모리를 할당하고 해당 메모리의 주소를 반환합니다. 그리고 'new'를 통해 생성된 객체의 경우, 해당 객체의 생성자가 호출됩니다.

 

[예제]

int* ptr = new int;   // 정수를 위한 동적 메모리를 할당하고 주소를 ptr에 저장
*ptr = 20;            // 할당된 메모리에 값을 저장

 

delete 연산자

'new' 연산자를 통해 할당된 메모리는 반드시 'delete' 연산자를 통해 해제되어야 합니다. 메모리를 제대로 해제하지 않으면 메모리 누수가 발생할 수 있습니다. 이는 사용하지 않는 메모리가 계속 남아있게 되어 시스템의 전체적인 성능을 저하시키는 원인이 됩니다.

 

[예제]

delete ptr;   // ptr이 가리키는 메모리를 해제
ptr = nullptr; // ptr을 null로 초기화


이 코드에서, 'delete'를 통해 동적으로 할당된 메모리를 해제하였고, 포인터를 nullptr로 초기화하였습니다. 이는 안전한 프로그래밍을 위해 중요한 습관입니다. 메모리를 해제한 후에는 반드시 해당 포인터를 nullptr로 만들어서, 더 이상 유효하지 않은 메모리 주소를 가리키지 않도록 해야 합니다.

 

이렇게 'new'와 'delete' 연산자를 통해 동적 메모리 할당을 이해하고 관리하는 것은 C++ 프로그래밍에서 중요한 요소 중 하나입니다. 

 

3.5.2. 생성자와 소멸자에서의 동적 메모리 관리

객체 내에 동적으로 할당된 메모리를 관리하는 것은 매우 중요합니다. 특히 C++에서는 이를 위해 생성자와 소멸자가 이용됩니다. 이 섹션에서는 이에 대한 자세한 내용을 알아보겠습니다.

 

생성자에서는 동적 메모리를 할당하는데 사용되고, 소멸자는 그 메모리를 해제하는 역할을 합니다. 이러한 메모리 관리의 중요한 포인트는, 할당된 메모리를 항상 해제하는 것입니다. 그렇지 않으면, 메모리 누수(memory leak)가 발생하게 됩니다.

 

이제 생성자와 소멸자에서 어떻게 동적 메모리를 관리하는지 예제 코드를 통해 확인해 보겠습니다.

[예제]

class MyClass {
private:
    int* data;

public:
    // 생성자에서 동적 메모리를 할당합니다.
    MyClass(int size) {
        data = new int[size];
    }

    // 소멸자에서 동적으로 할당된 메모리를 해제합니다.
    ~MyClass() {
        delete[] data;
    }
};

int main() {
    MyClass obj(5); // 5개의 int를 저장할 수 있는 메모리를 동적으로 할당합니다.
    return 0; // obj가 소멸되면서, 동적으로 할당된 메모리가 해제됩니다.
}

이 예제에서, 생성자는 메모리를 동적으로 할당하고 그 주소를 data에 저장합니다. 그런 다음 소멸자는 data가 가리키는 메모리를 해제합니다. 이는 객체가 소멸될 때 호출되어, 할당된 메모리가 해제되고 메모리 누수를 방지합니다.

 

이처럼, C++에서는 생성자와 소멸자를 이용하여 동적 메모리를 효과적으로 관리할 수 있습니다. 이렇게 함으로써, 우리는 메모리 사용량을 줄이고 프로그램의 성능을 향상할 수 있습니다. 이러한 메모리 관리 기법은 C++의 중요한 특징 중 하나로, 이를 이해하고 잘 활용하는 것은 매우 중요합니다.

 

 

2023.05.16 - [GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~] - [C/C++ 프로그래밍 : 중급] 2. 클래스와 객체

 

[C/C++ 프로그래밍 : 중급] 2. 클래스와 객체

Chapter 2. 클래스와 객체 클래스와 객체는 C++과 같은 객체 지향 언어에서 중요한 역할을 합니다. 이를 통해 개발자들은 효율적이고 가독성 높은 코드를 작성하는 데 도움을 받게 됩니다. 다양한

gdngy.tistory.com

 

반응형

댓글