아래 글은 퍼온 글입니다.

원본 주소는 아래와 같습니다.

http://yatoyato.tistory.com/878



Constructor / Destructor

생성자의 필요성

개인의 기본정보(이름, 전화번호, 나이)를 담고있는 person이라는 이름의 클래스를 정의해 본다. 단 person클래스의 기본기능은 데이터를 저장하고 출력하는 것으로 제한한다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

const int SIZE=20;

class person { 
    char name[SIZE]; 
    char phone[SIZE]; 
    int age;

public: 
    void showData(); 
};

void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
}

int main() { 
    person p={"KIM", "013-113-1113", 22}; 
    p.showData();

    return 0; 
}

메인함수 내에서 p라는 이름의 객체를 "생성과 동시에 초기화"하려고 한다. 구조체변수 초기화는 가능했지만 여기에서는 컴파일러가 에러를 발생시킨다. 문제는 초기화하고자 하는 멤버들이 private로 선언되어 있다는 것이다. 즉 클래스의 내부가 아니라 외부에 해당이 되므로 접근이 허용되지 않는다.

문제를 해결하는 가장 쉬운 방법은 멤버들을 public으로 선언하는 것이다. 그러나 이는 정보은닉에 위배되므로 다음과 같은 방법을 택하기로 결정내렸다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

const int SIZE=20;

class person { 
    char name[SIZE]; 
    char phone[SIZE]; 
    int age;

public: 
    void showData(); 
    void setData(char *_name, char *_phone, int _age); 
};

void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
}

void person::setData(char *_name, char *_phone, int _age) { 
    strcpy(name, _name); 
    strcpy(phone, _phone); 
    age=_age; 
}

int main(void) { 
    person p; 
    p.setData("KIM", "013-333-5555", 22); 
    p.showData();

    return 0; 
}

멤버변수를 초기화하기 위한 멤버함수가 선언되어 있다. 함수의 전달된 인자를 통해서 멤버변수에 단순히 복사만 하고 있지만, 필요한 경우에는 데이터의 유효성 검사에 관련된 코드도 삽입이 가능하다. 메인함수 내에서는 p라는 이름의 객체를 생성만 하고 있다. 따라서 멤버변수들은 쓰레기 값으로 초기화될 것이다. 그리고 그 다음 줄에서는 원하는 값으로 멤버들을 초기화하고 있다.

이는 궁극적으로 우리가 하고자 했던 "생성과 동시에 초기화"라는 것과는 다소 거리가 멀어진 것이다. 위와 같은 클래스의 정의는 프로그래머에게 제약을 가하는 형태가 된다. 프로그래머는 다음과 같은 사항을 반드시 기억해야 한다. 그러나 이는 바람직한 형태가 아니다.

"person클래스는 객체생성후 초기화를 해야 하는데, 이때 사용되는 함수가 setData이다."

뿐만 아니라, 대부분의 객체는 생성과 동시에 특정한 값을 지니는 것이 좋은 형태이다. 왜냐하면, 생성과 동시에 초기화한다는 개념은 객체지향에서 또다른 의미를 지니기 때문이다. 그렇다면 객체를 생성과 동시에 초기화할 수 있는 메커니즘이 제공되어야 한다고 결론내릴 수 있다. 그래서 제공되는 메커니즘이 바로 생성자(Constructor)이다.

