항목 5 : C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
클래스가 비어 있지만 비어 있는 게 아닌 때가 있습니다.
C++(컴파일러)가 빈 클래스를 훑고 지나갈 때 입니다.
C++의 어떤 멤버 함수는 여러분이 클래스 안에 직접 선언해 넣지 않으면 컴파일러가 저절로 선언해 주도록 되어 있습니다. 바로 복사 생성자, 복사 대입 연산자, 그리고 소멸자 입니다.
이때 컴파일러가 만드는 함수의 형태는 모두 기본형입니다. 이들은 모두 public 멤버이며 inline함수입니다.
여러분이 다음과 같이 썼다면
1
|
class Empty();
|
다음과 같이 쓴 것과 근본적으로는 같다는 이야기입니다.
1
2
3
4
5
6
7
8
9
|
class Empty{
public:
Empty(){...} // 기본 생성자
Empty(const Empty& rhs){...} // 복사 생성자
~Empty(){...} // 소멸자
Empty& operator=(const Empty& rhs) // 복사 대입 연산자
};
Colored by Color Scripter
|
컴파일러가 만들어낸 복사 생성자/복사 대입 연산자가 하는 일은 단순합니다.
원본 객체의 비정적 데이터를 사본 객체 쪽으로 복사하는 것이 전부입니다.
참조자를 데이터 멤버로 갖고 있는 클래스에 대입 연산을 지원 할때는 주의가 필요합니다.
1
2
3
4
5
6
7
8
9
10
11
|
template<class T>
class NameObject{
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; // 이제 이 멤버는 참조자입니다.
const T objectValue; // 이제 이 멤버는 상수입니다.
};
|
자, 그럼 여기서 어떤 일이 일어날지 생각해 봅시다.
1
2
3
4
5
6
7
8
|
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;
Colored by Color Scripter
|
p에 들어 있는 데이터 멤버에서 어떤 일이 일어나야 할까요?
대입 연산이 일어나기 전, p.nameValue 및 s.nameValue는 string 객체를 참조하고 있습니다. 물론 같은 string 객체는 아닙니다. 이때 대입 연산이 일어나면 p.nameValue가 s.nameValue가 참조하는 string을 가리켜야 할까요? 다시 말해 참조자 자체가 바뀌어야 하는 걸까요?
하지만 C++의 참조자는 원래 자신이 참조하고 있는 것과 다른 객체를 참조할 수 없습니다.
그렇다면 p.nameValue가 참조하는 string 객체 자체가 바뀌는게 맞을까요?
이렇게 되면 그 string에 대한 포인터나 참조자를 품고 있는 다른 객체들, 즉 실제 대입 연산에 직접적으로 관여하지 않는 객체까지 영향을 받게 됩니다.
어느 쪽을 정하더라도 찝찝한 이 문제에 대해, C++는 시원하게 '컴파일 거부'를 합니다.
그렇기 때문에, 참조자를 데이터 멤버로 갖고 있는 클래스에 대입 연산을 지원하려면 코드 작성자가 직접 복사 대입 연산자를 정의해 주어야 합니다.
복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 이 클래스는 암시적 복사 대입 연산자를 가질 수 없습니다. 컴파일러가 거부해 버리니까요.
파생 클래스에 대해 컴파일러가 만들어 주는 복사 대입 연산자는 기본 클래스 부분을 맡도록 되어 있긴 하지만, 이렇게 하더라도 파생 클래스 쪽에서 호출할 권한이 없는 멤버 함수는 암시적 복사 대입 연산자가 호출할 수는 없습니다.
꼭 잊지 말아야 할 것!
컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어 놓을 수가 있다.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2020.01.07 |
---|---|
[Effective C++] 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2020.01.06 |
[Effective C++] 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2020.01.04 |
[Effective C++] 낌새만 보이면 const를 들이대 보자! (0) | 2020.01.03 |
[Effective C++] #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2020.01.02 |