Std::vector:: push_back не скопируемый объект дает ошибку компилятора

Я получаю ошибки компиляции на g++ (GCC) 4.7.2, но не на MSVC-2012 при попытке std::vector::push_back не копируемого (private copy constructor), а перемещаемого объекта. Для меня мой пример похож на многие другие примеры на SO и в других местах. Сообщение об ошибке заставляет его выглядеть как проблема с тем, что структура не является "прямым конструктивным" - я не знаю, что это значит, поэтому вы не уверены в том, почему объект должен быть "прямым конструктивным", который нужно отбросить назад.

#include <vector>
#include <memory>

struct MyStruct
{

    MyStruct(std::unique_ptr<int> p);
    MyStruct(MyStruct&& other);
    MyStruct&  operator=(MyStruct&& other);

    std::unique_ptr<int> mP;

private:
            // Non-copyable
    MyStruct(const MyStruct&);
    MyStruct& operator=(const MyStruct& other);
};

int main()
{

    MyStruct s(std::unique_ptr<int>(new int(5)));
    std::vector<MyStruct> v;

    auto other = std::move(s);       // Test it is moveable
    v.push_back(std::move(other));   // Fails to compile

    return 0;
}

Дает ошибки

/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/type_traits: In instantiation of ‘struct std::__is_direct_constructible_impl<MyStruct, const MyStruct&>’:
... snip ...
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/stl_vector.h:900:9:   required from ‘void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = MyStruct; _Alloc = std::allocator<MyStruct>; std::vector<_Tp, _Alloc>::value_type = MyStruct]’
main.cpp:27:33:   required from here
main.cpp:16:5: error: ‘MyStruct::MyStruct(const MyStruct&)’ is private

Простой обходной путь из разных ответов:

  • Используйте MyStruct(const MyStruct&) = delete; вместо private ctor hack
  • Inherit boost::noncopyable (или другой класс с закрытым ctor)

Ответ 1

Сбой связан с ограничением g++ 4.7, который не реализует DR 1170, который очень поздно был изменен в процессе стандартизации С++ 11, чтобы скажем, что проверка доступа должна выполняться как часть вывода аргумента шаблона.

Основная причина заключается в том, что libstdС++ vector будет перемещать элементы, если операция перемещения не будет выбрана (т.е. объявлена ​​noexcept или throw()), иначе, если тип скопируется, элементы будут скопированы, иначе если тип не может быть скопирован, но имеет операцию перемещения с возможностью броска, то он будет перемещен (и если исключение будет выбрано, результаты будут undefined неуказаны.) Это выполняется с проверками на is_nothrow_move_constructible и is_copy_constructible. В вашем случае тип не является конструктивным, но не проверяется, поэтому проверяется свойство is_copy_constructible. У вашего типа есть конструктор копирования, но он недоступен, поэтому признак is_copy_constructible создает ошибку компилятора с g++ 4.7, потому что проверка доступа не выполняется при выводе аргумента шаблона.

Если вы создадите свой конструктор перемещения и переместите оператор присваивания noexcept, тогда тип будет перемещен и не должен быть скопирован, поэтому признак is_copy_constructible, который терпит неудачу, не используется, и код компилируется ОК.

В качестве альтернативы (как указано в комментариях), если вы создадите конструктор копии, то свойство is_copy_constructible получит правильный результат.

Другой альтернативой является использование чего-то типа boost::noncopyable, который неявно делает конструктор копии удаленным, поэтому признак is_copy_constructible работает правильно (а также работает со старыми компиляторами, такими как MSVC, которые не поддерживают удаленные функции должным образом). Я не знаю, что вы хотите сказать о невозможности найти ошибку, не показывает ли MSVC полный контекст ошибки компилятора?

Заключение: используйте unique_ptr, если это необходимо, но не делайте классы явно перемещаемыми

Я не согласен с этим заключением, он слишком экстремален. Вместо этого сделайте свои занятия, если это возможно, передвижными. Кроме того, когда это возможно, используйте удаленные функции, чтобы сделать тип не скопированным вместо частных + нереализованных функций, возможно, используя макрос для переносимости для старых компиляторов, например.

#if __cplusplus >= 201103L
#define NONCOPYABLE(TYPE) \
  TYPE(const TYPE&) = delete; TYPE& operator=(const TYPE&) = delete
#else
// must be used in private access region
#define NONCOPYABLE(TYPE) \
  TYPE(const TYPE&); TYPE& operator=(const TYPE&)
#endif

struct MyStruct
{
...
private:
    NONCOPYABLE(MyStruct);
};