다음과 같은 형태의 초기화는 허용되지 않는다.(JAVA와 C#과 같은 언어의 클래스 선언에서는 허용이 된다. 그래서 많이 헷갈려 한다.)

class AAA { 
    int a=10; 
    int b=20; 
};

클래스를 선언하는데 있어서 멤버를 초기화하는 것은 문법적 오류이다. 이는 구조체를 선언하면서 멤버를 초기화하지 못하는 것과 같은 이치이다.

생성자와 객체의 생성과정

c++에서의 객체 생성과정은 두단계로 나눠서 이야기할 수 있다. 그 두 단계는 다음과 같으며, 모든 객체는 반드시 이 과정을 거치게 되어 있다.

  1. 메모리 할당
  2. 생성자 호출

생성자에 대한 외형적 특징은 다음과 같다.

  • 함수이다.
  • 클래스의 이름과 같은 이름을 지닌다.
  • 리턴하지도 않고, 리턴타입도 선언되지 않는다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

const int SIZE=20;

class AAA { 
    int i, j;

public: 
    AAA() { 
        cout<<"생성자 호출"<<endl; 
        i=10, j=20; 
    }

    void showData() { 
        cout<<i<<' '<<j<<endl; 
    } 
};

int main(void) { 
    AAA aaa; 
    aaa.showData(); 
    return 0; 
}

클래스 안에 정의되어 있는 한 함수는 독특하게도 리턴타입이 선언되어 있지도 않고, 리턴하지도 않는다. 뿐만 아니라 클래스의 이름과 함수이름이 같다. 따라서 이 함수는 생성자이다. 앞에서 이야기한 생성자의 외형적 특징과 일치한다.

실행결과를 통해서 알 수 있는 사실은 생성자가 호출되었다는 사실이다. 이는 두가지 측면에서 확인이 가능한데, 첫번째로 "생성자 호출"이라는 메시지가 출력되었다는 것이고, 두번째는 멤버변수 i와 j값이 각각 10과 20으로 출력되었다는 것이다. 생성자 내를 보면 i와 j값을 각각 10과 20으로 초기화하고 있다는 것을 알 수 있다.

메인함수 내에서는 클래스 AAA의 객체 aaa를 생성하고 있다. 따라서 제일 먼저 메모리 공간이 할당되고 aaa라는 이름이 붙게 될 것이다. 물론 메모리공간만 할당이 되고 초기화는 이뤄지지 않는 상태이므로 멤버변수는 '쓰레기값'을 지니게 된다. 두번째 단계는 생성자의 호출이다. 따라서 생성자가 호출되면서 i와 j는 각각 10과 20으로 초기화된다.

다음 예제에서 위의 예제와 다른 부분은 정의되어 있는 생성자의 형태이다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

const int SIZE=20;

class AAA { 
    int i, j;

public: 
    AAA(int _i, int _j) { 
        i=_i, j=_j; 
    } 
    void showData() { 
        cout<<i<<' '<<j<<endl; 
    } 
};

int main(void) { 
    AAA aaa(111, 222); 
    aaa.showData(); 
    return 0; 
}

위 예제의 생성자는 함수의 리턴타입이 없고, 클래스와 이름이 같으므로 분명히 생성자다. 다만 매개변수 선언이 있을 뿐이다. 즉 "AAA클래스의 객체를 생성하되 이름은 aaa라 하고, 생성자 호출시 111과 222를 인자로 전달받을 수 있는 생성자를 호출하라"라는 의미이다. 따라서 aaa객체의 멤버변수 i와 j는 각각 111과 222로 초기화될 것이다.

위의 예제는 상당히 의미있는 예제이다. 왜냐하면 객체를 생성과 동시에 초기화하되, 원하는 값으로 초기화했기 때문이다. 이렇듯 생성자는 객체생성시 원하는 값으로 초기화하기 위한 용도로 사용이 된다. 물론 다른 용도로 사용할 수도 있지만, 그렇게 되면 적절한 형태의 생성자와는 거리가 멀어질 수도 있다. 가급적이면 생성자는 멤버변수를 초기화하는 용도로만 사용해야 한다.

객체생성문법은 크게 세 개의 영역으로 나뉘어진다. 제일 먼저 클래스의 이름이 나오고, 그 다음으로 객체의 이름이 등장한다. 그리고 마지막으로 객체생성시 호출하고자 하는 생성자에 대한 정보가 등장한다. 그러나 호출하고자 하는 생성자에 대한 정보가 없다면, void생성자, 즉 인자값을 받지않는 생성자를 호출하라는 의미로 받아 들여지게 된다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

const int SIZE=20;

class person { 
    char name[SIZE]; 
    char phone[SIZE]; 
    int age;

public: 
    person(char *_name, char *_phone, int _age); 
    void showData(); 
};

person::person(char *_name, char *_phone, int _age) { 
    strcpy(name, _name); 
    strcpy(phone, _phone); 
    age=_age; 
}

void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
}

