c++ dynamic_cast 实现原理
算法
前置知识
vtable、vptr
RTTI layout
- 每个vtable都应包含 当前vptr与源对象vptr的偏移量,其地址为 :vptr - 2个字
- 每个vtable都应包含 继承类的std::type_info,其地址为 : vptr - 1个字
主要的操作
dynamic_cast主要是操作多态类,并且一些继承关系在编译期就能确认并进行不合规的报错,所以dynamic_cast在运行时主要做:
- dynamic_cast<void cv*>, which returns a pointer to the complete lvalue, 可以用于判断两个指针是不是实际相等
- base-to-derived
- cross cast, 例如
dynamic_cast<Left*>(pRight)
需要保证pRight指向了MostDeriverdBaseClass / \ V V Left Right \ / V V MostDerived
源码
gcc
__dynamic_cast (const void *src_ptr, // object started from
const __class_type_info *src_type, // type of the starting object
const __class_type_info *dst_type, // desired target type
ptrdiff_t src2dst) // how src and dst are related
{
if (__builtin_expect(!src_ptr, 0))
return NULL; // Handle precondition violations gracefully.
const void *vtable = *static_cast <const void *const *> (src_ptr);
const vtable_prefix *prefix =
(adjust_pointer <vtable_prefix>
(vtable, -ptrdiff_t (offsetof (vtable_prefix, origin))));
const void *whole_ptr =
adjust_pointer <void> (src_ptr, prefix->whole_object);
const __class_type_info *whole_type = prefix->whole_type;
__class_type_info::__dyncast_result result;
// If the whole object vptr doesn't refer to the whole object type, we're
// in the middle of constructing a primary base, and src is a separate
// base. This has undefined behavior and we can't find anything outside
// of the base we're actually constructing, so fail now rather than
// segfault later trying to use a vbase offset that doesn't exist.
const void *whole_vtable = *static_cast <const void *const *> (whole_ptr);
const vtable_prefix *whole_prefix =
(adjust_pointer <vtable_prefix>
(whole_vtable, -ptrdiff_t (offsetof (vtable_prefix, origin))));
if (whole_prefix->whole_type != whole_type)
return NULL;
// Avoid virtual function call in the simple success case.
if (src2dst >= 0
&& src2dst == -prefix->whole_object
&& *whole_type == *dst_type)
return const_cast <void *> (whole_ptr);
whole_type->__do_dyncast (src2dst, __class_type_info::__contained_public,
dst_type, whole_ptr, src_type, src_ptr, result);
if (!result.dst_ptr)
return NULL;
if (contained_public_p (result.dst2src))
// Src is known to be a public base of dst.
return const_cast <void *> (result.dst_ptr);
if (contained_public_p (__class_type_info::__sub_kind
(result.whole2src & result.whole2dst)))
// Both src and dst are known to be public bases of whole. Found a valid
// cross cast.
return const_cast <void *> (result.dst_ptr);
if (contained_nonvirtual_p (result.whole2src))
// Src is known to be a non-public nonvirtual base of whole, and not a
// base of dst. Found an invalid cross cast, which cannot also be a down
// cast
return NULL;
if (result.dst2src == __class_type_info::__unknown)
result.dst2src = dst_type->__find_public_src (src2dst, result.dst_ptr,
src_type, src_ptr);
if (contained_public_p (result.dst2src))
// Found a valid down cast
return const_cast <void *> (result.dst_ptr);
// Must be an invalid down cast, or the cross cast wasn't bettered
return NULL;
}
clang
__dynamic_cast(const void *static_ptr, const __class_type_info *static_type,
const __class_type_info *dst_type,
std::ptrdiff_t src2dst_offset) {
// Possible future optimization: Take advantage of src2dst_offset
// Get (dynamic_ptr, dynamic_type) from static_ptr
void **vtable = *static_cast<void ** const *>(static_ptr);
ptrdiff_t offset_to_derived = reinterpret_cast<ptrdiff_t>(vtable[-2]);
const void* dynamic_ptr = static_cast<const char*>(static_ptr) + offset_to_derived;
const __class_type_info* dynamic_type = static_cast<const __class_type_info*>(vtable[-1]);
// Initialize answer to nullptr. This will be changed from the search
// results if a non-null answer is found. Regardless, this is what will
// be returned.
const void* dst_ptr = 0;
// Initialize info struct for this search.
__dynamic_cast_info info = {dst_type, static_ptr, static_type, src2dst_offset, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
// Find out if we can use a giant short cut in the search
if (is_equal(dynamic_type, dst_type, false))
{
// Using giant short cut. Add that information to info.
info.number_of_dst_type = 1;
// Do the search
dynamic_type->search_above_dst(&info, dynamic_ptr, dynamic_ptr, public_path, false);
#ifdef _LIBCXX_DYNAMIC_FALLBACK
// The following if should always be false because we should definitely
// find (static_ptr, static_type), either on a public or private path
if (info.path_dst_ptr_to_static_ptr == unknown)
{
// We get here only if there is some kind of visibility problem
// in client code.
syslog(LOG_ERR, "dynamic_cast error 1: Both of the following type_info's "
"should have public visibility. At least one of them is hidden. %s"
", %s.\n", static_type->name(), dynamic_type->name());
// Redo the search comparing type_info's using strcmp
info = {dst_type, static_ptr, static_type, src2dst_offset, 0};
info.number_of_dst_type = 1;
dynamic_type->search_above_dst(&info, dynamic_ptr, dynamic_ptr, public_path, true);
}
#endif // _LIBCXX_DYNAMIC_FALLBACK
// Query the search.
if (info.path_dst_ptr_to_static_ptr == public_path)
dst_ptr = dynamic_ptr;
}
else
{
// Not using giant short cut. Do the search
dynamic_type->search_below_dst(&info, dynamic_ptr, public_path, false);
#ifdef _LIBCXX_DYNAMIC_FALLBACK
// The following if should always be false because we should definitely
// find (static_ptr, static_type), either on a public or private path
if (info.path_dst_ptr_to_static_ptr == unknown &&
info.path_dynamic_ptr_to_static_ptr == unknown)
{
syslog(LOG_ERR, "dynamic_cast error 2: One or more of the following type_info's "
"has hidden visibility or is defined in more than one translation "
"unit. They should all have public visibility. "
"%s, %s, %s.\n", static_type->name(), dynamic_type->name(),
dst_type->name());
// Redo the search comparing type_info's using strcmp
info = {dst_type, static_ptr, static_type, src2dst_offset, 0};
dynamic_type->search_below_dst(&info, dynamic_ptr, public_path, true);
}
#endif // _LIBCXX_DYNAMIC_FALLBACK
// Query the search.
switch (info.number_to_static_ptr)
{
case 0:
if (info.number_to_dst_ptr == 1 &&
info.path_dynamic_ptr_to_static_ptr == public_path &&
info.path_dynamic_ptr_to_dst_ptr == public_path)
dst_ptr = info.dst_ptr_not_leading_to_static_ptr;
break;
case 1:
if (info.path_dst_ptr_to_static_ptr == public_path ||
(
info.number_to_dst_ptr == 0 &&
info.path_dynamic_ptr_to_static_ptr == public_path &&
info.path_dynamic_ptr_to_dst_ptr == public_path
)
)
dst_ptr = info.dst_ptr_leading_to_static_ptr;
break;
}
}
return const_cast<void*>(dst_ptr);
}