[C++] 03-2. 생성자와 소멸자(constructor, destructor)

SunowMin·2023년 9월 30일
0

C++

목록 보기
5/8

1. 생성자

1-1. 생성자란?

  • C++ 객체를 생성할 때 객체를 초기화 할 수 있음
  • 어떻게? 클래스는 객체가 생성될 때 자동으로 실행되는 생성자(constructor)라는 특별한 멤버 함수를 통해 객체를 초기화 함
  • 한 클래스에 여러 개의 생성자를 둘 수 있으나, 이 중 하나만 실행




1-2. 생성자의 특징

① 생성자의 목적은 객체가 생성될 때 필요한 초기 작업을 위함

  • 객체를 사용하기 전에 필요한 조치를 할 수 있도록 하기 위함

② 생성자 함수는 오직 한 번만 실행

  • 객체가 생성되는 시점에 오직 한 번만 자동으로 실행됨

③ 생성자 함수의 이름은 클래스 이름과 동일하게 작성해야 함

  • 생성자를 다른 멤버 함수와 쉽게 구분하기 위함

④ 생성자 함수의 원형에 리턴 타입을 선언하지 않음

  • 생성자는 함수이지만 리턴 타입을 선언해서는 안됨. (void 리턴 타입도 설정 불가)
class Circle {
	....
    Circle();  // 정상적인 생성자 선언.
    void Circle(int r);  // 컴파일 오류. 생성자는 리턴 타입 없음
    int Circle(double r);  // 컴파일 오류. 생성자는 리턴 타입 없음
    ....
};
  • 함수 실행 종료를 위해 return문은 사용 가능하지만 어떤 값도 리턴해서는 안됨
Circle::Circle() {
	...
	return;  // 생성자 함수를 종료하는 정상적인 리턴문
}

Circle::Circle() {
	...
    return 0;   // 컴파일 오류. 생성자 함수는 값을 리턴해서는 안 됨
}

⑤ 생성자는 중복 가능

  • 생성자는 한 클래스에 여러 개 만들 수 있음
  • 단, 매개 변수 개수나 타입이 서로 다르게 선언되어야 함
  • 사용자가 다양한 방법으로 객체를 생성하도록 함에 있음




1-3. 객체 생성될 때 생성자 실행 과정

  • 2개의 생성자를 가진 Circle 클래스
#include <iostream> 
using namespace std; 

class Circle {
public:
	int radius;
	Circle(); // 기본 생성자
	Circle(int r); // 매개 변수 있는 생성자
	double getArea();
}; 

Circle::Circle() {
	radius = 1;
	cout << "반지름 " << radius << " 원 생성" << endl;
}

Circle::Circle(int r) {
	radius = r;
	cout << "반지름 " << radius << " 원 생성" << endl;
}

double Circle::getArea() {
	return 3.14*radius*radius;
}

int main() {
	Circle donut; // 매개 변수 없는 생성자 호출
	double area = donut.getArea();
	cout << "donut 면적은 " << area << endl;

	Circle pizza(30); // 매개 변수 있는 생성자 호출
	area = pizza.getArea();
	cout << "pizza 면적은 " << area << endl;
}

실행결과
반지름 1 원 생성
donut 면적은 3.14
반지름 30 원 생성
pizza 면적은 2826

  • 객체의 생성 : 객체 크기의 공간을 할당한 후, 객체 내의 생성자 함수가 실행




1-4. 위임생성자(delegateing construct), 타겟 생성자

  • 한 클래스의 생성자들에는 대개 객체를 초기화하는 비슷한 코드가 중복됨
  • 아래의 경우에도 다음과 같이 2개의 생성자에 코드가 중복되어 있음
Circle::Circle() {
	radius = 1;
    cout << "반지름 " << radius << "원 생성" << endl; // 코드 중복
}
Circle::Circle(int r) {
	radius = r;
    cout << "반지름 " << radius << "원 생성" << endl; // 코드 중복
}
  • C++11부터는 중복된 초기화 코드를 하나로 몰고, 다른 생성자에서 이 생성자를 호출할 수 있게 함