int main(void) { 
    person p("KIM", "013-333-5555", 22); 
    p.showData();

    return 0; 
}

객체생성과정에서 첫번째, 두번째 인자로 문자열을, 세번째 인자로 int형 정수를 받을 수 있는 생성자의 호출을 요구하고 있다. 이러한 형태의 생성자가 존재한다면 무리없이 객체가 생성되겠지만, 존재하지 않는다면 컴파일오류가 발생하게 된다.

클래스 내에서는 생성자가 선언되어 있다. 그런데 이 생성자는 첫번째, 두번째 인자로 문자열을, 세번째 인자로 int형 정수를 받겠다고 선언되어 있다. 따라서 메인함수 내에서 선언하고 있는 객체 p가 생성되는 과정에서 요구하는 형태의 생성자가 된다.

사실 C++에서는 두가지 형태의 객체생성방법을 제시하고 있다. 첫번째 방법은 이미 앞에서 언급한 방법이다.

person p("KIM", "013-333-5555", 22);

두번째 방법은 명시적으로 생성자를 호출하는 방법이다.

person p = person("KIM", "013-333-5555", 22);

위의 두 문장은 같은 의미를 지닌다. 그러나 상대적으로 간결한 첫번째 방법이 주로 사용된다. 그래서 첫번째 방법을 대표적으로 소개한다.

public생성자, private생성자

지금까지 보아온 생성자들은 모두 public으로 선언되어 있다. 그렇지 않으면 클래스 외부에서 객체생성을 할 수 없기 때문이다.

class AAA { 
private: 
    AAA(){} 
};

int main() { 
    AAA aaa; 
    return 0; 
};

aaa라는 이름의 객체를 생성하되, 인자값을 받지않는 void생성자의 호출을 요구하고 있다. 그런데 void생성자의 호출요구가 들어간 곳은 main함수 영역이다.(main함수 내에 객체생성 문장이 존재하므로) 이는 AAA클래스의 외부에 해당하므로 만약에 생성자가 public으로 선언되어 있지 않다면 문제가 된다. 왜냐하면 접근이 불가능하기 때문이다.

결국 위의 예제는 컴파일 오류를 발생시킨다. 호출할 만한 적절한 형태의 생성자를 AAA클래스가 지니고 있지 않기 때문이다. 클래스 내의 생성자는 private로 선언되어 있다. 따라서 생성자는 일반적으로 public멤버로 선언되기 마련이다.

디폴트 생성자와 생성자의 특징

생성자가 지니는 특징은 다음과 같다.

  • 생성자를 하나도 정의하지 않으면 디폴트(default) 생성자가 자동삽입된다.
  • 생성자도 함수이므로 오버로딩이 가능하다.
  • 생성자도 함수이므로 디폴트 매개변수의 설정이 가능하다.

객체의 생성순서는 첫번째가 메모리 할당이고, 두번째가 생성자 호출이다. 실제로 C++의 모든 객체들은 생성과정에서 반드시 한번 생성자가 호출되게끔 되어있다. 따라서 첫번째 순서에 의해서 메모리만 할당된 경우 이는 객체라고 부를 수 없다. 반드시 두번째 순서에 의해서 생성자가 호출되어야만 객체라고 부를 수 있는 것이다.

생성자가 명시되지 않은 클래스의 경우에도 생성자의 호출은 있다. 다만 우리가 정의한 생성자가 아니라, 컴파일러에 의해서 자동으로 삽입된 생성자가 호출되었던 것이다. 이러한 생성자를 가리켜 디폴트(Default) 생성자라 한다.

class point { 
    int x, y; 
public: 
    point() {} 
}

