프로그래밍 언어/C++

[자료형(data type)] C++ 파생형 클래스 2 : 생성자와 소멸자 (+39강, 41강)

순정법사 2023.09.13

B. 생성자

1. 생성자의 기초

a. 멤버 변수의 초기화

클래스를 가지고 객체를 생성하면, 해당 객체는 메모리에 즉시 생성되지만

초기화 하기 전까진 사용할 수 없음

 

객체 멤버 변수는 private 멤버때문에 일반적인 방식으론 접근할 수 없어서

초기화를 위한 public 함수가 필요하고

이런 초기화 함수는 객체의 생성과 초기화 전까지 반드시 멤버의 초기화를 위해 호출 되어야 함

 

b. 생성자란?

🌟 객체의 생성과 동시에 멤버 변수를 초기화해주는 멤버 함수

 

객체 멤버의 초기화뿐만 아니라, 객체를 사용하기 위한 외부 환경까지도 초기화하는 역할

 

c. 생성자의 특징

  • 초기화를 위한 데이터를 인수로 전달받음
  • 생성자는 반환값이 없지만, void 형으로 선언 ❌
  • 초기화하는 방법이 여러개일 경우, 오버로딩 규칙에 따라 여러개 생성 가능

 

d. 생성자의 선언

생성자의 이름은 해당 클래스의 이름과 같음 (클래스이름())

생성자 클래스의 원형은 클래스 선언의 public 영역에 포함되어야 함

 

⏳ 예제 : Book 클래스의 생성자 원형에 따라 생성자 함수를 선언하는 예제

Book::Book(const string& title, int total_page)
{
    title_ = title;           // 책의 제목을 초기화함.
    total_page_ = total_page; // 책의 총 페이지를 초기화함.
    current_page_ = 0;        // 현재 페이지를 0으로 초기화함.
    set_percent();            // 총 페이지와 현재 페이지로 해당 책을 읽은 정도를 초기화함.
}

 

e. 생성자의 호출

클래스에서 객체를 생성할 때마다 해당 클래스의 생성자가 컴파일러에 의해 자동으로 호출

 

1) 암시적으로 호출

 

🌟 () 사용

 

⏳ 예제 : Book 클래스의 객체를 생성하면서 생성자를 암시적으로 호출

#include <iostream>
using namespace std;

class Book
{
private:
	int current_page_;
	void set_percent();
public:
	Book(const string& title, int total_page);
	string title_;
	int total_page_;
	double percent_;
	void Move(int page);
	void Open();
	void Read();
};

int main(void)
{
	Book web_book("HTML과 CSS", 350);	// 생성자의 암시적 호출 
	// 생성자가 호출되어 멤버 변수인 percent_가 초기화되었는지를 확인함. 
	cout << web_book.percent_;
	return 0;
}

Book::Book(const string& title, int total_page)
{
	title_ = title;
	total_page_ = total_page;
	current_page_ = 0;
	set_percent();
}

void Book::set_percent()
{
	percent_ = (double) current_page_ / total_page_ * 100;
}

✨ 실행결과

0

 

2) 명시적 호출

 

🌟 = 사용

 

⏳ 예제 : Book 클래스의 객체를 생성하면서 생성자를 명시적으로 호출

#include <iostream>
using namespace std;

class Book
{
private:
	int current_page_;
	void set_percent();
public:
	Book(const string& title, int total_page);
	string title_;
	int total_page_;
	double percent_;
	void Move(int page);
	void Open();
	void Read();
};

int main(void)
{
	Book web_book = Book("HTML과 CSS", 350);	// 생성자의 명시적 호출 
	// 생성자가 호출되어 멤버 변수인 percent_가 초기화되었는지를 확인함. 
	cout << web_book.percent_;
	return 0;
}

Book::Book(const string& title, int total_page)
{
	title_ = title;
	total_page_ = total_page;
	current_page_ = 0;
	set_percent();
}

void Book::set_percent()
{
	percent_ = (double) current_page_ / total_page_ * 100;
}

✨ 실행결과

0

 

 

2. 디폴트 생성자

a. 디폴트 생성자란?