Circle::Circle() : Circle(1) { } // Circle(int r)의 생성자 호출
Circle::Circle(int r) {
	radius = r;
    cout << "반지름 " << radius << "원 생성" << endl; // 코드 중복
}
  • 순서
    Circle() 생성자가 호출 → Circle() 생성자는 자신의 코드를 실행하기 전에 Circle(int r) 생성자 호출 → r에 1을 넘겨주어 radius를 1로 초기화하고 반지름과 원 생성을 대신 출력
  • 객체의 초기화 작업이 코딩된 Circle(int r)이 타켓 생성자
  • Circle() 생성자는 객체의 초기화를 다른 생성자에 위임한다고 해서 위임 생성자라고 부름
  • 위임 생성자에서는 타겟 생성자를 호출한 뒤, 자신만의 코드를 추가하면 됨




1-5. 생성자와 멤버 변수의 초기화

클래스의 멤버 변수들은 자동으로 초기화되지 않기 때문에 생성자에서 초기화 함

① 생성자 코드에서 멤버 변수 초기화

2개의 생성자가 각각 멤버 변수를 초기화하는 Point 클래스

class Point {
	int x, y;
public:
	Point();
	Point(int a, int b);
};
Point::Point() {  x = 0; y = 0; }
Point::Point(int a, int b) { x = a; y = b; }

② 생성자 서두에 초깃값으로 초기화

이 2개의 생성자는 생성자 코드의 구현부에 멤버 변수와 초깃값을 쌍으로 지정하고 이들을 콤마(,)로 나열하여 작성할 수 있음

Point::Point() : x(0), y(0) {  // 멤버 변수 x, y를 0으로 초기화
}
Point::Point(int a, int b)  // 멤버 변수 x=a, y=b로 초기화
	: x(a), y(b) {  // 콜론(:) 이하 부분을 다음 줄에 써도 됨
}

또는 다음과 같이 멤버 변수 초기화 가능

Point::Point(int a)
	: x(a), y(b) {
}
Point::Point(int a)
	: x(100+a), y(100) {
}

③ 클래스 선언부에서 직접 초기화

멤버 변수는 C++11부터 다음과 같이 선언문에서 직접 초기화 가능

class Point {
	int x = 0, y = 0;
    ...
};

예제

#include <iostream>
using namespace std;

class Point {
	int x, y;
public:
	Point();
	Point(int a, int b);
	void show() { cout << "(" << x << ", " << y << ")" << endl; }
};

Point::Point() : Point(0, 0) { } // Point(int a, int b) 생성자 호출

Point::Point(int a, int b)
	: x(a), y(b) { }

int main() {
	Point origin;
	Point target(10, 20);
	origin.show();
	target.show();
}

실행결과
(0, 0)
(10, 20)




생성자는 꼭 있어야 하는가?

  • C++ 컴파일러는 객체가 생성될 때 생성자 중 반드시 하나를 호출하도록 컴파일 함.
  • 따라서 생성자는 꼭 존재해야 함!

생성자를 선언하지 않은 클래스는 어떻게 될까?

  • 생성자가 없는 클래스에 대해서는 컴파일러가 기본 생성자(default construct)를 만들어 삽입하고, 자신이 삽입한 기본 생성자를 호출하도록 컴파일 함




1-6. 기본 생성자(default construct)

기본 생성자

  • 클래스에 선언된 어떤 생성자도 없을 때 컴파일러가 자동으로 생성해주는 생성자
  • 디폴트 생성자(default construct)라고도 부르며, 다음과 같이 매개 변수 없는 생성자
class Circle {
	Circle();  // 기본 생성자
};

① 기본 생성자가 자동으로 생성되는 경우

  • 생성자가 하나도 없는 클래스의 경우 컴파일러는 보이지 않게 기본 생성자 삽입
  • 컴파일러가 삽입한 기본 생성자는 아무 실행 없이 바로 리턴

② 기본 생성자가 자동으로 생성되지 않는 경우

  • 생성자가 하나라도 선언된 클래스의 경우 컴파일러는 기본 생성자를 자동 삽입하지 않음
  • 예를 들어, Circle donut;을 호출한 경우 Circle() 생성자가 필요한데 기본 생성자 대신 Circle(int r) 생성자만 존재함 ⇒ 이 문장에 컴파일 오류 발생







2. 소멸자