위 클래스에는 생성자가 정의되어 있다. 이 생성자는 인자값을 받지 않는 void생성자인 동시에 아무런 기능도 지니지 않는다. 이것이 바로 디폴트 생성자의 형태이다. 즉 클래스를 정의하는데 있어서 프로그래머가 생성자를 하나도 정의하지 않으면, 인자값도 받지 않고, 아무런 기능도 지니지 않는 디폴트 생성자가 자동으로 삽입된다. 따라서 다음의 두 클래스의 정의는 완전히 같은 것이다.

class point { 
    int x, y;

public: 
    void print() {...} 
}

class point { 
    int x, y;

public: 
    point() {} 
    void print() {...} 
}

다음 예제는 컴파일하는 과정에서 에러메시지를 출력한다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class point { 
    int x, y;

public: 
    point(int _x, int _y) { 
        x=_x; 
        y=_y; 
    } 
    void showData() { 
        cout<<x<<' '<<y<<endl; 
    } 
};

int main(void) { 
    point p1(10, 20); 
    p1.showData();

    point p2; 
    p2.showData();

    return 0; 
}

main함수 내를 보면 10과 20을 인자로 전달받을 수 있는 생성자를 호출하면서 객체를 생성하려고 한다. point클래스의 정의를 보면 두 개의 int형 정수를 인자로 받을 수 있는 생성자가 정의되어 있다. 따라서 문제없이 객체가 생성된다.

그러나 다음의 객체생성은 문제가 된다. 명시적으로 어떠한 생성자를 호출하겠다는 표현이 없으므로, 인자값을 받지 않는 void생성자를 호출하려고 할 텐데, 문제는 void생성자가 존재하지 않는다는 것이다. 앞에서도 이야기했듯이 프로그래머가 정의해놓은 생성자가 하나라도 존재하면 default생성자가 자동으로 삽입되지 않기 때문이다. 따라서 컴파일시 에러메시지를 출력하게 된다.

위 예제의 문제점은 void생성자를 하나 더 넣어주면 간단히 해결된다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class point { 
    int x, y;

public: 
    point() { 
        x=y=0; 
    } 
    point(int _x, int _y) { 
        x=_x, y=_y; 
    }

    void showData() { 
        cout<<x<<' '<<y<<endl; 
    } 
};

int main(void) { 
    point p1(10, 20); 
    p1.showData();

    point p2; 
    p2.showData();

    return 0; 
}

위 예제는 void생성자가 존재하고, int형 정수 두개를 인자로 받는 생성자가 존재한다. 이렇듯 생성자도 오버로딩이 가능하다. 생성자도 함수이기 때문에 함수의 특징을 그대로 지니고 있다. point클래스는 두 개의 생성자를 지니고 있으므로, 두 가지 형태로 객체생성이 가능하다. 각각의 객체는 매개변수에 따라 각각 다른 생성자를 호출한다.

위의 예제말고도 또다른 해법이 존재한다. 생성자도 함수이므로 오버로딩뿐만 아니라, 디폴트 매개변수를 설정할 수 있기 때문이다. 즉 위의 예제는 다음과 같은 형태로 달리 표현될 수 있다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class point { 
    int x, y;

public: 
    point(int _x=0, int _y=0) { 
        x=_x, y=_y; 
    }

    void showData() { 
        cout<<x<<' '<<y<<endl; 
    } 
};

int main(void) { 
    point p1(10, 20); 
    p1.showData();

    point p2; 
    p2.showData();

    point p3(10); 
    p3.showData();

    return 0; 
}

생성자에 디폴트 매개변수가 설정되어 있음을 알 수 있다. 따라서 int형 정수 두개를 인자로 전달하는 방법이외에 다음과 같은 형태의 객체생성이 가능하다.

생성자와 동적할당

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class person { 
    char *name; 
    char *phone; 
    int age;

public: 
    person(char *_name, char *_phone, int _age); 
    void showData(); 
};

person::person(char *_name, char *_phone, int _age) { 
    name=new char[strlen(_name)+1]; 
    strcpy(name, _name);

    phone=new char[strlen(_phone)+1]; 
    strcpy(phone, _phone); 
    age=_age; 
void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
}

int main(void) { 
    person p("KIM", "013-333-5555", 22); 
    p.showData();

    return 0; 
}

