I am working on implementing a std::function myself. To do small object optimization I need to implement a storage which maybe store the object locally. As far as I know, the strict aliasing rule allows any type of data be stored in a bytes array, but prohibit extracting the corresponding type of value from the store array, unless use the std::launder
. std::launder
was introduced in cpp17 but I hope my codes can run in cpp14 and even cpp11. So I read the implementation of GCC and CLang. in GCC they defined a union _Any_data
as the storage, which has the attrbuite may_alias
, looks like I should supposed it can violate the strict aliasing. And in CLang they use an additional pointer to store the result of new
, like
if (sizeof(_Fun) <= sizeof(__buf_) &&
is_nothrow_copy_constructible<_Fp>::value &&
is_nothrow_copy_constructible<_FunAlloc>::value)
{
__f_ = ::new ((void*)&__buf_) _Fun(_VSTD::move(__f), _Alloc(__af));
}
else
{
typedef __allocator_destructor<_FunAlloc> _Dp;
unique_ptr<__func, _Dp> __hold(__af.allocate(1), _Dp(__af, 1));
::new ((void*)__hold.get()) _Fun(_VSTD::move(__f), _Alloc(__a));
__f_ = __hold.release();
}
I have searched many questions and answers on SO, such answer like https://stackoverflow.com/a/41625067/11139119 said it's legal to use the pointer returned by the placement new. But it must cost other 8-bytes space to store a pointer, which is a waste in a way, especially GCC only costs 32-bytes on the size of std::function
. So my question is, before cpp14, how I can avoid violating strict aliasing without spending 8-bytes space on an extra pointer?
The following codes are the part of my small object optimization storage. I have no idea about how to implement get()
without incurring undefined behavior before cpp17.
class StoragePool {
public:
StoragePool() = default;
template <class Tp, class... Args, std::enable_if_t<soo::IsSmallObject<Tp>::value, int> = 0>
void emplace(Args&&... args) noexcept(noexcept(Tp(std::forward<Args>(args)...))) {
new (mem_) Tp(std::forward<Args>(args)...);
}
template <class Tp, class... Args, std::enable_if_t<!soo::IsSmallObject<Tp>::value, int> = 0>
void emplace(Args&&... args) {
auto tmp = new (mem_) Tp*;
*tmp = new Tp(std::forward<Args>(args)...);
}
template <class Tp, std::enable_if_t<soo::IsSmallObject<Tp>::value, int> = 0>
Tp* get() noexcept {
#if !(__cplusplus < 201703L)
return std::launder(reinterpret_cast<Tp*>(mem_));
#else
// ...
#endif
}
template <class Tp, std::enable_if_t<!soo::IsSmallObject<Tp>::value, int> = 0>
Tp* get() noexcept {
#if !(__cplusplus < 201703L)
return *std::launder(reinterpret_cast<Tp**>(mem_));
#else
// ...
#endif
}
// other functions are omitted because they are unimportant in this question.
private:
alignas(soo::small_object_alignment) unsigned char mem_[soo::small_object_size];
};