We want to share implementation details between std::function and its new cousins which we are designing. A program which targets a class foo by std::function<void()> shouldn’t pay the same price a second time when it initializes a std::unique/movable_function<void()> with a foo as well. However, we also don’t want to use exactly the same erasure class for both wrappers, because then unique_function would instantiate copy constructors it cannot use, representing a different kind of bloat.
The solution is to share some functions between similar templates. This might violate libc++’s ABI standards, or it might not. Internal functions, including erasure methods, are declared using this macro:
#define _LIBCPP_INLINE_VISIBILITY attribute ((always_inline))
The idea is that it’s dangerous for a function to be exported from a DLL, then vanish in a subsequent version at the whim of the inliner. A function which is always inlined will never be visible in the first place. This stabilizes not only libc++.dylib, but also user libraries with respect to the standard library.
The danger, but not the solution, also applies to vtables. If libc++ (or another library) uses a polymorphic class, then it’s committed to exporting it forever. The erasures of std::function are implemented using vtables, but this isn’t the only possible approach. libstdc++ uses switch statements instead.
Introducing new-but-similar specializations to the the vtable-polymorphic erasure class is making me confused.
- Is it OK at all that shared libraries tend to export standard erasures? Or is this all a giant bug requiring a ground-up rewrite? (I’ve not dug into the history.)
- If OK, should shared functions dominated by the vtables of several specializations be marked noinline? (Such a function is unreachable except through a vtable, which is already exported.) Is there some macro or protocol for this?
- I’m spending this effort optimizing a case without first analyzing it. Has libc++ already decided whether to care about std::function overhead, or should I just make fully-independent specializations and be through with it? Is there a known, quantified margin of headroom?
Regarding #2, why does the virtual function std::__function::__base::~__base have inline visibility? Yes, it’s called directly by the derived class destructor, but that is only accessible by a vtable. Is there some potential ABI error if it omitted the visibility spec, or is it just erring on the side of caution? (All the other virtual functions omit the spec.)
- Thanks much,