로또
5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. 본문
다루는 내용
클래스의 생성자, 복사 생성자, 복사 대입 연산자, 소멸자가 선언되어 있지 않고, 컴파일러에 의해 필요하다고 판단되면 컴파일러가 대신 생성한다.
컴파일러가 만드는 함수들
class Empty{}
int main(){
Empty e1; // 컴파일러가 기본 생성자와 소멸자를 필요하다고 판단하는 코드.
Empty e2(e1); // 컴파일러가 복사 생성자가 필요하다고 판단하는 코드.
e2 = e1; // 컴파일러가 복사 대입 연산자가 필요하다고 판단하는 코드.
return 0;
}
컴파일러는 위 코드에 대해 다음과 같이 여러 함수를 만들고 호출한다.
class Empty{
public:
Empty(){...} // 컴파일러가 생성한 기본 생성자
Empty(const Empty& rhs){...} // 컴파일러가 생성한 복사 생성자
~Empty(){...} // 컴파일러가 생성한 소멸자
Empty& operator=(const Empty& rhs){...} // 컴파일러가 생성한 복사 대입 연산자
}
int main(){
Empty e1; // 컴파일러가 기본 생성자와 소멸자를 필요하다고 판단하는 코드.
Empty e2(e1); // 컴파일러가 복사 생성자가 필요하다고 판단하는 코드.
e2 = e1; // 컴파일러가 복사 대입 연산자가 필요하다고 판단하는 코드.
return 0;
}
문제점
template<typename T>
class NamedObject{
public:
NamedObject(const char* name, const T& value);
NamedObject(const string name, const T& value);
private:
string name_value;
T object_value;
};
위 NamedObject 클래스 내에는 이미 생성자가 선언되어 있으므로 컴파일러는 기본 생성자를 생성하지 않는다.
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1);
하지만 복사 생성자나 복사 대입 연산자는 선언되어있지 않기 때문에, 위 코드처럼 컴파일러가 복사 생성자가 필요하다고 판단되면 만들어질 수 있다.
이 때, 복사 생성자에서 쓰일 string이나 int는 자체적으로 복사 생성자를 갖고 있으므로 no2에서는 단순히 no1의 각 비트를 그대로 복사해온다.
이제, 다음과 같은 경우를 생각해보자.
template<typename T>
class NamedObject{
public:
NamedObject(const string name, const T& value);
private:
string& name_value; // 참조자로 변경됨.
const T object_value; // const로 변경됨.
};
int main(){
string old_dog("ddochi");
string new_dog("lotto");
NamedObject<int> od(old_dog, 1);
NamedObject<int> nd(new_dog, 12);
od = nd; // 복사 대입 연산자 호출
return 0;
}
NamedObject class의 멤버 변수가 각각 참조자와 상수로 변경되었다.
참조자와 상수에 대해서는 초기화를 제외하고 대입 연산을 허용하지 않으므로 복사 대입 연산자를 생성할 수 없다.
때문에 위와 같은 경우에는 컴파일 에러가 발생한다.
이러한 문제를 해결하기 위해서 참조자나 상수를 멤버 변수로 갖고 있는 클래스에 대해서는 직접 복사 대입 연산자를 정의해주어야 할 것이다.
이것만은 잊지 말자!
- 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어놓을 수 있다.
복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 해당 클래스는 암시적 복사 대입 연산자를 가질 수 없다. 파생 클래스 쪽에서 복사 대입 연산자는 호출할 권한이 없는 멤버 함수이므로 컴파일러가 거부하기 때문이다.
이러한 성질을 이용해 위와 같은 문제 상황을 방지할 수 있다.
자세한 내용은 "6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자." 로 이어진다.
'책 > Effective C++' 카테고리의 다른 글
| 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자. (0) | 2023.11.06 |
|---|---|
| 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자. (0) | 2023.11.04 |
| 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자. (0) | 2023.11.03 |
| 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자. (1) | 2023.11.01 |
| 3. 낌새만 보이면 const를 들이대 보자! (1) | 2023.10.31 |