본문 바로가기
책 정리/Effective C++ 3rd

항목 35. 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자

by ocean20 2020. 3. 10.

34장에서 봤던 처럼, 가상함수는 클래스 설계자가 기본 정의를 제공하기 때문에, 재정의를 하지 않아도 작동한다. 하지만, 정작 다른작동을 해야할때  까먹고 재정의를 하지 않으면.. 디버깅이 아주 힘들어질 것이다.

 

이번장은 이러한 문제를 해결하기 위해, 가상함수를 대체할 무엇인가가 필요하다는 주제로 쓰였다.

 

1. NVI 관용구(Non Virtual Interface)

class GameCharacter{
public :
  int healthValue() const { // 파생클래스는 해당함수 재정의 불가
  … // 사전동작
  int retVal = doHealthValue();
  .. // 사후동작
}
private :
  virtual int doHealthValue() const { //파생클래스는 해당가상함수 재정의 가능
  …
  }
};

NVI 가상함수는 반드시 private멤버로 두어야 한다는 가상함수 은폐론이 기초가 기법이다.

 

가상함수를 private으로 두면 파생클래스는 재정의 있지만, 실체호출은 불가능하다.

호출을 하려면 비가상함수를 거쳐야하는데 이것이 바로 NVI 핵심이다.

여기서, healthValue() 가상함수의 랩퍼라고 한다.

 

healthValue() 호출하면 가상함수를 호출하기전에, 사전동작이 가능하며, 가상함수 호출후 사후동작이 가능하다. 또한 재정의도 가능하기 때문에 구현의 자유가 있지만, 호출 시점은 기본클래스의 고유권한으로 묶인다.

 

가상함수가 private 이어야만 하는건 아니지만, 그외를 할거만 NVI 하는 이유가 없다.

 

2. 함수포인터로 구현한 전략 패턴

class GameCharacter;

int defaultHealthCalc (const GameCharacter& gc);  // 체력치 계산에 대해 기본 알고리즘을 구현해둔 함수이다.

class GameCharacter{
public :
  typedef int (*HealthCalcFunc) (const GameCharacter&);
 
  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {}
 
  int healthValue() const {
  return healthFunc(*this);
  }
 
private :
  HealthCalcFunc healthFunc;

class knight : public GameCharacter {
public :
  explicit knight(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) { ... }
};

int loseHealthQuickly(const GameCharacter&);  // 다른 동작원리로 구현된
int loseHealthSlowly(const GameCharacter&);   // 체력이 다르게 감소된다.
 
knight kn1(loseHealthQuickly);  // 같은 타입의 knight인데도
knight kn2(loseHealthSlowly);   // 체력이 다르게 감소된다.

캐릭터 생성자에 체력 계산 알고리즘이 들어있는 함수용포인터를 넘기게 만들고, 이함수를 호출해서 체력계산을 수행한다.

단점으로는 defaultHealthCalc 함수는 knight의 public멤버가 아닌 부분을 건드릴 수 없다는 것이다.

만약, public 영역에 없는 부분을 비멤버 함수도 접근할 수 있게하려면, 그 클래스의 캡슐화를 약화시키는 방법이 있다.

이점과 단점을 조율하면서 알맞은 판단을 내려야 할 것이다.

 

3. tr1::function 으로 구현한 전략패턴

가상함수를 tr1::function 데이터 멤버로 대체하여, 호환되는 시그너처를 가진 함수호출성 개체를 사용할 있도록 만듭니다.. tr1 공부하고 다시봐야겠다..

 

 

결론은 가상함수를 대체할만한 것들을 공부해두자

 

이것만은 잊지말자!

1. 가상함수대신쓸수 있는 방법으로 NVI 관용구가 있다. NVI관용구 자체가 템플릿 메서드 패턴의 예이다.

2. 객체에 필요한 기능을 멤버함수에서 클래스 외부에 비멤버함수로 옮긴다면, 비멤버함수는 클래스의 public 멤버가 아닌 것들을 접근할 없다는 단점이 있다.

3. tr1::function 객체는 일반화된 함수 포인터처럼 동작한다. 객체는 주어진 대상 시그너처와 호환되는 모든 함수호출성 개체를 지원한다.

댓글