skgbanga skgbanga - 4 years ago 84
C++ Question

Extra move constructions on custom allocator for clang

I am trying to write a custom allocator for vector. This is the skelton code so far:

#include <iostream>
#include <vector>

struct Noisy
{
Noisy() { std::cout << "Default ctor called" << '\n'; }
Noisy(const Noisy&) { std::cout << "Copy ctor called" << '\n'; }
Noisy& operator=(const Noisy&) { std::cout << "Copy assignment called" << '\n'; return *this; }

Noisy(Noisy&&) { std::cout << "Move ctor called" << '\n'; }
Noisy& operator=(Noisy&&) { std::cout << "Move assignment called" << '\n'; return *this; }
};

template <typename T>
struct StackAllocator : Noisy
{
using value_type = T;
T* allocate(std::size_t n) { return nullptr; }
void deallocate(T* p, std::size_t n) { }
};

int main()
{
using alloc_t = StackAllocator<int>;
auto alloc = alloc_t{};
std::vector<int, alloc_t> vec(alloc);
}


On gcc 6.3, this produces what I expected: (Online link)

Default ctor called
Copy ctor called


However, on clang 3.9, this produces: (Online link)

Default ctor called
Copy ctor called
Move ctor called
Move ctor called


This is the source code of clang vector implementation. (here), but I couldn't find anything that would explain the two extra move constructions. (Interestingly I discovered that it uses compressed pair to take advantage of EBO for empty allocators)

Answer Source

tldr; If you just follow the calls, the copy and two moves come from:

  1. Copy into the __compressed_pair constructor
  2. Move into the __libcpp_compressed_pair_imp constructor
  3. Move into the __second_ member

You call vector(allocator_type const&):

    _LIBCPP_INLINE_VISIBILITY explicit vector(const allocator_type& __a)
#if _LIBCPP_STD_VER <= 14
        _NOEXCEPT_(is_nothrow_copy_constructible<allocator_type>::value)
#else
        _NOEXCEPT
#endif
        : __base(__a)
    {
#if _LIBCPP_DEBUG_LEVEL >= 2
        __get_db()->__insert_c(this);
#endif
    }

which calls __vector_base(allocator_type const&):

template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
__vector_base<_Tp, _Allocator>::__vector_base(const allocator_type& __a)
    : __begin_(nullptr),
      __end_(nullptr),
      __end_cap_(nullptr, __a)
{
}

where __end_cap_ is a __compressed_pair<pointer, allocator_type>, where we invoke the constructor __compressed_pair(pointer, allocator_type):

_LIBCPP_INLINE_VISIBILITY __compressed_pair(_T1_param __t1, _T2_param __t2)
    : base(_VSTD::forward<_T1_param>(__t1), _VSTD::forward<_T2_param>(__t2)) {}

Now, neither of our types are the same and neither are empty or final, so the base constructor that we invoke is __libcpp_compressed_pair_imp<pointer, allocator_type, 0>(pointer, allocator_type):

_LIBCPP_INLINE_VISIBILITY __libcpp_compressed_pair_imp(_T1_param __t1, _T2_param __t2)
    : __first_(_VSTD::forward<_T1_param>(__t1)), __second_(_VSTD::forward<_T2_param>(__t2)) {}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download