Meta 모듈은 런타임 타입 정보(RTTI)와 리플렉션 기능을 제공합니다. C++의 기본 RTTI보다 더 강력하고 효율적인 타입 시스템을 구현하고 있으며, Byeol 언어의 타입 시스템 기반이 됩니다. Monostate 패턴, Template Metaprogramming, 재귀적 초기화 등을 활용하여 설계되었습니다. 다음과 같은 기능을 제공합니다:
meta 모듈의 주요 클래스:
type 클래스는 Meta 모듈의 핵심이 되는 클래스예요. type 에 대한 다음의 기본적인 API를 제공합니다.
다음 함수들로 타입의 기본 정보를 얻을 수 있습니다:
isTemplate(): 템플릿 클래스 여부를 반환합니다.isAbstract(): 추상 클래스 여부를 반환합니다.getName(): 클래스명을 반환합니다. 이름은 demangle 되어 반환됩니다.getSupers()
super class들을 리스트에 담아 반환합니다. 이 클래스의 직접적인 부모 클래스는 리스트 마지막 원소에, 보다 더 super class일 수록 리스트 앞에 담겨있습니다.
예를들어 다음과 같은 코드를 보세요.
getSubs()
sub class들을 리스트에 담아 반환합니다. getSupers()와 마찬가지 방식으로, 가까운 sub 일 수록 리스트 앞에 담겨있습니다.
isSuper(const rhs& type)
this class가 rhs보다 super 클래스 인지 체크합니다. dynamic_cast과 동일한 기능이지만 알고리즘 복잡도가 더 뛰어납니다.
일반적으로 dynamic_cast는 vtable을 순회해서 체크합니다. 반면 meta는 메타 정보 생성시 super 클래스 계층도를 통해서 자신이 몇 tier의 자손인지를 미리 계산해둡니다. 이 tier 인덱스를 사용한 배열 접근과 타입 포인터의 주소값 비교를 통해 O(1) 시간에 타입 계층 확인이 가능합니다. 이는 vtable 순회 방식에 비해 캐시 친화적이고 예측 가능한 성능을 제공합니다.
isSub(const rhs& type)
isSuper와 반대로 동작합니다.
타입 계층 검사 예제
make()
이 type의 기본생성자로 인스턴스를 생성합니다. 만약, 기본생성자가 없다면 nullptr가 반환되니 주의하세요.
init()으로 초기화를, rel()로 해제를 합니다. 이는 BY_INIT_META 매크로를 통해서 자동으로 처리가 되는 부분이라 외부에서 직접 호출할 경우는 드뭅니다.
ttype 클래스는 사용자가 메타 정보를 다루고자할때 진입점이 되는 클래스예요. ttype 를 사용할때는 매번 객체를 생성해서 사용해야 합니다. 전체적으로 monostate 패턴으로 설계 되어있어서 매번 객체를 만들어 사용하더라도 값은 공유하기 때문에 추가비용은 들지 않습니다.
Meta 모듈의 전체적인 설계에 대해 파악하고자 한다면 핵심이 되는 type 을 먼저 살펴보세요.
ttype 기본 사용 예제
isTemplate() 이나 isAbstract(), getName() 같은 타입정보는 ttypeBase 에서 Template Metaprogramming을 통해 컴파일 타임에 채워줍니다. 반면, 클래스 상속과 같은 계층 정보는 불가능해요. 따라서 계층 정보를 채워주는 과정은 프로세스 시작후 최초 1번은 실행이 되어야 되어야 합니다. type에 init()이 존재하는 이유는 런타임에 클래스 계층 정보를 구성하기 위해서입니다.
그리고 그 계층을 만드는 핵심 조건은 각 사용자는 자신의 클래스를 정의할때 typedef로 super를 정의해야 한다라는 제약사항으로 해결합니다. 모든 클래스에 super가 존재한다면, init()함수 안에서 super::init()도 호출할 수 있기 때문에 간단한 재귀함수만으로도 클래스 계층을 구성할 수 있게 됩니다.
참고: 부모 클래스가 없는 최상위 클래스는 typedef adam super;로 정의하여 adam 에 연결됩니다. 이를 통해 모든 타입이 단일 부모로부터의 계층 구조를 이루게 됩니다. init()의 예시 코드를 한번 볼까요?
실제 핵심 코드는 위와 크게 다르지 않습니다. 실제로 몇 줄의 간단한 코드만으로도 클래스 계층을 구성합니다. 이 과정은 재귀를 사용하긴 하지만, 프로그램 실행 후 1번만 발생하기 때문에 비용도 비교적 적습니다.
앞서 얘기한 대로 각 type 클래스들의 정보는 init() 함수를 호출하면 생성되고 채워집니다. 문제는 각 클래스들에 대한 타입 객체를 일일이 사용자가 생성하고 각각을 명시적으로 init()을 호출하는 것은 굉장히 비효율적이라는 점입니다.
이걸 해결하고자 BY_INIT_META 매크로를 사용합니다. BY_INIT_META 는 BY_INITIATOR 매크로를 응용하는데, 이 매크로는 static 객체에 람다함수를 끼워넣음으로써 원하는 동작을 main() 함수가 시작되기 전에 실행하는 매크로입니다.
이 매크로를 응용하면 init() 함수를 main() 함수가 호출되기 직전에 실행하는 게 가능해집니다. 한가지 제약사항으로는 각 클래스 선언시에 BY_INIT_META (MyClass)를 추가해야 한다는 점입니다.
주의: static 초기화 순서를 사용하므로, 여러 translation unit 간의 초기화 순서는 보장되지 않습니다. 다만, 각 type의 init()은 재귀적으로 부모를 먼저 초기화 하며, 한번 초기화 되면 두번째부터는 skip 되는 구조이므로, 싱글 스레드 환경이라면 타입 계층 구조 내에서는 부모에서 자식으로 가는 초기화 순서가 보장됩니다.
이러한 메타 DSL 형태의 매크로들은 BY 매크로에 의해서 실행되도록 컨벤션으로 정의 되어있습니다. 그리고 나중에 Core 모듈에서 후술하겠지만, 이것 말고도 추가로 DSL 매크로를 정의해야 하기 때문에, 매번 BY_ prefix를 붙이는 불편함과, 매크로 이름이 충돌될 가능성을 줄이기 위해 BY 라는 DSL 확장 매크로를 사용합니다.
BY 매크로는 BY() 안에 BY_ prefix를 생략한 DSL 매크로를 , 을 붙여서 여러개 정의할 수 있습니다.
물론 이 역시 매번 4개나 되는 DSL 매크로를 클래스 정의시마다 추가하는건 매우 귀찮기 때문에 때문에, 대부분의 경우는 자주 사용하는 매크로들을 조합한 BY(CLASS()) 나 BY(ADT())를 통해서 한번에 정의합니다.
type 의 클래스 계층구조 상 아무런 부모도 없는 메타타입은 범용적으로 다루기가 어렵습니다. 예를들어 모든 메타타입에 대해서 동작하는 함수를 작성하고자 한다면, 다음과 같은 코드를 작성할 수 있어야 합니다.
이를 해결하고자 메타 정보를 구성할때 어떠한 부모클래스도 없는 클래스라면 부모를 adam 으로 정의합니다. 이제 다음과 같이 작성할 수 있게 됩니다.
adam을 활용한 타입 순회 예제
type 은 기본적으로도 비교적 많은 타입 정보를 제공하지만, byeol 처럼 언어를 다루는 경우에는 parameter나 return type과 같이 추가적인 정보를 담고 싶은 경우가 있습니다.
이때 쉽게 생각하면 type 을 상속받은 클래스를 만들면 되는거 아닌가라는 아이디어를 떠올리기 쉽지만 기억을 잘 떠올려보세요. 사용성을 위해서 우리는 ttype<T>에서 언급한 것처럼, 항상 사용자의 최종 진입점은 ttype<T>에 접근하면서 시작되어야 합니다. 문제는 ttype 의 코드를 Meta 모듈에 있고, 추가타입 정보를 넣길 원하는 쪽은 meta에 종속되는 더 상위 모듈이라는 점이죠. 이때는 종속하는 쪽에서 수정할 수는 없기 때문에 상속으로는 불가능하고 메타 타입을 주입하는 형태로 문제를 해결합니다. 이는 Template Metaprogramming과 SFINAE(Substitution Failure Is Not An Error) 기법을 사용한 Type Injection 이라고 부르는 패턴을 사용합니다.
핵심 코드는 ttypeBase 에 있습니다.
tmetaTypeDef는 template specialization을 사용하여, T에 typedef metaType이 있을 경우에는 해당타입을 반환하고, 없으면 type 을 반환합니다. ttype 은 ttypeBase 를 상속하며, ttypeBase 는 바로 tmetaTypeDef<T>::is를 상속합니다.
이걸 통해서 만약 class T에 대해 ttype<T>를 호출하는 순간, class T의 개발자가 typedef metaType MyType; 처럼 새로운 MyType 클래스를 선언하여 추가하면 해당 ttype<T>()로 객체를 만들었을때 MyType을 기반으로 해서 만들어지게 됩니다. 이 메커니즘은 Policy-based Design의 한 형태로, 타입별로 다른 메타 타입 구현을 주입할 수 있게 합니다.
실제로 이 기능은 Core 모듈에서 ntype 을 주입하기 위해 사용합니다. 자세한 내용은 ntype 을 참조하세요.
마지막으로 meta 정보가 초기화 되는 과정을 다시 복기해 보죠.
다음 문서: memlite 모듈 - 커스텀 메모리 관리