C++ 고급 문법 (25) — RTTI 직접 구현(Implementing RTTI)
지난 글에서 dynamic_cast는 상속 그래프를 탐색하느라 typeid 비교보다 10배 넘게 느렸고, -fno-rtti로 빌드하면 아예 쓸 수 없었다. 그래서 LLVM/Clang처럼 타입 판정이 잦고 -fno-rtti로 빌드하는 프로젝트는 RTTI를 직접 구현한다. 각 객체에…
지난 글에서 dynamic_cast는 상속 그래프를 탐색하느라 typeid 비교보다 10배 넘게 느렸고, -fno-rtti로 빌드하면 아예 쓸 수 없었다. 그래서 LLVM/Clang처럼 타입 판정이 잦고 -fno-rtti로 빌드하는 프로젝트는 RTTI를 직접 구현한다. 각 객체에…
기반 클래스 포인터만 들고 있는데 실제 객체가 어떤 타입인지 알아야 할 때가 있다. 이벤트 큐에서 꺼낸 객체를 타입별 핸들러로 보내거나, 특정 파생 타입에만 있는 기능을 쓰는 경우다. C++은 실행 시점…
Java나 C#에는 interface 키워드가 있지만 C++에는 없다. 대신 데이터 멤버 없이 순수 가상 함수와 가상 소멸자만 가진 추상 클래스를 인터페이스로 쓴다. 인터페이스를 두면 코드가 구체 클래스 대신 "이런 동작을 할…
도형마다 넓이를 계산하는 방법은 다르지만, "도형"이라는 객체 자체는 넓이를 정할 방법이 없다. 그렇다고 Shape::area()가 0을 반환하게 두면 파생 클래스가 재정의를 빠뜨려도 조용히 0이 나온다. 순수 가상 함수(pure virtual function) =…
Base* p = new Derived; delete p;는 컴파일도 실행도 문제없어 보인다. 하지만 Base의 소멸자가 가상이 아니면 delete는 정적 타입 Base의 소멸자만 호출한다. Derived가 잡은 자원은 해제되지 않고, 표준상으로는 미정의 동작이다.…
지난 글에서 본 가상 함수는 호출마다 vtable을 거치는 간접 호출이라 인라인되지 않는다. 하지만 어떤 파생 타입을 쓸지 컴파일 시점에 이미 정해져 있다면 실행 시점 디스패치는 필요 없다. CRTP(Curiously Recurring Template…
shape.area()라는 호출이 실제로 어느 함수로 이어질지는 두 시점 중 하나에 정해진다. 컴파일할 때 정적 타입만 보고 정하면 정적 바인딩(static binding, early binding), 실행할 때 객체의 실제 타입을 보고 정하면 동적…
파생 클래스 객체를 기반 클래스의 포인터나 참조로 다루는 것을 업캐스팅(upcasting)이라 한다. public 상속이면 캐스트 없이 암시적으로 일어나고, 다형성은 이 변환을 전제로 동작한다. 하지만 같은 업캐스팅이라도 포인터·참조가 아니라 값으로 일어나면 파생…
public 상속은 "파생 클래스는 기반 클래스의 일종이다(is-a)"를 뜻해서, 파생 객체를 기반 참조로 넘길 수 있고 기반의 public 인터페이스가 그대로 노출된다. private 상속은 의미가 다르다. 기반 클래스의 구현을 가져다 쓰지만 외부에는…
생성자를 public이 아닌 곳에 두면 "누가 이 객체를 만들 수 있는가"를 클래스가 직접 정할 수 있다. protected 생성자는 파생 클래스만 기반 부분을 만들게 해서, 순수 가상 함수 없이도 기반 클래스를…