- 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들 수 있다.
C++이 자동 생성하는 함수들
C++ 클래스는 생성자와 소멸자, 대입 연산자를 포함한다.
생성자는 새로운 객체를 메모리에 만드는데 필요한 과정을 제어하고 객체의 초기화를 진행한다.
소멸자는 객체를 없앰과 동시에 그 객체가 메모리에서 적절히 사라질 수 있도록 하는 과정을 제어한다.
대입 연산자는 기존의 객체에 다른 객체의 값을 줄 때 사용하는 함수이다.
이러한 것들은 유용하면서도 위험할 수 있는 함수들이다.
C++이 스스로 만드는 함수
C++은 복사 생성자, 복사 대입 연산자, 소멸자를 직접 선언하지 않으면 스스로 생성한다.
이들은 모두 public 멤버이며 inline함수이다.
class Empty{};
만약 위와 같이 작성했다면 다음과 같이 변한다.
class Empty {
public:
Empty() { ... } // 기본 생성자
Empty(const Empty& rhs) { ... } // 복사 생성자
~Empty() { ... } // 소멸자
Empty& operator=(const Empty& rhs) { ... } // 복사 대입 연산자
};
이러한 함수들을 스스로 만드는 이유는 기본 클래스 및 비정적 데이터 멤버의 생성자와 소멸자를 호출하는 코드가 필요해서이다. 이때 소멸자는 이 클래스가 상속한 기본 클래스의 소멸자가 가상 소멸자로 되어 있지 않으면 비가상 소멸자로 만들어진다.
복사 생성자와 복사 대입 연산자는 원본 객체의 비정적 데이터를 사본 객체 쪽으로 복사한다.
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);
...
private:
std::string nameValue;
T objectValue;
};
이 NamedObject 템플릿 안에는 생성자가 선언되어 있으므로, 컴파일러는 기본 생성자를 만들어 내지 않는다.
반면 복사 생성자나 복사 대입 연산자는 NamedObject에 선언되어 있지 않기 때문에 이 두 함수의 기본형이 컴파일러에 의해 만들어진다.
컴파일러가 만드는 함수들은 적법하고 이유가 있어야 한다. 둘 중 하나라도 해당하지 않으면 자동으로 생성되지 않는다.
template<class T>
class NamedObject {
public:
// 이 생성자는 상수타입의 name을 취하지 않는다.
// nameVlaue가 비상수 string의 참조자가 되었기 때문이다.
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; // 참조자
const T objectValue; // 상수
};
만약 다음과 같이 사용한다면 어떻게 될까
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> p(oldDog, 36);
p = s;
대입 연산이 일어나기 전, p.nameValue 및 s.nameValue는 각각 다른 string 객체를 참조하고 있다.
대입 연산이 일어나면 p.nameVlaue는 s.nameValue가 참조하는 string을 가리키는 것, 원래 p.nameValue가 가리키고 있던 객체가 바뀌는 것 모두 말이 되지 않는다. (데이터 멤버가 참조자와 상수이기 때문)
그렇기 때문에, 참조자를 데이터 멤버로 갖고 있는 클래스에 대입 연산을 지원하려면 직접 복사 대입 연산자를 정의해야 한다. 데이터 멤버가 상수 객체인 경우에도 비슷하다.
복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 이 클래스는 암시적 복사 대입 연산자를 가질 수 없다. (컴파일이 되지 않는다.)