2-1. 소멸자란?

  • C++ 객체 역시 언젠가는 소멸되는데, 객체가 소멸되면 객체 메모리는 시스템으로 반환됨 + 객체 소멸 시 생성자 함수가 실행
  • 소멸자(destructor) : 객체가 소멸되는 시점에서 자동으로 호출되는 클래스의 멤버 함수




2-2. 소멸자의 특징

① 소멸자의 목적은 객체가 사라질 때 필요한 마무리 작업을 위함

  • 객체가 사라지기 전에 필요한 조치를 하도록 하기 위함

② 소멸자의 이름은 클래스 이름앞에 ~를 붙임

  • Circle 클래스의 소멸자 이름은 ~Circle()이며, 소멸자는 다음과 같이 작성
Circle::~Circle() {...}

③ 소멸자는 리턴 타입이 없으며 어떤 값도 리턴해서는 안됨

④ 소멸자는 오직 한 개만 존재하며 매개 변수를 가지지 않음

⑤ 소멸자가 선언되어 있지 않으면 기본 소멸자(default destructor)

  • 기본 소멸자는 아무 일도 하지 않고 단순 리턴하도록 만들어 짐




2-3. 소멸자 실행

#include <iostream> 
using namespace std; 

class Circle {
public:
	int radius;
	Circle(); 
	Circle(int r); 
	~Circle(); // 소멸자 선언
	double getArea(); 
};

Circle::Circle() {
	radius = 1;
	cout << "반지름 " << radius << " 원 생성" << endl;
} // Circle::Circle():Circle(1)으로 해도 됨

Circle::Circle(int r) {
	radius = r;
	cout << "반지름 " << radius << " 원 생성" << endl;
}

Circle::~Circle() {
	cout << "반지름 " << radius << " 원 소멸" << endl;
}

double Circle::getArea() {
	return 3.14*radius*radius;
}

int main() {
	Circle donu;
	Circle pizza(30); 
	return 0;
} 

실행결과
반지름 1 원 생성
반지름 30 원 생성
반지름 30 원 소멸
반지름 1 원 소멸

  • main() 함수가 종료하면 main() 함수의 스택에 생성된 pizza, donut 객체가 생성된 반대순으로 소멸됨



생성자/소멸자 실행 순서

  • 함수 내에서 선언된 객체를 지역 객체(local object)라고 부르고, 함수가 실행될 때 생성되고 함수가 종료할 때 소멸됨
  • 함수 바깥에 선언된 객체를 전역 객체(global object)라고 부르고, 프로그램이 로딩될 때 생성되고 main()이 종료한 뒤 프로그램 메모리가 사라질 때 소멸됨
  • 전역 객체나 지역 객체 모두 생성된 순서의 반대순으로 소멸

예제 : 지역 객체와 전역 객체의 생성 및 소멸 순서

#include <iostream> 
using namespace std; 

class Circle {
public:
	int radius;
	Circle(); 
	Circle(int r);
	~Circle(); 
	double getArea();
};

Circle::Circle() {
	radius = 1;
	cout << "반지름 " << radius << " 원 생성" << endl;
}  // Circle::Circle():Circle(1)로 해도 됨

Circle::Circle(int r) {
	radius = r;
	cout << "반지름 " << radius << " 원 생성" << endl;
}

Circle::~Circle() {
	cout << "반지름 " << radius << " 원 소멸" << endl;
}

double Circle::getArea() {
	return 3.14*radius*radius;
}

Circle globalDonut(1000);   // 전역 객체 생성
Circle globalPizza(2000);  // 전역 객체 생성

void f() {
	Circle fDonut(100);   // 지역 객체 생성
	Circle fPizza(200);   // 지역 객체 생성
}

int main() {
	Circle mainDonut;   // 지역 객체 생성
	Circle mainPizza(30);   // 지역 객체 생성
	f();
}

실행 결과
반지름 1000 원 생성
반지름 2000 원 생성
반지름 1 원 생성
반지름 30 원 생성
반지름 100 원 생성
반지름 200 원 생성
반지름 200 원 소멸
반지름 100 원 소멸
반지름 30 원 소멸
반지름 1 원 소멸
반지름 2000 원 소멸
반지름 1000 원 소멸






해당 포스팅은 '황기태, 『명품 C++ Programming』, 생능출판사'를 참고하여 작성하였습니다.

0개의 댓글