🌟 객체가 생성될 때 사용자가 초깃값을 명시하지 않으면, 컴파일러가 자동으로 제공하는 생성자

 

  • 매개변수 ❌
  • 대부분 디폴트 생성자는 0, Null, 빈 문자열로 초기화 진행
  • 다른 생성자가 있으면 제공하지 않음

 

📘 컴파일러가 제공하는 디폴트 생성자의 원형

//Class명이 Book일경우
Book::Book() { }

 

b. 디폴트 생성자 정의하기

1) 디폴트 인수를 이용한 디폴트 생성자의 정의

 

모든 인수에 디폴트 값을 명시하면, 인수를 전달하지 않고도 객체를 생성할 수 있는 디폴트 생성자가 됨

 

⏳ 예제 : 디폴트 인수 사용

Book::Book(const string& title = "웹 프로그래밍", int total_page = "100");

 

2) 함수 오버로딩을 이용한 디폴트 생성자의 정의

 

⏳ 예제 : 함수 오버로딩을 이용한 정의

Book();

 

📌 클래스는 단 하나의 디폴트 생성자만을 가질 수 있으므로, 둘 중 한 가지 방법으로만 디폴트 생성자를 정의

 

c. 디폴트 생성자를 가지는 객체의 선언

다음과 같이 세 가지 방법으로 선언할 수 있음

 

⏳ 예제 : 디폴트 생성자 호출

1. Book web_book;             // 디폴트 생성자의 암시적 호출
2. Book web_book = Book();    // 디폴트 생성자의 명시적 호출
3. Book *ptr_book = new Book; // 디폴트 생성자의 암시적 호출

 

📌 디폴트 생성자를 암시적 호출을 할때에는 괄호()를 사용하면 안됨  (1.에 () 추가한다는 뜻)

 

 

3. 복사 생성자 (41강)

a. 복사 연산자 = 얕은 복사

🌟 값을 복사하는 것이 아닌, 값을 가리키는 포인터를 복사하는 생성자, 컴파일러 자동 제공

 

1) 변수의 얕은 복사

 

복사(대입) 연산자를 이용한 복사는 얕은 복사가 됨

변수에서 대입 연산자를 이용한 값의 복사는 문제 X but 객체에서는 문제가 될 수 있음

 

⏳ 예제 : 변수의 얕은 복사

int x = 10;
int y = x;

 

2) 객체의 얕은 복사

 

⏳ 예제 : 객체의 얕은 복사

//얕은 복사
void operator=(const Knight& k) {
    _hp = k._hp;
}

...

Knight k1;
Knight k3;	//복사 연산자를 이용한 얕은 복사
k3 = k1;

 

위와 같이 객체를 대입 연산으로 받으면, 클래스의 연산자 오버로딩이 자동으로 호출됨 (생략해도 원래 있음) 

 

이와 같은 모양이 자동 제공

 

💥 이때 객체의 멤버가 heap 영역을 참조할 경우 문제가 발생할 수 있음

 

마치 유도 미사일이 갈 곳이 없어지는 것처럼!

 

b. 복사 생성자 (copy constructor) = 깊은 복사

🌟 자신과 같은 클래스 타입의 다른 객체에 대한 참조(reference)를 인수로 전달받아, 그 참조를 가지고 자신을 초기화하는 방법

 

새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 가짐

 

⏳ 예제

Book(const Book& book);

Book book2(book1);
Book book2 = book1;	//둘이 동일

 

만약 복사중 멤버 변수로 어떠한 객체의 포인터를 가지고 있고 새로운 동적 할당을 해야 한다면

복사 생성자에서 따로 그 객체를 생성하는 코드를 작성해줘야 두번 공간을 삭제하는 일이 없어짐

 

⏳ 예제 : 복사 생성자의 객체 메모리 관리

#include <iostream>
using namespace std;

