사용자가 직접 클래스 안에 선언하지 않아도 컴파일러가 알아서 선언하는 함수에는 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자가 있다. 모두 public이자 inline 함수로 선언된다.
class Empty{};
즉, 이 Empty 클래스와
class Empty{
public:
Empty() { ... }
Empty(const Empty& rhs) { ... }
~Empty() { ... }
Empty& operator=(const Empty& rhs) { ... }
};
이 Empty 클래스는 근본적으로 동일하다.
Empty e1; // 기본 생성자, 소멸자
Empty e2(e1); // 복사 생성자
e2 = e1; // 복사 대입 연산자
단 이 함수들은 컴파일러가 필요하다고 판단할 때만 만들어진다.
예를 들어 Empty e1;
코드가 존재하는 경우에만 기본 생성자와 소멸자가 만들어진다.
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<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // 복사 생성자 호출
컴파일러가 만든 복사 생성자는 no1
의 nameValue
와 objectValue
를 사용해서 no2
의 nameValue
와 objectValue
를 각각 초기화한다.
string nameValue
의 경우 string 타입은 자체적으로 복사 생성자가 있으므로 string의 복사 생성자를 호출하면서 초기화한다.int objectValue
는 기본 제공 타입이므로 no1.objectValue
의 각 비트를 그대로 복사하여 초기화한다.컴파일러가 만든 복사 대입 연산자 또한 근본적으로 복사 생성자와 동일하다.
// 아까 정의한 NamedObject와 비교하며 보기
template<typename T>
class NamedObject {
public:
// const char* name을 인자로 취하는 생성자 삭제
// string& nameValue이기 때문
NamedObject(const 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> s(oldDog, 36);
p=s; // 대입 연산
p
와 s
의 nameValue
는 string 객체를 참조하고 있다.
이때 대입 연산이 일어나면
p.nameValue
가 s.nameVlaue
가 참조하는 string을 참조하게 될까?p.nameValue
가 참조하는 객체 자체가 바뀌는 걸까?결국 컴파일러는 이런 상황에 대해 컴파일을 거부한다.
따라서 참조자(ex. nameValue
)를 데이터 멤버로 갖는 클래스의 경우 직접 대입 연산자를 정의해야 한다.
상수(ex. objectValue
)를 데이터 멤버로 갖는 경우도 마찬가지이다. 상수 객체를 수정하는 것은 문법에 어긋나기 때문이다.
정리
1. 컴파일러는 경우에 따라 클래스의 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 생성할 수 있다.
2. 참조자나 상수를 데이터 멤버로 갖는 클래스는 직접 대입 연산자를 정의해야 한다.