#7. [모던 C++] 개선된 클래스(default, delete, override, final, 생성자 위임, 생성자 상속)(C++11)
- [MEC++#11] 정의되지 않은 비공개 함수보다 delete된 함수를 선호하라.(delete를 이용한 암시적 형변환과 템플릿 인스턴스화 차단 참고)
- [MEC++#12] 재정의 함수들을 override로 선언하라.(override 참고)
- (C++11~) default, delete가 추가되어 암시적으로 생성되는 멤버 함수의 사용 여부를 좀더 명시적으로 정의할 수 있습니다.
- (C++11~) override가 추가되어 가상 함수 오버라이딩의 코딩 규약이 좀더 단단해졌습니다.
- (C++11~) final이 추가되어 가상 함수를 더이상 오버라이딩 못하게 할 수 있습니다.
- (C++11~) final이 추가되어 강제적으로 상속을 제한할 수 있습니다.
- (C++11~) 생성자 위임이 추가되어 생성자의 초기화 리스트 코드가 좀더 간결해 졌습니다.
- (C++11~) 생성자 상속이 추가되어 부모 개체의 생성자도 상속받아 사용할 수 있어 자식 개체의 생성자 재정의 코드가 좀더 간결해 졌습니다.
default와 delete
기존에는 암시적으로 정의되는 멤버 함수들은 private
를 이용하여 억지로 가시성을 조정하여 사용 여부를 제어했는데요(클래스의 암시적 정의 참고),
C++11 부터는 default나 delete로 사용 여부를 결정합니다.
1
2
3
4
5
class T_11 {
public:
T_11() = default; // 암시적 버전의 기본 생성자 사용
T_11(const T_11&) = delete; // 암시적 버전의 복사 생성자 막음
};
(C++20~) 삼중 비교 연산자를 default로 정의할 수 있습니다.
delete를 이용한 암시적 형변환과 템플릿 인스턴스화 차단
delete는 그 어떤 함수도 삭제할 수 있습니다. “함수를 안만들면 되지, 뭐하러 만들고 삭제하냐” 할 수 있을텐데요, 이 기능을 활용하면, 재밌게도 함수 호출시의 암시적 형변환을 차단할 수 있습니다.
다음의 Func_11(int)
함수는 Func_11('a')
와 같이 char
를 전달해도 실행됩니다. char
가 int
로 암시적 형변환되기 때문이죠.
그런데, void Func_11(char) = delete;
와 같이 char
를 사용하는 함수를 delete한다면, Func_11('a')
와 같이 호출했을때 컴파일 오류가 발생하여 암시적 형변환을 차단할 수 있습니다.
1
2
3
4
5
6
7
8
9
class T_11 {
public:
void Func_11(int) {}
void Func_11(char) = delete; // char를 인자로 받는 함수를 삭제합니다.
};
T_11 t;
t.Func_11(10);
t.Func_11('a'); // (X) 컴파일 오류. delete된 함수입니다.
또한 특정 타입의 템플릿 인스턴스화를 차단할 수 있습니다.
다음 예에서는 char
타입의 함수 템플릿 특수화를 delete 하여 char
타입의 인스턴스화를 차단합니다.
1
2
3
4
5
6
7
8
template<typename T>
void Func_11(T) {}
template<>
void Func_11<char>(char) = delete; // char 버전 함수 삭제
Func_11(10);
Func_11('a'); // (X) 컴파일 오류. delete된 함수입니다.
override
기존에는 부모 개체의 가상 함수를 오버라이딩할 때 아무런 조치 없이 오버라이딩했는데요(가상 함수 참고), 가끔 오타인지, 새로운 가상 함수 정의인지 헷갈릴 때가 있습니다. 하기와 같은 실수를 찾아내는게 상당히 어려웠죠.
1
2
3
4
5
6
7
8
9
10
class Base {
public:
virtual void Func1() {};
virtual void Func2() {};
};
class Derived : public Base {
virtual void Func1() {}; // (O)
virtual void Func2() {}; // (O)
virtual void Func_2() {}; // (△) 비권장. 오타일까요? 새로운 가상 함수를 정의한 것일까요?
};
C++11 부터는 명시적으로 override를 지정해주면 컴파일러단에서 잘못된 오버라이딩인지 검사해 줍니다. 무조건 사용하세요. 삶이 쾌적해 집니다.
1
2
3
4
5
6
7
8
9
10
class Base {
public:
virtual void Func1() {};
virtual void Func2() {};
};
class Derived_11 : public Base {
virtual void Func1() override {}; // (O)
virtual void Func2() override {}; // (O)
virtual void Func_2() override {}; // (X) 컴파일 오류. 부모 개체에 해당 멤버 없음
};
final
기존에는 상속을 제한하기 위해 생성자를 private
로 만들어 가시성을 제한 하거나, public Non-Virtual 소멸자로 만들고 상속하지 않도록 서로간에 정하자 라고 했었는데요(상속 제한 참고),
C++11 부터는 다음처럼 final로 상속을 제한할 수 있습니다.
1
2
3
4
5
class Base_11 final {
};
// (X) 컴파일 오류. Base_11은 상속할 수 없습니다.
class Derived_11 : public Base_11 {
};
또한, 멤버 함수의 override도 막을 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
class Base_11 {
public:
virtual void Func1() {};
virtual void Func2() final {};
};
class Derived1_11 : public Base_11 {
virtual void Func1() override final {}; // (O) Base를 오버라이드하고, 자식 개체에서는 오버라이드 못하게 합니다.
virtual void Func2() override {}; // (X) 컴파일 오류. Func2는 오버라이드 할 수 없습니다.
};
class Derived2_11 : public Derived1_11 {
virtual void Func1() override final {}; // (X) 컴파일 오류. Func1은 오버라이드 할 수 없습니다.
};
생성자 위임
기존에는 다양한 버전의 생성자를 만들고자 할때 각각의 생성자에서 초기화 리스트를 사용하여 멤버 변수를 초기화 했는데요(초기화 리스트 참고),
1
2
3
4
5
6
7
8
9
10
class T {
int m_X;
int m_Y;
int m_Z;
public:
T() : m_X{0}, m_Y{0}, m_Z{0} {}
explicit T(int x) : m_X{x}, m_Y{0}, m_Z{0} {}
T(int x, int y) : m_X{x}, m_Y{y}, m_Z{0} {}
T(int x, int y, int z) : m_X{x}, m_Y{y}, m_Z{z} {}
};
C++11 부터는 다른 생성자에 위임할 수 있어 좀더 간결하게 생성자 코드를 만들 수 있습니다.
1
2
3
4
5
6
7
8
9
10
class T_11 {
int m_X;
int m_Y;
int m_Z;
public:
T_11() : T_11{0, 0, 0} {} // T_11(int x, int y, int z)에 위임
explicit T_11(int x) : T_11{x, 0, 0} {} // T_11(int x, int y, int z)에 위임
T_11(int x, int y) : T_11{x, y, 0} {} // T_11(int x, int y, int z)에 위임
T_11(int x, int y, int z) : m_X{x}, m_Y{y}, m_Z{z} {}
};
생성자 상속
기존에는 자식 개체 생성자에서 부모 개체의 생성자를 명시적으로 호출해야 했는데요(자식 개체의 생성자 재정의 참고),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base {
int m_X;
int m_Y;
public:
Base(int x, int y) : m_X{x}, m_Y{y} {}
};
class Derived : public Base {
public:
// Derived 생성자에서 Base(x, y) 생성자를 명시적으로 호출
Derived(int x, int y) : Base{x, y} {}
};
Derived d(10, 20);
C++11 부터는 using 선언의 형태로 부모 개체의 생성자를 그대로 상속받아 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base {
int m_X;
int m_Y;
public:
Base(int x, int y) : m_X{x}, m_Y{y} {}
};
class Derived_11 : public Base {
public:
// 생성자 상속
using Base::Base;
};
Derived_11 d{10, 20};
댓글남기기