class Pet {
public:
	Pet() { cout << "Pet()" << endl; }
	~Pet() { cout << "~Pet()" << endl; }
	Pet(const Pet& p) {
		cout << "Pet(Pet& p)" << endl; 
	}
};
class Knight {
public:
	Knight() { 
		cout << "Knight()" << endl;
		_pet = new Pet();
	}
	~Knight() { 
		cout << "~Knight()" << endl; 
		delete _pet;
	}
	Knight(const Knight& k) {
		cout << "Knight(const Knight& k)" << endl;
		_pet = new Pet(*(k._pet));	//깊은 복사시 새로운 객체를 생성할 수 있게 해야 함
		_hp = k._hp;
	}
public:
	int _hp = 0;
	Pet* _pet;
};

int main() {
	Knight k1;
	k1._hp = 200;

	Knight k2 = k1;	//복사 생성자 이용한 깊은 복사
}

 

💙 복사 생성자를 사용하는 경우

 

1. 객체가 함수에 인수로 전달될 때

2. 함수가 객체를 반환값으로 반환할 때

3. 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화할 때

 

⏳ 예제 : 복사 생성자 사용

//복사 생성자
Knight(const Knight& other) {
    this->_hp = other._hp;
    this->_attack = other._attack;
    this->_defence = other._defence;
}

...

int main()
{
	Knight k1(100, 10, 1);
	Knight k2(k1);
    //==Knight k2 = Knight(k1);
    ...
}

 

 

 


c. 소멸자

1. 소멸자의 기초

a. 소멸자(destructor) 란?

🌟 사용이 끝난 객체를 정리해주는 멤버 함수

 

객체의 수명이 끝나면 컴파일러에 의해 자동으로 호출

 

b. 소멸자의 특징

  • 인수 ❌ 
  • 반환값 없지만 void형으로 선언 ❌
  • 객체는 여러 개의 생성자를 가질 수 있지만, 소멸자는 단 하나
  • 소멸자는 const, volatile 또는 static으로 선언 될 순 없지만, 그 것들로 선언된 객체의 소멸을 위해서 호출될 순 있음

 

c.소멸자의 선언

소멸자의 이름은 해당 클래스의 이름과 같으며 ~ 붙여 구분

클래스 소멸자의 원형은 클래스 선언의 public 영역에 포함

 

⏳ 예제 : Book 클래스의 소멸자 원형

public: 
	~Book();

 

💥 소멸자는 반환값이 없음에 주의

예를 들어 new 키워드로 동적 메모리에 할당 했다면, 소멸자에서는 delete 키워드로 반환해야 함 (안하면 누수)

 

⏳ 예제 : 소멸자 선언

Book::~Book() { }

 

d. 소멸자의 호출

소멸자의 호출 시기는 컴파일러가 알아서 처리

 

📊 객체가 선언된 메모리 영역별로 소멸자가 호출되는 시기

메모리 영역 소멸자 호출 시기
데이터(data) 영역 해당 프로그램이 종료될 때
스택(stack) 영역 해당 객체가 정의된 블록을 벗어날 때
힙(heap) 영역 delete를 사용하여 해당 객체의 메모리를 반환할 때
임시 객체 임시 객체의 사용을 마쳤을때

 

e. virtual 소멸자

자식 클래스의 소멸자가 실행되지 않으면

소멸자에 만약 어떤 메모리를 정리하고 있었던 상황이라면 큰 문제가 발생함 

 

이렇게 pet은 평생 delete를 하지 못하게 됨,,

 

가상 함수로 선언하게 되면, 형변환이 어떻게 되든 원본 데이터의 함수를 부르게 되니까

🌟꼭 소멸자는 필히 virtual을 사용해야 함

 

 

 


출처 : http://www.tcpschool.com/cpp/cpp_conDestructor_constructor

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

https://www.inflearn.com/course/%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%9E%85%EB%AC%B8-%EC%98%AC%EC%9D%B8%EC%9B%90-rookiss#curriculum

 

[게임 프로그래머 입문 올인원] C++ & 자료구조/알고리즘 & STL & 게임 수학 & Windows API & 게임 서버 -

어디부터 시작할지 막막한 게임 프로그래밍 입문자를 위한 All-In-One 커리큘럼입니다. C++, 자료구조/알고리즘, STL, 게임 수학, Windows API, 게임 서버 입문으로 이어지는 알찬 커리큘럼으로 게임 프

www.inflearn.com