private
은 해당 클래스 내에서만 접근 가능하고, protected
는 상속관계일 때 접근이 가능하다고 배운다.접근
이 아니라 정의
에서도 통용되는 말이다. 생각해보면 당연하다. 내가 호출할 수 없는 함수를 내가 정의한다는것이 가당키나 한가?private virtual
자체를 막아놓는다.실제로 C#에선 위처럼 컴파일 에러를 던져준다.
private virtual
이 가능하다!접근
이 아니라 정의
에서도 통용되는 말이다”라고 말했던게 기억이 나는가?접근
과 정의
를 분리한다면 private virtual
은 충분히 가능하다. 어디서 접근
할지는 Base클래스가 정해도 해당 함수가 무엇을 할지에 대한 정의
는 자식 클래스에게 맡기는 관점으로 가는 것이다.#include<cstdio>
//================== Base ==================
class Base {
private:
virtual int retInt() {
return 1;
}
public:
void printInt() {
printf("%d\n", retInt());
}
};
//================== Derived ==================
class Derived : public Base {
private:
virtual int retInt() override {
return 2;
}
};
//================== main ==================
int main() {
Base b;
Derived d;
b.printInt(); // 1출력
d.printInt(); // 2출력
return 0;
}
public 비가상 멤버함수
를 통해 private 가상함수
를 간접적으로 호출하게 하는 방식을 비가상 함수 인터페이스(NVI) 관용구
라고 부르고 이는 템플릿 메서드
(템플릿 클래스에서 템플릿 아님)라는 고전적인 디자인 패턴을 C++식으로 구현한 것이다.템플릿 메서드
에 대한 대표적인 예시로 언리얼의 Tick()
함수와 유니티의 Update()
함수를 들 수 있다.해당 함수들은 게임엔진이 매 프레임마다 한 번 씩 호출하도록 정의된 함수들이다.
게임엔진은 프레임마다 유저 모르게 게임 내의 하나의 오브젝트들에 대해 엄청나게 많은 처리를 한다. 그런데 유저는 Tick()
, Update()
함수의 앞뒤에 무슨 일을 해야하는지에 대해 몰라도 해당 함수들만 오버라이딩 하면 원하는 게임 로직을 만들 수 있다.
사실 유니티의 Update() 함수는 private멤버를 public멤버가 호출하는 형식이 아니라 리플렉션을 활용하긴 하지만… 어쨋든 내가 정의한 함수 이외의 일들은 엔진이 알아서 처리해준다는 관점에서
템플릿 메서드
패턴과 유사하다 볼 수 있다.
NVI관용구
는 엄격하게 private
일 필요는 없다. 그러니 다른 언어들이 private virtual
을 막아놓는 것도 합리적인 언어설계라고 생각할 수 있다.