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 소멸자
자식 클래스의 소멸자가 실행되지 않으면
소멸자에 만약 어떤 메모리를 정리하고 있었던 상황이라면 큰 문제가 발생함
가상 함수로 선언하게 되면, 형변환이 어떻게 되든 원본 데이터의 함수를 부르게 되니까
🌟꼭 소멸자는 필히 virtual을 사용해야 함
출처 : http://www.tcpschool.com/cpp/cpp_conDestructor_constructor
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ OPP 3대요소 : 캡슐화 (0) | 2023.09.14 |
---|---|
C++ 연산자 오버로딩 (0) | 2023.09.13 |
[자료형(data type)] C++ 파생형 클래스 1 : 클래스의 기본 (0) | 2023.09.13 |
C++ 범위 : C++ 네임스페이스 (0) | 2023.09.13 |
C++ 범위 : 유효범위와 연결 (0) | 2023.09.13 |