为什么`std :: make_shared`用`-fno-rtti`执行两次单独的分配?

Vit*_*meo 25 c++ rtti c++11

#include <memory>
struct foo { };
int main() { std::make_shared<foo>(); }
Run Code Online (Sandbox Code Playgroud)

由这两个所产生的asssembly g++7clang++5-fno-exceptions -Ofast上面的代码:

  • 包含对单个呼叫operator new,如果-fno-rtti通过.

  • 包含两个单独的呼叫operator new如果-fno-rtti通过.

这可以gcc.godbolt.org (clang++5版本)轻松验证:

以上godbolt链接的屏幕截图与highlighed operator new call

为什么会这样?为什么禁用RTTI会阻止make_shared统一对象控制块分配?

Use*_*ess 12

为什么禁用RTTI会阻止make_shared统一对象和控制块分配?

您可以从汇编程序中看到(只是粘贴文本非常适合链接和拍照),统一版本不会分配简单foo但是a std::_Sp_counted_ptr_inplace,而且该类型有vtable(回想一下它需要一个一般的虚拟析构函数,以应对自定义删除器)

mov QWORD PTR [rax], OFFSET FLAT:
  vtable for
  std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
  (__gnu_cxx::_Lock_policy)2>+16
Run Code Online (Sandbox Code Playgroud)

如果禁用RTTI,则无法生成就地计数指针,因为它必须是虚拟的.

请注意,非就位版本仍然引用vtable,但它似乎只是直接存储去虚拟化的析构函数地址.

  • 是的,这个文本可以在godbolt上找到......现在.但谁知道这个网站仍在运行时链接是否会中断?并且,是的,我认为这是一个QoI实现,从某种意义上说,实施者为no-rtti写了第二个版本非常慷慨.我不会责怪他们只是说没有RTTI_就不能实现_shared_ptr. (2认同)
  • 如果使用libc ++,即使禁用rtti也只能进行单一分配.除了使用`dynamic_cast`或`typeid`之外,您可以使用`-fno-rtti`完成所有操作. (2认同)
  • 不,`shared_ptr`在删除器(模板)上没有参数化.实例确实有一个删除器,但是`make_shared`必须创建自己的删除器,在不解除分配的情况下销毁`T`.使用`make_shared`,你*知道你正在摧毁的确切类型*.需要RTTI的驱逐舰是荒谬的. (2认同)

Whi*_*TiM 11

当然,std::shared_ptr将在编译器支持的假设下实现rtti.但它可以在没有它的情况下实施.请参阅没有RTTI的shared_ptr?.

从这个旧的GCC的libstdc ++ #42019错误中得到启示.我们可以看到Jonathan Wakely在没有RTTI的情况下添加了一个修复程序.

在GCC的libstdc ++中,std::make_shared使用其服务std::allocated_shared使用非标准构造函数(如下面的代码所示).

如此补丁中所示,从第753行可以看出,获取默认删除器只需要使用typeid 如果启用RTTI的服务,否则,它需要一个不依赖于RTTI 的单独分配.

编辑: 9 - 2017年5月:删除此前发布的受版权保护的代码

我没有调查libcxx,但我想相信他们做了类似的事情....


Yak*_*ont 6

没有充分的理由.这看起来像libstdc ++中的QoI问题.

使用clang 4.0,libc ++没有这个问题.,而libstdc ++确实如此.

使用RTTI的libstdc ++实现依赖于get_deleter:

void* __p = _M_refcount._M_get_deleter(typeid(__tag));
                  _M_ptr = static_cast<_Tp*>(__p);
                  __enable_shared_from_this_helper(_M_refcount, _M_ptr, _M_ptr);
_M_ptr = static_cast<_Tp*>(__p);
Run Code Online (Sandbox Code Playgroud)

一般来说,get_deleter没有RTTI就无法实现.

它似乎正在使用删除器位置和标记来存储T此实现.

基本上,使用的是RTTI版本get_deleter. get_deleter依赖于RTTI.获取make_shared无劳动RTTI需要重写它,并且他们采取了导致它做的两个拨款的简单的路线.

make_shared统一T和引用计数块.我想有两个可变大小的删除器和可变大小的T东西变得讨厌,所以他们重用了删除器的可变大小的块来存储T.

get_deleter未执行RTTI并返回a 的修改后的(内部)void*可能足以从此删除器中执行所需操作; 但可能不是.