Reputation: 93274
#include <memory>
struct foo { };
int main() { std::make_shared<foo>(); }
The asssembly generated by both g++7
and clang++5
with -fno-exceptions -Ofast
for the code above:
Contains a single call to operator new
if -fno-rtti
is not passed.
Contains two separate calls to operator new
if -fno-rtti
is passed.
This can be easily verified on gcc.godbolt.org (clang++5
version):
Why is this happening? Why does disabling RTTI prevent make_shared
from unifying the object and control block allocations?
Upvotes: 25
Views: 1656
Reputation: 21576
Naturally, std::shared_ptr
will be implemented with the assumption of the compiler supporting rtti
. But it can be implemented without it. See shared_ptr without RTTI? .
Taking a cue from this old GCC's libstdc++ #42019 bug. We can see that Jonathan Wakely added a fix to make this possible without RTTI.
In GCC's libstdc++, std::make_shared
uses the services of std::allocated_shared
which uses a non-standard constructor(as seen in the code, reproduced below).
As seen in this patch, from line 753, you can see that getting the default deleter simply requires using the services of typeid
if RTTI is enabled, otherwise, it requires a separate allocation that doesn't depend on RTTI.
EDIT: 9 - May -2017: removed copyrighted code previously posted here
I haven't investigated libcxx
, but I want to believe they did similar thing....
Upvotes: 11
Reputation: 275385
No good reason. This looks like a QoI issue in libstdc++.
Using clang 4.0, libc++ does not have this issue., while libstdc++ does.
The libstdc++ implementation with RTTI relies on 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);
and in general, get_deleter
isn't possible to implement without RTTI.
It appears that it is using the deleters position and the tag to store the T
in this implementation.
Basically, the RTTI version used get_deleter
. get_deleter
relied on RTTI. Getting make_shared
to work without RTTI
required rewriting it, and they took an easy route that caused it to do two allocations.
make_shared
unifies the T
and reference counting blocks. I suppose with both variable sized deleters and variable sized T
things get nasty, so they reused the deleter's variable sized block to store the T
.
A modified (internal) get_deleter
that did not do RTTI and returned a void*
might be enough to do what they need from this deleter; but possibly not.
Upvotes: 6
Reputation: 67723
Why does disabling RTTI prevent make_shared from unifying the object and control block allocations?
You can see from the assembler (just pasting the text is really preferable to both linking and to taking pictures of it) that the unified version doesn't allocate a simple foo
but an std::_Sp_counted_ptr_inplace
, and further that that type has a vtable (recall it needs a virtual destructor in general, to cope with custom deleters)
mov QWORD PTR [rax], OFFSET FLAT:
vtable for
std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
(__gnu_cxx::_Lock_policy)2>+16
If you disable RTTI, it can't generate the inplace counted pointer because that needs to be virtual.
Note that the non-inplace version still refers to a vtable, but it seems to just be storing the de-virtualized destructor address directly.
Upvotes: 12