Advanced C++

[C++] Class type name 얻기

longseabear 2023. 10. 14. 17:31

[C++] compile time에서 class type name 얻기

**PRETTY_FUNCTIONFUNCSIG**

PRETTY_FUNCTION(clang, GNUC)과 FUNCSIG(MSVC)를 사용해서 함수 및 메소드의 이름과 시그니처 정보를 얻을 수 있다. 주로 디버깅과 로깅 작업에서 사용하기 위해 정의된 매크로이다. 중요한 것은 template가 포함되어있을 때 template 이름도 같이 포함해서 리턴해준다는 것이다. 각 OS에서 위의 두 매크로를 잘 활용하면 get_class_name<원하는 타입 명>() 형식으로 원하는 타입의 이름을 스트링으로 얻을 수 있다.

#include <iostream>
using namespace std;
class MyTypeName {
    int val;
};

template<typename Type>
constexpr auto get_class_name() noexcept {
    cout << __FUNCSIG__ << "\n";
    return;
}

int main() {
    cout << __FUNCSIG__ <<"\n";
    get_class_name<MyTypeName>();
    return 0;
}

/* 출력
int __cdecl main(void)
auto __cdecl get_class_name<class MyTypeName>(void) noexcept
*/
#include <iostream>
using namespace std;
class MyTypeName{
    int val;
};

template<typename Type>
constexpr auto get_class_name() noexcept {
    cout << __PRETTY_FUNCTION__ << "\n";
    return;
}

int main(){
    cout << __PRETTY_FUNCTION__;
    get_class_name<MyTypeName>();
    return 0;
}

/* 출력
int main()
constexpr auto stripped_type_name() [with Type = MyTypeName]
*/

문제는 출력이 다르다는 것이다. MVC의 경우 실제 템플릿을 사용하는 것 처럼 “<>”로 구분되어 있고, gnu의 경우 [With Type = Class 명]으로 따로 구분한다. 따라서, 버전에 따라 parsing 버전이 달라져야 한다.

규칙을 찾아보면, MVC는 <로 시작해서 >로 끝나고, GNU는 =로 시작해서 ]로 끝나는 것을 알 수 있다. 미리 시작과 끝을 os에 따라 다음과 같이 정의하자.

#if defined(__GNUC__) || defined(__clang__) // GCC 컴파일러에서 정의되는 매크로
    #define __MY_PRETTY_FUNCTION_SIGNITURE __PRETTY_FUNCTION__
    #define __MY_PRETTY_FUNCTION_START '='
    #define __MY_PRETTY_FUNCTION_END ']'
#elif defined(_MSC_VER) // Microsoft Visual C++에서 정의되는 매크로
    #define __MY_PRETTY_FUNCTION_SIGNITURE __FUNCSIG__
    #define __MY_PRETTY_FUNCTION_START '<'
    #define __MY_PRETTY_FUNCTION_END '>'
#endif

이제 Compile time에 해당 string을 parsing하여 클래스 이름만 얻을 수 있으면 된다.

String_view

c++ 17에서 추가된 feature로 메모리 할당 없이 읽기 전용으로 stirng에 대한 여러 유틸함수를 사용할 수 있게 해준다.

사전에 정의한 START와 END 매크로를 이용하여 class 이름을 parsing 한다.

template<typename Type>
[[nodiscard]] constexpr auto get_class_name() noexcept {
    std::string_view pretty_function{ __MY_PRETTY_FUNCTION_SIGNITURE };
    auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(__MY_PRETTY_FUNCTION_START) + 1);
    auto value = pretty_function.substr(first, pretty_function.find_last_of(__MY_PRETTY_FUNCTION_END) - first);
    return value;
}

바로 사용해도 상관없지만, compile time에 수행되도록 강제해보자. template의 default 할당자에 해당 함수를 쓰게하면, compile time에 해당 type에 대한 함수가 실행되도록 강제되고, 모두 같은 값을 사용할 것이다.

template<typename Type, auto = get_class_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr std::string_view type_name(int) noexcept {
    constexpr auto value = get_class_name<Type>();
    return value;
}

Hash

컴파일 타임에 Type에 대한 hash값을 다음과 같은 방식으로 얻을 수 있다.

template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr auto get_type_hash(){
    unsigned int h = 0;
    for (auto x : stripped_type_name<Type>()) h = 65599 * h + x;
    return h ^ (h >> 16);
}
  • x65599 hash를 사용하여 간단하게 구현하였다.

Test

constexpr auto A = type_name<MyTypeName>(0);
const auto B = type_name<MyTypeName>(0);
constexpr auto C = type_name<MyTypeName2>(0);
auto D = type_name<MyTypeName2>(0);

constexpr auto a = get_type_hash<MyTypeName>();

cout << A << " " << B << " " << C << " " << D;
cout << "\n" << a  << "\n";

/* 출력
int __cdecl main(void)
class MyTypeName class MyTypeName class MyTypeName2 class MyTypeName2
2354176136
*/

Assembly를 켜서 알 수 있는 사실은 사용할 때에도 constexpr 명령어를 붙여 상수 타임에 동작함을 보장해주어야 한다. constexpr을 붙이지 않으면 type_name 함수까지는 접근하게 된다.

Hash는 어떠한 함수 콜 없이 바로 값을 할당하는 것을 확인할 수 있다.

 

Source: AdvancedCppStudy/getClassNameExample.cpp at main · Longseabear/AdvancedCppStudy (github.com)

Reference: 컴파일 중에 문자열해쉬 만들기....(를 시도해 보자? -_-) | 포프머신 (popekim.com)

 

컴파일 중에 문자열해쉬 만들기....(를 시도해 보자? -_-) | 포프머신

포인터의 확실한 이해 | C 언어 독학 | 모든 프로그래밍 언어의 어머니 5차 산업혁명을 책임질 컴퓨터 하드웨어 지식! POCU 아카데미에서 C 언어를 마스터하세요!

blog.popekim.com

skypjack/entt: Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more (github.com)

 

GitHub - skypjack/entt: Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more

Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more - GitHub - skypjack/entt: Gaming meets modern C++ - a fast and reliable entity component system (ECS) and m...

github.com