Skip to content

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(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 영역에 포함되어야 한다.


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, ""(빈 문자열) 등으로 초기화를 진행한다.


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


Book::Book() {}


  • 디폴트 생성자는 클래스에 생성자가 단 하나도 정의되지 않았을 때만, 컴파일러에 의해 자동으로 제공된다.
  • 만약 사용자가 생성자를 단 하나라도 정의했다면, 위와 같은 객체의 선언은 오류를 발생시킬 것이다.
  • 따라서 위와 같이 초깃값을 명시하지 않고 객체를 생성하고 싶다면, 사용자가 직접 디폴트 생성자를 정의해야 한다.


1) 디폴트 생성자의 정의

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


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

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


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

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


Book();


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


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

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


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


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


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


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


Book web_book();


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


4. 복사 생성자

1) 얕은 복사와 깊은 복사

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


int x = 10;
int y = x;


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


Book web_book("HTML과 CSS", 350);
Book html_book = web_book;


  • 하지만 =를 이용한 객체의 대입은 얕은 복사로 수행된다.
  • 얕은 복사란 값을 복사하는 것이 아닌, 값을 가리키는 포인터를 복사하는 것이다.
  • 따라서 변수의 생성에서 =를 이용한 값의 복사는 문제가 되지 않지만, 객체에서는 문제가 발생할 수도 있다.
  • 특히 객체의 멤버가 메모리 공간의 힙(Heap) 영역을 참조할 경우에는 문제가 발생한다.


2) 복사 생성자

  • C++에서 복사 생성자란 자신과 같은 클래스 타입의 다른 객체에 대한 참조(Reference)를 인수로 전달 받아, 그 참조를 가지고 자신을 초기화하는 방법이다.
  • 복사 생성자는 새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 가지게 해준다.
  • 왜냐하면, 복사 생성자를 이용한 대입은 깊은 복사를 통한 값의 복사이기 때문이다.


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


Book(const 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); 구문은 컴파일러에 의해 다음과 같이 복사 생성자를 사용한 것으로 해석된다.


Book html_book = 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 클래스의 소멸자 원형은 다음과 같다.


~Book();


  • 위의 코드에서 클래스의 소멸자는 반환값이 없음에 주의한다.


  • 예를 들어 생성자에서 new 키워드를 이용해 동적으로 메모리를 할당했다면, 소멸자에서는 delete 키워드를 이용해 동적으로 할당 받은 메모리를 반환해야 한다.
  • 그렇지 않으면 해당 프로그램에 메모리 누수(Memory Leak)가 계속해서 발생하게 될 것이다.
  • 즉, 클래스 내부에서 new 키워드를 이용해 생성된 동적 메모리를 다시 해제하기 위해서 소멸자 내부에 delete 키워드를 포함해야 한다.


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


Book::~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 객체의 소멸자가 호출되었습니다.

References