Item 35 : Consider alternatives to virtual functions
가상 함수 대신 쓸 것들도 생각해 두자.
1. The Template Method Pattern via the Non-Virtual Interface(NVI) Idiom
이 전략을 사용하는 경우 virtual function은 private으로 둬야 한다.
non-virtual 멤버 함수를 만들고 non-virtual 멤버 함수가 private virtual function을 호출하도록 한다.
사전 동작과 사후 동작이 가능하다는 장점이 있다.
virtual function이 호출되기 이전, 호출된 후 특정한 작업을 수행할 수 있도록 보장한다.
즉, 가상 함수가 호출되기 전에 적절한 context가 설정되고, 호출이 끝나면 context가 깨끗해진다.
Derived class에서 private virtual function은 다시 정의되어야 한다.
가상함수를 다시 정의하는 것은 어떤일을 어떻게 해야 하는지를 의미하고, 가상 함수를 호출하는 것은 어떤일을 언제 해야 하는지를 의미한다.
Derived class에서 가상함수를 재정의 하는 것을 허락함으로써, 어떻게 기능이 구현될지 정할 수 있도록 한다.
하지만, base class는 언제 함수가 호출되어야 할지 정할 권리를 자체적으로 보유한다.
2. The Strategy Pattern via Function Pointers
함수의 포인터를 넘겨 이 함수를 호출함으로써 구현하는 전략이다.
이 방법은 같은 타입의 인스턴스들이 다른 타입의 함수를 가질 수 있도록 한다.
또한, 함수는 run time에 바뀔 수 있다는 융통성을 가진다.
하지만, 포인터로 넘겨주는 함수는 멤버 함수가 아니다.
따라서 객체의 internal part에 접근할 특별한 권한이 없다.
non-member 함수가 클래스의 non-public 부분에 접근하면 encapsulation이 감소된다.
3. The Strategy Pattern via tr1::function
tr1::function과 호환되는 시그니처를 가진 callable entity(함수호출성 개체)를 사용하도록 한다.
tr1::function은 어떤 함수가 가진 시그니처와 호환되는 시그니처를 갖는 함수 호출성 개체의 표현을 가능하게 해주는 템플릿이다.
함수에 대한 generalized pointer처럼 작동한다.
주어진 target signature와 호환되는 모든 함수호출성 개체를 지원한다.
4. The Classic Strategy Pattern
함수를 나타내는 클래스 hierarchy를 하나 만들고, 실제 계산 함수는 이 클래스 hierarchy의 virtual member function으로 만든다.
Item 36 : Never redefine an inherited non-virtual function
상속받은 비가상 함수를 파생 클래스에서 재정의하면 안된다.
Base class에 있는 non-virtual function을 Derived class에서 재정의 하는 경우를 고려해보자.
non-virtual function은 statically bound이다.
즉, 객체의 타입이 아닌, 객체를 가리키는 포인터의 타입에 의해 호출할 함수가 결정된다.
따라서 Derived class 타입의 객체의 주소를 Base class 타입에 대한 포인터에 할당한다면, 포인터를 통해 non-virtual function을 호출한 경우, Base class의 비가상 함수가 호출된다.
그러나, Virtual function은 dynamically bound이므로 앞에서 설명한 문제를 겪지 않는다.
객체의 타입에 의해 호출될 함수가 결정된다.
정리하면, Base class B와 Derived class D, 그리고 non-virtual member function mf에 대해,
B 객체에 적용되는 모든 것은 D 객체에 적용된다. 왜냐하면 D는 B객체이기 때문이다.
또한, mf가 비가상 함수이기 때문에, B를 상속받는 클래스들은 mf의 인터페이스 뿐만 아니라 구현도 상속받는다.
만약 mf가 B와 D에서 서로 달라야 한다면, mf는 virtual 함수여야 한다.
Item 37 : Never redefine a function's inherited default parameter value
절대로 상속받은 기본 매개변수 값을 재정의하지 않는다.
virtual function은 dynamically bound이지만, default parameter values는 statically bound이다.
Static binding은 early binding, dynamic binding은 late binding이다.
객체의 static type은 program text에서 선언되는 타입이다.
static type은 객체를 가리키는 포인터의 타입에 의해 결정된다.
객체의 dynamic type은 현재 참조하는 객체의 타입에 의해 결정된다.
즉, dynamic type은 그것이 어떻게 행동할 것인지 설명한다.
virtual function은 dynamically bound이며, 이는 호출할 함수가 그것이 호출될 때 결정된다는 것을 의미한다.
그러나, default parameter는 statically bound이다.
즉, derived class에서 정의된 virtual function을 호출할 때, base class의 default parameter value를 사용할 수 있다.
따라서 상속받은 기본 매개변수 값을 재정의하면 안된다.
왜 C++에서 default parameter value는 static type인가?
그것은 runtime efficiency에 있다.
만약 default parameter value가 dynamically bound이면, 컴파일러는 런타임에 virtual function의 적절한 default parameter value를 결정해야 한다.
이는 느리고 더 복잡하다.
멤버 함수가 virtual function인 경우, default parameter value가 존재하면, base class와 derived class에 다른 값을 설정하는 실수를 할 수 있다.
이 경우, non-virtual interface idiom(NVI idiom)을 활용하여 실수를 방지할 수 있다.
non-virtual function에서 default parameter를 지정하여 private virtual function을 호출하도록 한다.
Item 38 : Model "has-a" or "is-implemented-in-terms-of" through composition
Composition은 "has-a" 또는 "is-implemented-in-terms-of"를 의미한다.
application domain(응용 영역)에서는 has-a relationship을, implementation domain(구현 영역)에서는 is-implemented-in-terms-of relationship를 나타낸다.
class Address{};
class PhoneNumber{};
class Person
{
public:
...
private:
string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
위 코드의 Person 클래스는 has-a 관계이다.
ex) person has a name and has an address.
template<class T>
class Set{
public:
bool member(const T& item)const;
void insert(const T& item);
void remove(const T& item);
std::size_t size() const;
private:
std::list<T> rep;
};
위 예시는 클래스 Set을 더 효율적으로 구현하기 위해 list를 사용하여 구현했다.
Set과 list는 is-a 관계로는 만들 수 없기 때문에, private member로 list를 사용하여 data를 관리하도록 구현했다.
이러한 관계를 is-implemented-in-terms-of이라고 한다.
Item 39 : Use private inheritance judiciously
private 상속은 심사숙고해서 구사하자.
private 상속은 is-a를 의미하지 않는다.
1. 클래스가 private으로 상속된다면, 컴파일러는 일반적으로 derived class의 객체를 base class의 객체로 변환하지 않는다.
2. base class의 public, protected인 멤버라도 derived class에서는 private member가 된다.
private 상속은 is-implemented-in-terms-of를 의미한다.
private으로 상속하는 경우는 보통, Base class의 몇몇 기능의 장점을 활용할 뿐이지, 두 객체 사이의 개념적 관계가 없는 경우이다.
private 상속은 구현 기법이다.
구현만 상속되고, 인터페이스는 상속되지 않는다.
Derived class가 Base class를 활용해서 구현된다고 할 수 있다.
composition도 is-implemented-in-terms-of를 의미하는데, 이 둘중 무엇을 어떻게 선택해야 하는가?
답은 간단하다.
composition은 할 수 있을 때, 그리고 private inheritance는 해야만 할 때 사용한다.
private, protected member에 접근해야 할 때, virtual function을 오버라이드해야 할 때, 공간 최적화 문제가 있을 때 private 상속을 사용한다.
private 상속을 사용하는 대신, public 상속과 composition을 사용하는 경우 복잡해 보이지만 다음과 같은 장점이 있다.
아래 코드는 public 상속과 composition을 섞은 예시이다.
class Widget{
private:
class WidgetTimer : pubic Timer{
public:
virtual void onTick() const;
...
};
WidgetTimer timer;
...
};
1. Derived class는 만들 수 있지만, Derived class에서 Base class의 virtual function을 오버라이드할 수 없다.
WidgetTimer가 Widget의 private member이기 때문에, Widget의 derived class는 WidgetTimer에 접근할 수 없다.
따라서, virtual function인 onTick 또한 재정의 할 수 없다.
2. 컴파일 의존성을 최소화할 수 있다.
만약, Widget이 Timer를 상속받는다면, Timer의 정의가 Widget이 컴파일될 때 필요하다.
그러나, WidgetTimer의 정의를 Widget과 분리시켰기 때문에, Widget을 다시 컴파일 할 때 Timer를 다시 컴파일할 필요가 없다.
앞에서 private 상속을 사용하는 예시로 공간 최적화를 언급했다.
empty base optimization(공백 기본 클래스 최적화)는 데이터가 전혀 없는 클래스를 private으로 상속할 경우 공백 클래스가 메모리에서 차지하는 공간을 최적화하는 것을 의미한다.
Item 40 : Use multiple inheritance judiciously
다중 상속은 심사숙고해서 사용하자.
multiple inheritance(MI)는 같은 이름을 하나 이상의 base class에서 상속받는 것이 가능하다.
그러나 어떤 base class의 함수를 사용해야 할지 모호하기 때문에, 어떤 base class의 함수를 사용할지 지정해야 한다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] Chapter 7: Templates and Generic Programming(2) (0) | 2022.07.13 |
---|---|
[Effective C++] Chapter 7: Templates and Generic Programming(1) (0) | 2022.07.09 |
[Effective C++] Chapter 6: Inheritance and Object-Oriented Design(1) (0) | 2022.07.06 |
[Effective C++] Chapter 5: Implementations(2) (0) | 2022.07.06 |
[Effective C++] Chapter 5: Implementations(1) (0) | 2022.07.05 |