对分配器感知中的向量元素的引用 class 调用复制构造函数

Reference to vector element in allocator aware class calls copy constructor

我有一个 class,其中包含一个向量的向量。它是分配器感知的。尝试调用 operator[] 将元素存储在引用中时,Visual Studio 2015 编译失败,AppleClang(最新版)没问题。

我不确定这是否是错误,哪个编译器是正确的,或者我的代码中是否存在某些未定义的行为。

举个简单的例子,尽量简单。

#include <cstdlib>
#include <memory>
#include <new>
#include <vector>

/* Allocator */
template <class T>
struct my_allocator {
    typedef T value_type;
    my_allocator() = default;

    template <class U>
    constexpr my_allocator(const my_allocator<U>&) noexcept {
    }

    T* allocate(std::size_t n) {
        if (n > std::size_t(-1) / sizeof(T))
            throw std::bad_alloc();
        if (auto p = static_cast<T*>(std::malloc(n * sizeof(T))))
            return p;
        throw std::bad_alloc();
    }

    void deallocate(T* p, std::size_t) noexcept {
        std::free(p);
    }
};

template <class T, class U>
bool operator==(const my_allocator<T>&, const my_allocator<U>&) {
    return true;
}
template <class T, class U>
bool operator!=(const my_allocator<T>&, const my_allocator<U>&) {
    return false;
}

/* Example Element */
struct X {
    X() = default;
    X(X&&) = default;
    X& operator=(X&&) = default;

    X(const X&) = delete;
    X& operator=(const X&) = delete;

    int test = 42;
};

/* Example Container Class */
template <class T, class Allocator = std::allocator<T>>
struct vec_of_vec {
    using OuterAlloc = typename std::allocator_traits<
            Allocator>::template rebind_alloc<std::vector<T, Allocator>>;

    vec_of_vec(const Allocator& alloc = Allocator{})
            : data(10, std::vector<T, Allocator>{ alloc },
                      OuterAlloc{ alloc }) {

        for (int i = 0; i < 10; ++i) {
            data[i].resize(42);
        }
    }

    std::vector<T, Allocator>& operator[](size_t i) {
        return data[i];
    }

    std::vector<std::vector<T, Allocator>, OuterAlloc> data;
};

/* Trigger Error */
int main(int, char**) {
    my_allocator<X> alloc;
    vec_of_vec<X, my_allocator<X>> test(alloc);

    X& ref_test = test[0][0]; // <-- Error Here!
    printf("%d\n", ref_test.test);

    return 0;
}

VS 尝试使用 X 的复制构造函数。

error C2280: 'X::X(const X &)': attempting to reference a deleted function

function main.cpp(42): note: see declaration of 'X::X'

我在使用分配器和 allocator_traits 时遗漏了什么吗?

GCC 错误揭示了正在发生的事情,在 VS2015 的情况下可能相同。

In file included from memory:65,
                 from prog.cc:2:
bits/stl_uninitialized.h: In instantiation of '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = __gnu_cxx::__normal_iterator<const X*, std::vector<X, my_allocator<X> > >; _ForwardIterator = X*; _Allocator = my_allocator<X>]':
bits/stl_vector.h:454:31:   required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = X; _Alloc = my_allocator<X>]'
bits/alloc_traits.h:250:4:   required from 'static std::_Require<std::__and_<std::__not_<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type>, std::is_constructible<_Tp, _Args ...> > > std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::vector<X, my_allocator<X> >; _Args = {const std::vector<X, my_allocator<X> >&}; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::_Require<std::__and_<std::__not_<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type>, std::is_constructible<_Tp, _Args ...> > > = void]'
bits/alloc_traits.h:344:16:   required from 'static decltype (std::allocator_traits<_Alloc>::_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::vector<X, my_allocator<X> >; _Args = {const std::vector<X, my_allocator<X> >&}; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; decltype (std::allocator_traits<_Alloc>::_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = void]'
bits/stl_uninitialized.h:351:25:   required from '_ForwardIterator std::__uninitialized_fill_n_a(_ForwardIterator, _Size, const _Tp&, _Allocator&) [with _ForwardIterator = std::vector<X, my_allocator<X> >*; _Size = long unsigned int; _Tp = std::vector<X, my_allocator<X> >; _Allocator = my_allocator<std::vector<X, my_allocator<X> > >]'
bits/stl_vector.h:1466:33:   required from 'void std::vector<_Tp, _Alloc>::_M_fill_initialize(std::vector<_Tp, _Alloc>::size_type, const value_type&) [with _Tp = std::vector<X, my_allocator<X> >; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = std::vector<X, my_allocator<X> >]'
bits/stl_vector.h:421:9:   required from 'std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = std::vector<X, my_allocator<X> >; _Alloc = my_allocator<std::vector<X, my_allocator<X> > >; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = std::vector<X, my_allocator<X> >; std::vector<_Tp, _Alloc>::allocator_type = my_allocator<std::vector<X, my_allocator<X> > >]'
prog.cc:59:42:   required from 'vec_of_vec<T, Allocator>::vec_of_vec(const Allocator&) [with T = X; Allocator = my_allocator<X>]'
prog.cc:76:46:   required from here
bits/stl_uninitialized.h:275:25: error: no matching function for call to '__gnu_cxx::__alloc_traits<my_allocator<X>, X>::construct(my_allocator<X>&, X*, const X&)'
      __traits::construct(__alloc, std::__addressof(*__cur), *__first);```

Wandbox

如果我们从下往上看,我们会看到:

  • vec_of_vec构造函数
  • filling vector<vector> 10 份空内向量
  • 建造副本

尽管内部向量是空的,但它的复制构造函数要求它包含的类型是可复制构造的。

P.S.

我不知道 clang 是如何克服这个问题的。它可能会识别 vector<vector> 如果填充了默认值(如果带有传递的分配器实例的构造函数仍然符合默认值),因此不使用复制而是使用默认构造

编辑:

修复错误替换

vec_of_vec(const Allocator& alloc = Allocator{})
        : data(10, std::vector<T, Allocator>{ alloc },
                  OuterAlloc{ alloc }) {

    for (int i = 0; i < 10; ++i) {
        data[i].resize(42);
    }
}

来自

vec_of_vec(const Allocator& alloc = Allocator{})
{
    data.resize(10); // here we don't `fill` it by copies but default-construct 10 instances
    for (int i = 0; i < 10; ++i) {
        data[i].resize(42);
    }
}

或有状态分配器的版本:

vec_of_vec(const Allocator& alloc = Allocator{}):
    data(OuterAlloc(alloc))
{
    for (int i = 0; i < 10; ++i) {
        data.emplace_back(alloc);
        data.back().resize(42);
    }
}