std::any 没有 RTTI,它是如何工作的?
std::any without RTTI, how does it work?
如果我想使用 std::any
,我可以在关闭 RTTI 的情况下使用它。下面的示例编译并按预期运行 -fno-rtti
和 gcc。
int main()
{
std::any x;
x=9.9;
std::cout << std::any_cast<double>(x) << std::endl;
}
但是std::any
如何存储类型信息呢?如我所见,如果我使用 "wrong" 类型调用 std::any_cast
,我会按预期得到 std::bad_any_cast
异常。
这是如何实现的,或者这可能只是一个 gcc 功能?
我发现 boost::any
也不需要 RTTI,但我发现也不是如何解决的。 .
深入研究 STL header 本身没有给我任何答案。该代码对我来说几乎不可读。
手动实现有限的 RTTI 并不难。您将需要静态通用函数。在不提供完整实现的情况下,我可以说这么多。
这是一种可能性:
class meta{
static auto id(){
static std::atomic<std::size_t> nextid{};
return ++nextid;//globally unique
};
std::size_t mid=0;//per instance type id
public:
template<typename T>
meta(T&&){
static const std::size_t tid{id()};//classwide unique
mid=tid;
};
meta(meta const&)=default;
meta(meta&&)=default;
meta():mid{}{};
template<typename T>
auto is_a(T&& obj){return mid==meta{obj}.mid;};
};
这是我的第一个观察;远非理想,缺少许多细节。可以使用 meta
的一个实例作为他假定的 std::any
.
实现的 none 静态数据成员
可能的解决方案之一是为可能存储在 any
中的每种类型生成唯一 ID(我假设您知道更多 any
的内部工作方式)。可以执行此操作的代码可能如下所示:
struct id_gen{
static int &i(){
static int i = 0;
return i;
}
template<class T>
struct gen{
static int id() {
static int id = i()++;
return id;
}
};
};
有了这个实现你可以使用类型的 id 而不是 RTTI typeinfo
来快速检查类型。
注意函数内部静态变量和静态函数的使用。这样做是为了避免静态变量初始化顺序未定义的问题。
TL;DR; std::any
持有指向模板化 class 的静态成员函数的指针。此函数可以执行许多操作并且特定于给定类型,因为函数的实际实例取决于 class.
的模板参数
libstdc++中std::any
的实现并不复杂,大家可以看看:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any
基本上,std::any
包含两件事:
- 指向(动态)分配存储的指针;
- 指向 "storage manager function" 的指针:
void (*_M_manager)(_Op, const any*, _Arg*);
当您使用类型为 T
的对象构造或分配新的 std::any
时,_M_manager
指向特定于类型 T
的函数(实际上是class 的静态成员函数特定于 T
):
template <typename _ValueType,
typename _Tp = _Decay<_ValueType>,
typename _Mgr = _Manager<_Tp>, // <-- Class specific to T.
__any_constructible_t<_Tp, _ValueType&&> = true,
enable_if_t<!__is_in_place_type<_Tp>::value, bool> = true>
any(_ValueType&& __value)
: _M_manager(&_Mgr::_S_manage) { /* ... */ }
由于此函数特定于给定类型,因此您不需要 RTTI 来执行 std::any
所需的操作。
此外,很容易检查您是否在 std::any_cast
内转换为正确的类型。下面是std::any_cast
的gcc实现的核心:
template<typename _Tp>
void* __any_caster(const any* __any) {
if constexpr (is_copy_constructible_v<decay_t<_Tp>>) {
if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage) {
any::_Arg __arg;
__any->_M_manager(any::_Op_access, __any, &__arg);
return __arg._M_obj;
}
}
return nullptr;
}
您可以看到,这只是您尝试转换的对象内的存储函数 (_any->_M_manager
) 与您要转换为的类型的管理器函数 (&any::_Manager<decay_t<_Tp>>::_S_manage
).
class _Manager<_Tp>
实际上是 _Manager_internal<_Tp>
或 _Manager_external<_Tp>
的别名,具体取决于 _Tp
。
此 class 还用于 std::any
class.
的对象分配/构造
如果我想使用 std::any
,我可以在关闭 RTTI 的情况下使用它。下面的示例编译并按预期运行 -fno-rtti
和 gcc。
int main()
{
std::any x;
x=9.9;
std::cout << std::any_cast<double>(x) << std::endl;
}
但是std::any
如何存储类型信息呢?如我所见,如果我使用 "wrong" 类型调用 std::any_cast
,我会按预期得到 std::bad_any_cast
异常。
这是如何实现的,或者这可能只是一个 gcc 功能?
我发现 boost::any
也不需要 RTTI,但我发现也不是如何解决的。
深入研究 STL header 本身没有给我任何答案。该代码对我来说几乎不可读。
手动实现有限的 RTTI 并不难。您将需要静态通用函数。在不提供完整实现的情况下,我可以说这么多。 这是一种可能性:
class meta{
static auto id(){
static std::atomic<std::size_t> nextid{};
return ++nextid;//globally unique
};
std::size_t mid=0;//per instance type id
public:
template<typename T>
meta(T&&){
static const std::size_t tid{id()};//classwide unique
mid=tid;
};
meta(meta const&)=default;
meta(meta&&)=default;
meta():mid{}{};
template<typename T>
auto is_a(T&& obj){return mid==meta{obj}.mid;};
};
这是我的第一个观察;远非理想,缺少许多细节。可以使用 meta
的一个实例作为他假定的 std::any
.
可能的解决方案之一是为可能存储在 any
中的每种类型生成唯一 ID(我假设您知道更多 any
的内部工作方式)。可以执行此操作的代码可能如下所示:
struct id_gen{
static int &i(){
static int i = 0;
return i;
}
template<class T>
struct gen{
static int id() {
static int id = i()++;
return id;
}
};
};
有了这个实现你可以使用类型的 id 而不是 RTTI typeinfo
来快速检查类型。
注意函数内部静态变量和静态函数的使用。这样做是为了避免静态变量初始化顺序未定义的问题。
TL;DR; std::any
持有指向模板化 class 的静态成员函数的指针。此函数可以执行许多操作并且特定于给定类型,因为函数的实际实例取决于 class.
libstdc++中std::any
的实现并不复杂,大家可以看看:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any
基本上,std::any
包含两件事:
- 指向(动态)分配存储的指针;
- 指向 "storage manager function" 的指针:
void (*_M_manager)(_Op, const any*, _Arg*);
当您使用类型为 T
的对象构造或分配新的 std::any
时,_M_manager
指向特定于类型 T
的函数(实际上是class 的静态成员函数特定于 T
):
template <typename _ValueType,
typename _Tp = _Decay<_ValueType>,
typename _Mgr = _Manager<_Tp>, // <-- Class specific to T.
__any_constructible_t<_Tp, _ValueType&&> = true,
enable_if_t<!__is_in_place_type<_Tp>::value, bool> = true>
any(_ValueType&& __value)
: _M_manager(&_Mgr::_S_manage) { /* ... */ }
由于此函数特定于给定类型,因此您不需要 RTTI 来执行 std::any
所需的操作。
此外,很容易检查您是否在 std::any_cast
内转换为正确的类型。下面是std::any_cast
的gcc实现的核心:
template<typename _Tp>
void* __any_caster(const any* __any) {
if constexpr (is_copy_constructible_v<decay_t<_Tp>>) {
if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage) {
any::_Arg __arg;
__any->_M_manager(any::_Op_access, __any, &__arg);
return __arg._M_obj;
}
}
return nullptr;
}
您可以看到,这只是您尝试转换的对象内的存储函数 (_any->_M_manager
) 与您要转换为的类型的管理器函数 (&any::_Manager<decay_t<_Tp>>::_S_manage
).
class _Manager<_Tp>
实际上是 _Manager_internal<_Tp>
或 _Manager_external<_Tp>
的别名,具体取决于 _Tp
。
此 class 还用于 std::any
class.