본문 바로가기
C++

C++ 생성자 & 소멸자

by godfeeling 2020. 7. 5.

멤버 변수의 초기화

클래스를 가지고 객체를 생성하면, 해당 객체는 메모리에 즉시 생성됩니다.

 

하지만 이 객체는 모든 멤버 변수를 초기화하기 전에는 사용할 수 없습니다.

 

 

 

객체의 멤버 변수는 사용자나 프로그램이 일반적인 초기화 방식으로 초기화할 수 없습니다.

 

그 이유는 객체의 멤버 중에는 private 멤버도 있으므로, 이러한 private 멤버에 직접 접근할 수 없기 때문입니다.

 

 

 

따라서 private 멤버에 접근할 수 있는 초기화만을 위한 public 함수가 필요합니다.

 

이러한 초기화 함수는 객체가 생성된 후부터 사용되기 전까지 반드시 멤버의 초기화를 위해 호출되어야 합니다.

 

생성자(constructor)

C++에서는 객체의 생성과 동시에 멤버 변수를 초기화해주는 생성자(constructor)라는 멤버 함수를 제공합니다.

 

C++에서 클래스 생성자(constructor)의 이름은 해당 클래스의 이름과 같습니다.

 

, Book 클래스의 생성자는 Book()이 됩니다.

 

 

 

이러한 생성자는 다음과 같은 특징을 가집니다.

 

 

 

1. 생성자는 초기화를 위한 데이터를 인수로 전달받을 수 있습니다.

 

2. 생성자는 반환값이 없지만, void형으로 선언하지 않습니다.

 

3. 객체를 초기화하는 방법이 여러 개 존재할 경우에는 오버로딩 규칙에 따라 여러 개의 생성자를 가질 수 있습니다.

 

생성자의 선언

앞서 살펴본 Book 클래스의 생성자 원형은 다음과 같습니다.

 

예제

Book(const string& title, int total_page);

 

 

 

위의 예제처럼 클래스의 생성자는 어떠한 반환값도 명시하지 않음에 주의합니다.

 

 

 

다음 예제는 앞서 살펴본 Book 클래스의 생성자 원형에 따라 생성자 함수를 선언하는 예제입니다.

 

예제

Book::Book(const string& title, int total_page)

 

{

 

title_ = title; // 책의 제목을 초기화함.

 

total_page_ = total_page; // 책의 총 페이지를 초기화함.

 

current_page_ = 0; // 현재 페이지를 0으로 초기화함.

 

set_percent(); // 총 페이지와 현재 페이지로 해당 책을 읽은 정도를 초기화함.

 

}

 

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

생성자의 호출

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

 

 

 

다음 예제는 Book 클래스의 객체를 생성하면서 생성자가 암시적으로 호출되는 예제입니다.

 

예제

int main(void)

 