main함수 내에서 p라는 이름의 객체를 생성하고 있다. 따라서 객체생성 순서에 의해서 제일 먼저 메모리 공간이 할당되고, p라는 이름을 부여하게 된다. 그 다음으로 생성자를 호출하면서 문자열들과 정수가 인자로 전달된다. 생성자 내에서는 전달된 문자열의 길이를 계산해서 정확한 크기만큼 메모리 공간을 할당하고 문자열을 복사하고 있다.

.. strlen()은 문자열의 길이를 계산하여 값을 반환하는데, '\0'은 포함하지 않는다.

결과적으로 객체 p는 main함수 내에서 생성되었으므로, 스택stack영역에 할당이 되겠지만, 생성자 내에서 메모리공간을 동적할당하고 있기 때문에, 멤버변수 name과 phone이 가리키는 메모리공간은 힙heap이 된다. 
이러한 형태의 초기화가 주는 이점은 메모리 공간을 효율적으로 사용할 수 있다는 것이다.

이러한 동적할당은 메모리공간을 효율적으로 활용한다는 이점은 있지만, 이렇게 클래스를 디자인할 경우 생각해 봐야 할 문제가 많아지기 때문이다. 문자열의 길이가 크게 유동적이지 않다면 동적할당을 사용하지 않는 클래스가 더 좋은 디자인에 속한다. 그럼에도 불구하고 생성자 내에서 메모리 공간을 동적할당해야 할 상황은 앞으로 많이 등장하게 된다.

위 예제는 한가지 중요한 문제를 지니고 있다. 그것은 생성자 내에서 동적할당한 메모리 공간을 어디서도 해제해주지 않고 있다는 것이다. 이러한 경우 메모리 누수(유출)현상이 발생하게 된다. 다음 예제는 문제를 해결한 것이다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class person { 
    char *name; 
    char *phone; 
    int age;

public: 
    person(char *_name, char *_phone, int _age); 
    void showData(); 
    void delMemory(); 
};

