05. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
- Empty Class
- 복사 생성자 / 복사 대입 연산자의 역할
- 이것만은 잊지 말자 !
Empty Class
C++의 컴파일러는 개발자가 클래스 안에 멤버 함수를 직접 선언해주지 않으면 컴파일러가 저절로 선언해주는 것이 있다. 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자는 선언이 되어있지 않은 경우 컴파일러가 자동적으로 선언하는 함수에 해당하고 이들은 모두 public 멤버 그리고 inline 함수로 선언된다. 따라서 아래의 두 클래스는 동일하다.
컴파일러가 기본 함수를 자동 생성하는 시점은 컴파일러가 판단해서 해당 함수가 필요하다고 생각되는 시점이며 위와 같이 코드가 선언되어 있을 때, 각각의 함수들이 생성되는 시점은 아래와 같다.
추가로 소멸자의 경우, 클래스를 상속한 기본 클래스의 소멸자가 가상 소멸자가 아니라면 자동 생성되는 소멸자 역시 비가상 소멸자이다. 즉, 소멸자의 가상성은 자동 생성되는 경우에 기본 클래스로부터 물려받게 된다.
복사 생성자 / 복사 대입 연산자의 역할
기본적으로 C++에서 복사 생성자와 복사 대입 연산자가 하는 일은 단순하다. 원본 객체의 비정적 데이터를 사본 객체 쪽으로 복사하는 동작만을 수행한다.
위 클래스에서 NamedObject 템플릿에는 생성자가 선언이 되어있다. 따라서 컴파일러는 기본 생성자를 생성하지 않고, 선언된 생성자 인자가 필요한 클래스만을 만든다. 즉, 생성자를 선언한 시점에서 인자가 없는 기본 생성자를 컴파일러를 생성하지 않는다.
반면, 복사 생성자와 복사 대입 연산자는 선언되어 있지 않기 때문에 기본형이 컴파일러에 의해 만들어지게 된다. 아래는 복사 생성자의 사용 예이다.
복사 생성자는 no1의 nameValue와 objectvalue를 no2로 복사하게 되는데, nameValue의 경우 string 타입이므로 표준 string 타입의 자체 복사 생성자에게 no1.nameValue를 인자로 넘김으로써 복사가 이루어진다. objectValue의 경우 템플릿 T가 int형 즉, 기본 제공 타입이므로 각 비트를 그대로 복사해한다.
복사 대입 연산이 이루어지는 경우 컴파일러의 판단하에 적법(legal) 그리고 이치에 닿아야만(resonable) 복사 대입 연산자인 operator=을 자동 생성하는데, 최종적인 코드 결과가 둘 중 하나라도 검사에 통과하지 못하면 컴파일러는 자동생성을 거부한다.
위의 코드에서 생성자는 상수가 아닌 비상수 string의 참조자를 인자로 취하고 따라서 char*는 없어졌다. 복사 대입 연산자 operator=은 없다고 가정하고 아래의 코드를 만나면 어떠한 일이 벌어질까?
결론적으로 C++의 참조자는 자신이 참조하고 있는 것과 다른 객체를 참조할 수 없다. 즉, 참조자를 데이터 멤버로 가지고 있는 클래스에 대입 연산을 지원하려면 직접 복사 대입 연산자를 정의해 주어야 한다. 데이터 멤버가 상수인 경우에도 비슷하게 동작한다.
추가적으로 복사 대입 연산자를 private으로 선언한 기본 클래스로부터 파생된 클래스이 경우, 이 클래스는 암시적 대입 연산자를 가질 수 없다.
이것은 잊지 말자 !
컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어 놓을 수 있다.