{

 

Book web_book("HTMLCSS", 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;

 

}

 

 

이때 생성자가 호출되었는지를 멤버 변수 percent_의 초기화 여부로 확인하고 있습니다.

 

멤버 변수 percent_는 생성자에 포함된 private 멤버 함수인 set_percent() 함수에 의해서만 0으로 초기화되기 때문입니다.

 

 

 

또한, 아래와 같이 사용자가 직접 생성자를 명시적으로 호출할 수도 있습니다.

 

예제

int main(void)

 

{

 

Book web_book = Book("HTMLCSS", 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;

 

}

 

디폴트 생성자(default constructor)

디폴트 생성자는 객체가 생성될 때 사용자가 초깃값을 명시하지 않으면, 컴파일러가 자동으로 제공하는 생성자입니다.

 

디폴트 생성자는 사용자로부터 인수를 전달받지 않으므로, 매개변수를 가지지 않습니다.

 

매개변수를 가지지 않으므로 대부분의 디폴트 생성자가 0이나 NULL, 빈 문자열 등으로 초기화를 진행합니다.

 

 

 

컴파일러가 제공하는 디폴트 생성자의 원형은 다음과 같습니다.

 

원형

Book::Book() { }

 

 

 

디폴트 생성자는 클래스에 생성자가 단 하나도 정의되지 않았을 때만, 컴파일러에 의해 자동으로 제공됩니다.

 

만약 사용자가 생성자를 단 하나라도 정의했다면, 위와 같은 객체의 선언은 오류를 발생시킬 것입니다.

 

따라서 위와 같이 초깃값을 명시하지 않고 객체를 생성하고 싶다면, 사용자가 직접 디폴트 생성자를 정의해야 합니다.

 

디폴트 생성자의 정의

C++에서 사용자가 직접 디폴트 생성자를 정의하는 방법은 다음과 같습니다.

 

 

 

1. 함수 오버로딩을 이용한 방법

 

2. 디폴트 인수를 이용한 방법

 

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

C++에서는 기존 생성자의 모든 인수에 디폴트 인수를 명시함으로써 디폴트 생성자를 정의할 수 있습니다.

 

 

 

예제

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

 

 

 

위의 예제처럼 모든 인수에 디폴트 값을 명시하면, 인수를 전달하지 않고도 객체를 생성할 수 있는 디폴트 생성자가 됩니다.

 

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

C++에서는 함수 오버로딩을 이용하여 매개변수를 가지지 않는 또 하나의 생성자를 정의할 수 있습니다.

 

예제

Book();

 

 

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

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

C++에서 디폴트 생성자를 가지는 객체는 다음과 같이 여러 가지 방법으로 선언할 수 있습니다.

 

예제

1. Book web_book; // 디폴트 생성자의 암시적 호출

 

2. Book web_book = Book(); // 디폴트 생성자의 명시적 호출

 

3. Book *ptr_book = new Book; // 디폴트 생성자의 암시적 호출

 

 

 

예제

int main(void)

 

{

 

Book web_book; // 디폴트 생성자의 암시적 호출

 

// 생성자가 호출되어 멤버 변수인 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;

 

}

 

 

하지만 다음과 같은 구문은 컴파일러가 객체의 선언이 아닌 함수의 호출로 보고 오류를 발생시킬 것입니다.

 

예제

Book web_book();

 

디폴트 생성자를 암시적으로 호출하고 싶을 때에는 괄호(())를 사용해서는 안 됩니다.

 

 

얕은 복사와 깊은 복사

새롭게 생성하는 변수에 다른 변수의 값을 대입하기 위해서는 대입 연산자(=)를 사용하면 됩니다.

 

예제

int x = 10;

 

int y = x;

 

 

 

마찬가지로 새롭게 생성하는 객체에 또 다른 객체의 값을 대입하기 위해서도 대입 연산자(=)를 사용할 수 있습니다.

 

예제

Book web_book("HTMLCSS", 350);

 

Book html_book = web_book;

 

 

 

하지만 대입 연산자를 이용한 객체의 대입은 얕은 복사(shallow copy)로 수행됩니다.

 

얕은 복사(shallow copy)란 값을 복사하는 것이 아닌, 값을 가리키는 포인터를 복사하는 것입니다.

 

따라서 변수의 생성에서 대입 연산자를 이용한 값의 복사는 문제가 되지 않지만, 객체에서는 문제가 발생할 수도 있습니다.

 

특히 객체의 멤버가 메모리 공간의 힙(heap) 영역을 참조할 경우에는 문제가 발생합니다.

 

복사 생성자(copy constructor)

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

 

복사 생성자는 새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 가지게 해줍니다.

 

왜냐하면, 복사 생성자를 이용한 대입은 깊은 복사(deep copy)를 통한 값의 복사이기 때문입니다.

 

 

 

Book 클래스의 복사 생성자의 원형은 다음과 같습니다.

 

예제

Book(const Book&);

 

 

 

복사 생성자는 다음과 같은 상황에서 주로 사용됩니다.

 

 

 

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

 

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

 

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

 

 

 

예제

Book::Book(const Book& origin) // 복사 생성자의 선언

 

{

 

title_ = origin.title_;

 

total_page_ = origin.total_page_;

 

current_page_ = origin.current_page_;

 

percent_ = origin.percent_;

 

}

 

int main(void)

 

{

 

Book web_book("HTMLCSS", 350);

 

Book html_book(web_book);

 

...

 

}

 

 

위의 예제는 복사 생성자를 이용해 새롭게 생성되는 html_book 객체를 같은 클래스의 web_book 객체로 초기화하고 있습니다.

 

위의 예제에서 Book html_book(web_book); 구문은 컴파일러에 의해 다음과 같이 복사 생성자를 사용한 것으로 해석됩니다.

 

 

소멸자(destructor)

C++에서 생성자는 객체 멤버의 초기화뿐만 아니라, 객체를 사용하기 위한 외부 환경까지도 초기화하는 역할을 합니다.

 

따라서 객체의 수명이 끝나면 생성자의 반대 역할을 수행할 멤버 함수도 필요해집니다.

 

이러한 역할을 하는 멤버 함수를 소멸자(destructor)라고 합니다.

 

소멸자는 객체의 수명이 끝나면 컴파일러에 의해 자동으로 호출되며, 사용이 끝난 객체를 정리해 줍니다.

 

 

 

C++에서 클래스 소멸자의 이름은 해당 클래스의 이름과 같으며, 이름 앞에 물결 표시(tilde, ~)를 붙여 생성자와 구분합니다.

 

, Book 클래스의 소멸자는 ~Book()이라는 이름을 가지게 됩니다.

 

 

 

이러한 소멸자는 다음과 같은 특징을 가집니다.

 

 

 

1. 소멸자는 인수를 가지지 않습니다.

 

2. 소멸자는 반환값이 없지만 void형으로 선언하지 않습니다.

 

3. 객체는 여러 개의 생성자를 가질 수 있지만, 소멸자는 단 하나만 가질 수 있습니다.

 

4. 소멸자는 const, volatile 또는 static으로 선언될 수는 없지만, const, volatile 또는 static으로 선언된 객체의 소멸을 위해서 호출될 수는 있습니다.

 

소멸자의 선언

앞서 살펴본 Book 클래스의 소멸자 원형은 다음과 같습니다.

 

소멸자 원형

~Book();

 

 

 

위의 예제에서 클래스의 소멸자는 반환값이 없음에 주의합니다.

 

 

 

예를 들어 생성자에서 new 키워드를 이용해 동적으로 메모리를 할당했다면, 소멸자에서는 delete 키워드를 이용해 동적으로 할당받은 메모리를 반환해야 합니다.

 

그렇지 않으면 해당 프로그램에 메모리 누수(memory leak)가 계속해서 발생하게 될 것입니다.

 

 

 

앞서 살펴본 Book 클래스의 소멸자는 객체를 소멸하는 것 이외에는 실제로 수행할 작업이 없습니다.

 

소멸자 선언

Book::~Book() { }

 

클래스 소멸자의 원형은 클래스 선언의 public 영역에 포함되어야 합니다.

소멸자의 호출

C++에서 소멸자의 호출 시기는 컴파일러가 알아서 처리하게 됩니다.

 

C++에서 객체가 선언된 메모리 영역별로 소멸자가 호출되는 시기는 다음과 같습니다.

 

메모리 영역

소멸자 호출 시기

데이터(data) 영역

해당 프로그램이 종료될 때

스택(stack) 영역

해당 객체가 정의된 블록을 벗어날 때

(heap) 영역

delete를 사용하여 해당 객체의 메모리를 반환할 때

임시 객체

임시 객체의 사용을 마쳤을 때

 

 

예제

Book::~Book() // 소멸자의 선언

 

{

 

// 프로그램이 종료될 때 컴파일러에 의해 자동으로 호출됨.

 

cout << "Book 객체의 소멸자가 호출되었습니다." << endl;

 

}

 

'C++' 카테고리의 다른 글

C++ OOP캡슐화  (0) 2020.07.05
C++ 연산자 오버로딩  (0) 2020.07.05
C++ 클래스  (0) 2020.07.05
C++ 범위  (0) 2020.07.05
C++ 함수  (0) 2020.07.05

댓글