C++의 어떤 멤버 함수는 클래스 안에 직접 선언되어 있지 않다면 컴파일러가 저절로 선언해 주도록 되어있다.
1.복사 생성자
2.복사 대입 연산자
3.소멸자
만약 생성자조차도 선언되어 있지 않다면 컴파일러가 대신 기본 생성자를 선언해 놓는다.
이들은 모두 public 멤버이며 inline함수이다(30장 참고).
class Empty{ }; //아무것도 선언되어 있지 않다. 생성자, 소멸자마저
눈에 봤을 때 생성자, 소멸자도 없는 빈 깡통 클래스이지만 컴파일러가 자동으로 선언하기에 실질적으로
class Empty
{
public:
Empty(); //기본 생성자
Empty(const Empty& rhs); //복사 생성자
~Empty(); //소멸자
Empty& operator=(const Empty& rhs); //복사 대입 연산자
};
이런 상태라는 것이다.
컴파일러가 꼭 필요하다고 판단할 때만 만들어지도록 되어 있지만, 필요하다고 판단될 조건이 대단한 것이 아니다.
만들어지는 조건을 만족하는 코드는 아래와 같다.
Empty e1; //기본 생성자, 소멸자
Empty e2(e1); //복사 생성자
e2 = e1; //복사 대입 연산자
컴파일러가 만드는 함수가 하는 일이 무엇이기에 자동으로 만들어 주는걸까?
기본 생성자와 소멸자가 하는 일은 일차적으로 컴파일러에게 배후의 코드를 깔 수 있는 자리를 마련하는 것이다.
기본 클래스 및 비정적 멤버 변수의 생성자와 소멸자를 호출하는 코드가 여기서 생성된다.(당연한 얘기 생성자 호출시에 클래스가 생성되기에)
이때 소멸자는 이 클래스가 상속한 기본 클래스의 소멸자가 가상 소멸자로 되어 있지 않으면 역시 비가상 소멸자로 만들어진다(7장 참고).(소멸자의 가상성을 부모 클래스로부터 물려받는 경우를 제외)
template<typename T>
class NamedObject
{
public:
NamedObject(const std::string& name, const T& value)
:nameValue(name), objectValue(value)
{}
private:
string nameValue;
T objectValue;
};
NamedObject 템플릿 안에 생성자가 선언되어 있으므로, 컴파일러는 기본 생성자를 만들지 않을 것이다.
하지만 복사 연산자와 복사 대입 연산자는 어떠한가? NamedObject 템플릿 안에 선언되어 있지 않기 때문에, 두 함수의 기본형을 컴파일러가 만들어 줄것이다.
NamedObject<int> no1("no1", 2);
NamedObject<int> no2(no1); //컴파일러가 복사생성자를 만들게 되는 시점(필요해 졌으니)
컴파일러가 만드는 복사 생성자는 no1.nameValue와 no1.obejctValue를 이용하여 no2.nameValue와 no2.obejctValue를 초기화해야 한다. nameValue의 타입이 string인데, STL의 string 타입은 복사 생성자를 갖고 있으므로 no2.nameValue의 초기화는 string의 복사 생성자에 no1.nameValue를 인자로 넘겨 호출하여 이루어 진다.
복사 대입 연산자도 근본적으로는 위의 복사 생성자와 동작 원리가 같다. 하지만 만약 NamedObject의 nameValue가 string이 아닌 string에 대한 참조자라면?
template<typename T>
class NamedObject
{
public:
NamedObject(std::string& name, const T& value) //nameValue가 비상수 string의 참조자이기에
:nameValue(name), objectValue(value) //상수 타입의 string을 쓰지 않는다.
{}
private:
string& nameValue;
T objectValue;
};
결론적으로 복사 대입 연산자는 (operator=)의 자동생성을 거부하고 에러가 발생한다.
string name = "name";
NamedObject<int> no1(name, 3);
NamedObject<int> no2(no1);
NamedObject<int> no3(name, 4); //컴파일러가 복사 연산자를 자동으로 생성했기에 문제x
no3 = no1; //복사 대입 연산자 에러
따라서 개발자가 직접 정의해 주어야 한다.
template<typename T>
class NamedObject
{
public:
NamedObject(std::string& name, const T& value)
:nameValue(name), objectValue(value)
{}
NamedObject& operator=(const NamedObject& rhs) //복사 대입 연산자
{
nameValue = rhs.nameValue;
objectValue = rhs.objectValue;
return (*this);
}
private:
string& nameValue;
T objectValue;
};
복사 대입 연산자가 private인 클래스의 파생 클래스인 경우
복사 대입 연산자를 private으로 선언한 클래스로 부터 파생된 클래스의 경우 또한 컴파일러가 암시적 복사 대입 연산자를 가질 수 없다. 정리하자면 파생 클래스의 경우 컴파일러가 만드는 복사 대입 연산자는 부모 클래스 부분이 맡도록 되어 있고(12장 참고), 자식 클래스 쪽에서 호출할 권한이 없는 멤버 함수는 복사 대입 연산자가 어떻게 호출할 수가 없다.(조금만 생각해보면 당연한 소리다. public, 혹은 protected로 하면 해결)
이것만은 잊지 말자
컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어준다.
'서적 정리 > Effective C++' 카테고리의 다른 글
9.객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2021.12.10 |
---|---|
8.예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2021.12.10 |
7.다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2021.12.10 |
6.컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2021.12.10 |
4.객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2021.12.10 |
3.낌새만 보이면 const를 들이대 보자! (0) | 2021.12.10 |
2.#define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2021.12.10 |
1.C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2021.12.10 |
댓글