12. 생성자(Constructor)
1. 키워드
- 생성자(Constructor)
- 디폴트 생성자(Default Constructor)
- 복사 생성자(Copy Constructor)
- 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)
- 소멸자(Destructor)
2. 생성자
1) 멤버 변수의 초기화
- 클래스를 가지고 객체를 생성하면, 해당 객체는 메모리에 즉시 생성된다.
- 하지만 이 객체는 모든 멤버 변수를 초기화하기 전에는 사용할 수 없다.
- 객체의 멤버 변수는 사용자나 프로그램이 일반적인 초기화 방식으로 초기화할 수 없다.
- 그 이유는 객체의 멤버 중에는
private
멤버도 있으므로, 이러한private
멤버에 직접 접근할 수 없기 때문이다. - 따라서
private
멤버에 접근할 수 있는 초기화만을 위한public
함수가 필요하다. - 이러한 초기화 함수는 객체가 생성된 후부터 사용되기 전까지 반드시 멤버의 초기화를 위해 호출되어야 한다.
2) 생성자
- C++에서는 객체의 생성과 동시에 멤버 변수를 초기화해 주는 생성자라는 멤버 함수를 제공한다.
- C++에서 클래스 생성자의 이름은 해당 클래스의 이름과 같다.
- 즉,
Book
클래스의 생성자는Book()
이 된다. - 이러한 생성자는 다음과 같은 특징을 가진다.
1] 생성자는 초기화를 위한 데이터를 인수로 전달 받을 수 있다.
2] 생성자는 반환값이 없지만, void
타입으로 선언하지 않는다.
3] 객체를 초기화하는 방법이 여러 개 존재할 경우 오버로딩 규칙에 따라 여러 개의 생성자를 가질 수 있다.
3) 생성자의 선언
- 앞서 살펴본
Book
클래스의 생성자 원형은 다음과 같다.
- 위의 코드처럼 클래스의 생성자는 어떠한 반환값도 명시하지 않음에 주의한다.
- 다음의 코드는 앞서 살펴본
Book
클래스의 생성자 원형에 따라 생성자 함수를 선언하는 것이다.
Book::Book(const string &title, int total_page) {
title_ = title; // 책의 제목을 초기화함
total_page_ = total_page; // 책의 총 페이지를 초기화함
current_page_ = 0; // 현재 페이지를 0으로 초기화함
set_percent(); // 총 페이지와 현재 페이지로 해당 책을 읽은 정도를 초기화함
}
- 클래스 생성자의 원형은 클래스 선언의
public
영역에 포함되어야 한다.
4) 생성자의 호출
- C++에서는 클래스에서 객체를 생성할 때마다 해당 클래스의 생성자가 컴파일러에 의해 자동으로 호출된다.
- 다음의 코드는
Book
클래스의 객체를 생성하면서 생성자가 암시적으로 호출되는 것이다.
#include <iostream>
#include <string>
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_ << endl;
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
- 또는 다음과 같이 생성자 및 메서드를 클래스 안에 작성할 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int current_page_;
void set_percent() {
percent_ = (double)current_page_ / total_page_ * 100;
}
public:
Book(const string &title, int total_page) {
title_ = title;
total_page_ = total_page;
current_page_ = 0;
set_percent();
}
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_ << endl;
return 0;
}
// 0
- 이때 생성자가 호출되었는지를 멤버 변수
percent_
의 초기화 여부로 확인하고 있다. - 멤버 변수
percent_
는 생성자에 포함된private
멤버 함수인set_percent()
함수에 의해서만0
으로 초기화되기 때문이다.
- 또한, 다음과 같이 사용자가 직접 생성자를 명시적으로 호출할 수도 있다.
#include <iostream>
#include <string>
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_ << endl;
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
- 포인터와
new
키워드를 사용하여 명시적으로 호출할 수도 있다. - 이때
new
키워드를 사용하여 할당된 메모리는delete
키워드로 반드시 해제해 준다.
#include <iostream>
#include <string>
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 = new Book("웹 프로그래밍", 100); // 포인터와 new 키워드를 사용하여 명시적 호출
// 생성자가 호출되어 멤버 변수인 percent_가 초기화되었는지를 확인함
cout << web_book->percent_ << endl;
delete web_book; // 할당된 메모리 해제
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
3. 디폴트 생성자
- 디폴트 생성자는 객체가 생성될 때 사용자가 초깃값을 명시하지 않으면, 컴파일러가 자동으로 제공하는 생성자이다.
- 디폴트 생성자는 사용자로부터 인수를 전달 받지 않으므로, 매개변수를 가지지 않는다.
- 매개변수를 가지지 않으므로 대부분의 디폴트 생성자가
0
이나NULL
,""
(빈 문자열) 등으로 초기화를 진행한다.
- 컴파일러가 제공하는 디폴트 생성자의 원형은 다음과 같다.
- 디폴트 생성자는 클래스에 생성자가 단 하나도 정의되지 않았을 때만, 컴파일러에 의해 자동으로 제공된다.
- 만약 사용자가 생성자를 단 하나라도 정의했다면, 위와 같은 객체의 선언은 오류를 발생시킬 것이다.
- 따라서 위와 같이 초깃값을 명시하지 않고 객체를 생성하고 싶다면, 사용자가 직접 디폴트 생성자를 정의해야 한다.
1) 디폴트 생성자의 정의
- C++에서 사용자가 직접 디폴트 생성자를 정의하는 방법은 다음과 같다.
1] 함수 오버로딩을 이용한 방법
2] 디폴트 인수를 이용한 방법
(1) 함수 오버로딩을 이용한 디폴트 생성자의 정의
- C++에서는 함수 오버로딩을 이용하여 매개변수를 가지지 않는 또 하나의 생성자를 정의할 수 있다.
- 클래스는 단 하나의 디폴트 생성자만을 가질 수 있으므로, 둘 중 한 가지 방법으로만 디폴트 생성자를 정의해야 한다.
(2) 디폴트 인수를 이용한 디폴트 생성자의 정의
- C++에서는 기존 생성자의 모든 인수에 디폴트 인수를 명시함으로써 디폴트 생성자를 정의할 수 있다.
- 위의 코드처럼 모든 인수에 디폴트 값을 명시하면, 인수를 전달하지 않고도 객체를 생성할 수 있는 디폴트 생성자가 된다.
2) 디폴트 생성자를 가지는 객체의 선언
- C++에서 디폴트 생성자를 가지는 객체는 다음과 같이 여러 가지 방법으로 선언할 수 있다.
1. Book web_book; // 디폴트 생성자의 암시적 호출
2. Book web_book = Book(); // 디폴트 생성자의 명시적 호출
3. Book *ptr_book = new Book; // 디폴트 생성자의 암시적 호출
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int current_page_;
void set_percent();
public:
Book(const string &title = "웹 프로그래밍", int total_page = 100);
string title_;
int total_page_;
double percent_;
void Move(int page);
void Open();
void Read();
};
int main(void) {
Book web_book; // 디폴트 생성자의 암시적 호출
// 생성자가 호출되어 멤버 변수인 percent_가 초기화되었는지를 확인함
cout << web_book.percent_ << endl;
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
- 하지만 다음과 같은 구문은 컴파일러가 객체의 선언이 아닌 함수의 호출로 보고 오류를 발생시킬 것이다.
- 즉, 디폴트 생성자를 암시적으로 호출하고 싶을 때에는
()
(괄호)를 사용해서는 안 된다.
4. 복사 생성자
1) 얕은 복사와 깊은 복사
- 새롭게 생성하는 변수에 다른 변수의 값을 대입하기 위해서는
=
(대입 연산자)를 사용하면 된다.
- 마찬가지로 새롭게 생성하는 객체에 또 다른 객체의 값을 대입하기 위해서도
=
를 사용할 수 있다.
- 하지만
=
를 이용한 객체의 대입은 얕은 복사로 수행된다. - 얕은 복사란 값을 복사하는 것이 아닌, 값을 가리키는 포인터를 복사하는 것이다.
- 따라서 변수의 생성에서
=
를 이용한 값의 복사는 문제가 되지 않지만, 객체에서는 문제가 발생할 수도 있다. - 특히 객체의 멤버가 메모리 공간의 힙(Heap) 영역을 참조할 경우에는 문제가 발생한다.
2) 복사 생성자
- C++에서 복사 생성자란 자신과 같은 클래스 타입의 다른 객체에 대한 참조(Reference)를 인수로 전달 받아, 그 참조를 가지고 자신을 초기화하는 방법이다.
- 복사 생성자는 새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 가지게 해준다.
- 왜냐하면, 복사 생성자를 이용한 대입은 깊은 복사를 통한 값의 복사이기 때문이다.
Book
클래스의 복사 생성자의 원형은 다음과 같다.
- 복사 생성자는 다음과 같은 상황에서 주로 사용된다.
1] 객체가 함수에 인수로 전달될 때
2] 함수가 객체를 반환값으로 반환할 때
3] 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화할 때
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int current_page_;
void set_percent();
public:
Book(const string &title, int total_page);
Book(const Book &); // 복사 생성자의 원형 정의
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);
Book html_book(web_book);
cout << "첫 번째 책의 제목은 " << web_book.title_
<< "이고, 총 페이지는 " << web_book.total_page_ << "장입니다." << endl;
cout << "두 번째 책의 제목은 " << html_book.title_
<< "이고, 총 페이지는 " << html_book.total_page_ << "장입니다." << endl;
return 0;
}
Book::Book(const string &title, int total_page) {
title_ = title;
total_page_ = total_page;
current_page_ = 0;
set_percent();
}
Book::Book(const Book &origin) { // 복사 생성자의 선언
title_ = origin.title_;
total_page_ = origin.total_page_;
current_page_ = origin.current_page_;
percent_ = origin.percent_;
}
void Book::set_percent() {
percent_ = (double)current_page_ / total_page_ * 100;
}
// 첫 번째 책의 제목은 HTML과 CSS이고, 총 페이지는 350장입니다.
// 두 번째 책의 제목은 HTML과 CSS이고, 총 페이지는 350장입니다.
- 위의 코드는 복사 생성자를 이용해 새롭게 생성되는
html_book
객체를 같은 클래스의web_book
객체로 초기화하고 있다. Book html_book(web_book);
구문은 컴파일러에 의해 다음과 같이 복사 생성자를 사용한 것으로 해석된다.
- 다음의 코드처럼 복사 생성자를 이용해 포인터 객체로 생성할 수도 있다.
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int current_page_;
void set_percent();
public:
Book(const string &title, int total_page);
Book(const Book &); // 복사 생성자의 원형 정의
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);
Book *html_book = new Book(web_book);
cout << "첫 번째 책의 제목은 " << web_book.title_
<< "이고, 총 페이지는 " << web_book.total_page_ << "장입니다." << endl;
cout << "두 번째 책의 제목은 " << html_book->title_
<< "이고, 총 페이지는 " << html_book->total_page_ << "장입니다." << endl;
delete html_book;
return 0;
}
Book::Book(const string &title, int total_page) {
title_ = title;
total_page_ = total_page;
current_page_ = 0;
set_percent();
}
Book::Book(const Book &origin) { // 복사 생성자의 선언
title_ = origin.title_;
total_page_ = origin.total_page_;
current_page_ = origin.current_page_;
percent_ = origin.percent_;
}
void Book::set_percent() {
percent_ = (double)current_page_ / total_page_ * 100;
}
5. 소멸자
- C++에서 생성자는 객체 멤버의 초기화뿐만 아니라, 객체를 사용하기 위한 외부 환경까지도 초기화하는 역할을 한다.
- 따라서 객체의 수명이 끝나면 생성자의 반대 역할을 수행할 멤버 함수도 필요해진다.
- 이러한 역할을 하는 멤버 함수를 소멸자라고 한다.
- 소멸자는 객체의 수명이 끝나면 컴파일러에 의해 자동으로 호출되며, 사용이 끝난 객체를 정리해 준다.
- C++에서 클래스 소멸자의 이름은 해당 클래스의 이름과 같으며, 이름 앞에
~
(물결 표시)를 붙여 생성자와 구분한다. - 즉,
Book
클래스의 소멸자는~Book()
이라는 이름을 가지게 된다. - 이러한 소멸자는 다음과 같은 특징을 가진다.
1] 소멸자는 인수를 가지지 않는다.
2] 소멸자는 반환값이 없지만 void
타입으로 선언하지 않는다.
3] 객체는 여러 개의 생성자를 가질 수 있지만, 소멸자는 단 하나만 가질 수 있다.
4] 소멸자는 const
, volatile
또는 static
으로 선언될 수는 없지만, const
, volatile
또는 static
으로 선언된 객체의 소멸을 위해서 호출될 수는 있다.
1) 소멸자의 선언
- 앞서 살펴본
Book
클래스의 소멸자 원형은 다음과 같다.
- 위의 코드에서 클래스의 소멸자는 반환값이 없음에 주의한다.
- 예를 들어 생성자에서
new
키워드를 이용해 동적으로 메모리를 할당했다면, 소멸자에서는delete
키워드를 이용해 동적으로 할당 받은 메모리를 반환해야 한다. - 그렇지 않으면 해당 프로그램에 메모리 누수(Memory Leak)가 계속해서 발생하게 될 것이다.
- 즉, 클래스 내부에서
new
키워드를 이용해 생성된 동적 메모리를 다시 해제하기 위해서 소멸자 내부에delete
키워드를 포함해야 한다.
- 앞서 살펴본
Book
클래스의 소멸자는 객체를 소멸하는 것 이외에는 실제로 수행할 작업이 없다.
- 클래스 소멸자의 원형은 클래스 선언의
public
영역에 포함되어야 한다.
2) 소멸자의 호출
- C++에서 소멸자의 호출 시기는 컴파일러가 알아서 처리하게 된다.
- C++에서 객체가 선언된 메모리 영역별로 소멸자가 호출되는 시기는 다음과 같다.
1] 데이터(Data) 영역
- 해당 프로그램이 종료될 때
2] 스택(Stack) 영역
- 해당 객체가 정의된 블록을 벗어날 때
3] 힙(Heap) 영역
delete
를 사용하여 해당 객체의 메모리를 반환할 때
4] 임시 객체
- 임시 객체의 사용을 마쳤을 때
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int current_page_;
void set_percent();
public:
Book(const string &title, int total_page);
~Book(); // 소멸자의 원형 정의
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);
cout << web_book.percent_ << endl;
return 0;
}
Book::Book(const string &title, int total_page) {
title_ = title;
total_page_ = total_page;
current_page_ = 0;
set_percent();
}
Book::~Book() { // 소멸자의 선언
// 프로그램이 종료될 때 컴파일러에 의해 자동으로 호출됨
cout << "Book 객체의 소멸자가 호출되었습니다." << endl;
}
void Book::set_percent() {
percent_ = (double)current_page_ / total_page_ * 100;
}
// 0
// Book 객체의 소멸자가 호출되었습니다.