[C++] 항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 정리하였습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 2. 생성자, 소멸자 및 대입 연산자

👉🏻 항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자

1. C++이 생성해주는 멤버 함수

- 기본 생성자
- 소멸자
- 복사 생성자
- 복사 대입 연산자

이 4가지의 멤버 함수는 개발자가 선언해주지 않으면, 컴파일러가 대신 생성해준다. 이들은 public 멤버이며, inline 함수로 생성된다.

1.1. 선언/호출

class Empty {
public:
	Empty() { ... } // 기본 생성자
	Empty(const Empty& rhs) { ... } // 복사 생성자
	~Empty() { ... } // 소멸자
	
	Empty& operator=(const Empty& rhs) { ... } // 복사 대입 연산자
}

Empty e1; // 기본 생성자/소멸자 호출
Empty e2(e1); // 복사 생성자 호출
e2 = e1 // 복사 대입 연산자 호출

이때 소멸자는 이 클래스가 상속한 기본 클래스의 소멸자가
가상(virtual) 소멸자로 되어있지 않으면, 비가상 소멸자로 만들어진다.

1.2. 복사 생성자/복사 대입 연산자

template <tyename 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); // 복사 생성자 호출

NamdObject 클래스 내에는 생성자가 선언되어 있다면, 컴파일러는 기본 생성자를 생성하지 않는다.

이 코드에선 복사 연산자나 복사 대입 연산자가 보이지 않는다.
필요한 경우 복사 연산자/복사 대입 연산자를 호출하게 된다.

생성된 복사 생성자는 no1.nameValue와 no1.objectValue를 이용하여 no2.nameValue와 no2.objectValue를 각각 초기화해준다.
nameValuestring 타입이므로 string 자체적으로 포함되어있는 복사 생성자를 통해 초기화 해준다.
nameObjectint이므로 기본 제공 타입이다. 이런 경우 no1.objectValue의 각 비트를 그대로 복사해 온다.

1.2. 암시적으로 생성할 수 없는 경우

template <class 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; // 복사 대입 연산자

만약 클래스 내의 변수가 참조자와 상수가 있다면 어떻게 될까?

이러한 경우 C++에서 컴파일을 거부하게 되며, 이유는 아래와 같다.

1. nameValue : C++의 참조자는 원래 자신이 참조하고 있는 것과 다른 객체를 참조할 수 없다.
2. objectValue : 이미 초기화된 상수의 값을 변경할 수 없다.
3. 앞선 이유로, 복사 대입 연산자를 생성할 수 없다.

에러를 해결하기 위해선, 복사 대입 연산자를 직접 만들어주어야 할것이다.

이외에도, 생성한 클래스가 복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 경우에도, 암시적 복사 대입 연산자를 가질수 없다.

🧐 정리

  • 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 소멸자, 복사 생성자, 복사대입 연산자를 암시적으로 생성한다.
  • 암시적으로 생성할 수 없는 경우 생성하지 않으며, 필요시 직접 생성해주어야 한다.
  • 생성자가 이미 존재한다면, 컴파일러는 기본 생성자를 생성하지 않는다.

cpp 카테고리 내 다른 글 보러가기

댓글남기기