person::person(char *_name, char *_phone, int _age) { 
    name=new char[strlen(_name)+1]; 
    strcpy(name, _name);

    phone=new char[strlen(_phone)+1]; 
    strcpy(phone, _phone); 
    age=_age; 
void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
void person::delMemory() { 
    delete []name; 
    delete []phone; 
}

int main(void) { 
    person p("KIM", "013-333-5555", 22); 
    p.showData(); 
    p.delMemory();

    return 0; 
}

이전 예제와 차이를 보이는 부분은 생성자에서 동적할당한 메모리공간을 해제하는 함수를 멤버로 포함시킨 것이다. 언뜻 보면 문제가 해결된 듯하다. 그러나 이러한 클래스는 프로그래머에게 제약을 가하는 형태가 된다. 프로그래머는 다음과 같은 사항을 반드시 기억해야 한다.

"person클래스의 객체는 소멸되기 전에 반드시 delMemeory함수를 호출해야 한다."

이는 바람직한 형태가 아니다. 이러한 형태의 클래스가 대략 5개 정도가 된다고 할 때 신경쓸 일이 한두가지가 아니다. 그래서 등장한 메커니즘이 바로 소멸자(Destructor)이다.

소멸자의 특징과 필요성

경우에 따라서 객체는 소멸되기 전에 다양한 형태의 정리작업이 필요하다.(동적할당한 메모리 공간의 해제) 이러한 일들이 객체소멸시 자동적으로 처리된다면 프로그래머는 그만큼 부담을 덜 수 있다. 이러한 목적으로 사용되는 것이 소멸자이다.

객체 소멸과정도 객체의 생성과정과 마찬가지로 두단계를 거친다. 그 두 단계는 다음과 같으며, 모든 객체는 반드시 이 과정을 거쳐서 소멸된다.

  1. 소멸자 호출
  2. 메모리 반환(해제)

소멸자에 대한 외형적 특징은 다음과 같다.

  • 함수이다.
  • 클래스의 이름 앞에 ~가 붙은 형태의 이름을 지닌다.
  • 리턴하지도 않고, 리턴타입도 선언되지 않는다.
  • 매개변수를 받을 수 없다. 따라서 오버로딩도 불가능하고, 디폴티 매개변수 선언도 불가능하다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class AAA { 
public: 
    AAA() { 
        cout<<"생성자 호출"<<endl; 
    } 
    ~AAA() { 
        cout<<"소멸자 호출"<<endl; 
    } 
};

int main(void) { 
    AAA aaa1; 
    AAA aaa2;

    return 0; 
}

클래스 이름앞에 ~가 붙어서 구성이 되어 있는 함수가 존재한다. 물론 리턴타입도 없으며, 인자도 받지않는 void함수이다. 따라서 이 함수는 소멸자의 조건에 충족되고, 그러므로 객체소멸시 자동적으로 호출된다.

위 예제의 실행결과를 통해서 객체가 소멸되기 이전에 소멸자가 먼저 호출된다는 것을 확인할 수 있다. 메인함수 내에서 두 개의 객체가 생성될 것이다. 따라서 두 번의 생성자 호출에 의해 "생성자 호출"이라는 메시지가 두번 출력된다. 그러고 나서 main함수가 종료되면서 지역적으로 선언된 객체 aaa1과 aaa2가 소멸되므로,(사실 main함수호출이 종료되면 프로그램 자체가 종료되지만) 두번의 소멸자 호출에 의해 "소멸자 호출"이라는 메시지가 두번 출력된다.

소멸자의 가장 중요한 특징은 객체소멸시 반드시 한번 호출된다는 것이다.

#include <iostream> 
using std::cout; 
using std::cin; 
using std::endl;

class person { 
    char *name; 
    char *phone; 
    int age;

public: 
    person(char *_name, char *_phone, int _age); 
    ~person(); 
    void showData(); 
};

person::person(char *_name, char *_phone, int _age) { 
    name=new char[strlen(_name)+1]; 
    strcpy(name, _name);

    phone=new char[strlen(_phone)+1]; 
    strcpy(phone, _phone);

    age=_age; 
person::~person() { 
    delete []name; 
    delete []phone; 
void person::showData() { 
    cout<<"name: "<<name<<endl; 
    cout<<"phone: "<<phone<<endl; 
    cout<<"age: "<<age<<endl; 
}

int main(void) { 
    person p("KIM", "013-333-5555", 22); 
    p.showData();

    return 0; 
}

클래스 내에 정의되어 있는 생성자를 보면 메모리를 동적할당하고 있으며, 그 아래 정의되어 있는 소멸자를 보면 동적할당된 메모리 공간을 해제하고 있다. 따라서 객체생성시 동적할당이 이뤄지고, 객체소멸시 할당된 메모리 공간이 해제될 것이다.

일반적으로 클래스를 정의할 때에는 멤버변수의 초기화를 위해서 생성자를 항상 정의하기 마련이다. 그러나 소멸자는 다르다. 객체소멸시 처리해야 할 일이 있을 때에만 프로그래머가 정의한다. 좀더 구체적으로 말하면 다음과 같다.

"생성자 내에서 메모리를 동적할당하는 경우, 이를 해제하기 위해서 반드시 소멸자를 정의해야 한다."

디폴트 소멸자

소멸자를 정의하지 않더라도 디폴트 소멸자가 자동으로 삽입된다. 디폴트 생성자는 아무런 일도 하지 않는다. 따라서 다음의 두 코드는 서로 완전히 같은 것이다.

class point { 
    int x, y;

public: 
    void print() {...} 
}

class point { 
    int x, y;

public: 
    point() {} 
    ~point() {} 
    void print() {...} 
}

첫번째 코드를 보면 생성자와 소멸자가 정의되어 있지 않다. 따라서 디폴트 생성자와 디폴트 소멸자가 삽입될 것이다. 반면에 오른쪽에 있는 코드는 생성자와 소멸자를 정의해 주고 있는데, 그 형태가 디폴트 생성자와 디폴트 소멸자와 같다. 따라서 이 둘은 완전히 같은 클래스의 정의에 해당한다.

, .