05: 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 string& name, const T& value);
private:
string nameValue;
T objectValue;
};
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // 복사 생성자 호출
복사 생성자나 복사 대입 연산자는 NamedObject에 선언되어 있지 않기 때문에, 컴파일러에 의해 기본형이 만들어진다. no1.namedValue와 no1.objectValue를 사용해서 no2.namvedValue 및 no2.objectValue를 각각 초기화한다.
복사 대입 연산자도 근본적으로는 동작 원리가 같다. 하지만 최종 결과 코드가 적법해야(legal)하고 이치에 닿아야만(resonable)해야 하며, 둘 중 어느 검사도 통과하지 못하면 컴파일러는 oeprator=의 자동생성을 거부한다.
template<typename T>
class NamedObject
{
public:
NamedObject(string& name, const T& value);
private:
string& nameValue;
const T objectValue;
};
string newDog("Persephone");
string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;
대입 연산이 일어나면 어떻게 되어 있을까?
s.nameValue가 참조하는 string을 가리키도록 p의 참조자 자체가 바뀌어야 하나? 하지만 이렇게는 할 수 없다. C++에서는 참조자의 값을 바꿀 수 없기 때문이다.
그렇다면 p.nameValue가 참조하는 string 객체 자체가 바뀌는게 맞을까? 이렇게 되면 그 string에 대한 포인터나 참조자를 품고 있는 다른 객체들, 즉 실제 대입 연산에 직접적으로 관여하지 않는 객체까지 영향을 받게 된다.
이런 문제를 C++은 컴파일 거부라는 카드를 낸다. 그렇기 때문에, 참조자를 데이터 멤버로 갖고 있는 클래스에 대입 연산을 지원하려면 직접 복사 대입 연산자를 정의해 주어야 한다. 데이터 멤버가 상수 객체인 경우도 마찬가지이다. 상수 멤버를 수정하는 것은 문법에 어긋나기 때문이다.
복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 이 클래스는 암시적 복사 대입 연산자를 가질 수 없다. 컴파일러가 거부해 버린다. 파생 클래스 쪽에서 호출할 권한이 없는 멤버함수는 암시적 복사 대입 연산자가 어떻게 호출할 수 없다.
이것만은 잊지 말자!
- 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어 놓을 수 있다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 07: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2023.10.03 |
---|---|
[Effective C++] 06: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2023.10.02 |
[Effective C++] 04: 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2023.09.30 |
[Effective C++] 03: 낌새만 보이면 const를 들이대 보자! (0) | 2023.09.28 |
[Effective C++] 02: #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2023.09.28 |