У меня есть не копируемый класс, упрощенный следующим образом:
struct NonCopyable
{
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator = (const NonCopyable&) = delete;
NonCopyable(NonCopyable&&) = default;
NonCopyable& operator = (NonCopyable&&) = default;
};
Я храню объекты этого класса в различных std :: list. В какой-то момент у меня есть пустой std :: vector этих списков, размер которого я хотел бы изменить, чтобы он содержал фиксированное количество списков пустой. Однако он жалуется на отсутствие конструктора копирования NonCopyable, хотя (насколько я понимаю) он не должен пытаться создать какие-либо NonCopyables!
std::vector<std::list<NonCopyable>> v; // OK
v.resize(4); // ERROR
v.emplace_back(); // ERROR
std::list<NonCopyable> l; // OK
v.push_back(l); // ERROR (unsurprisingly)
v.push_back(std::move(l)); // ERROR
Почему это? Есть ли способ сделать это, не делая класс копируемым?
Я использую VS2017 с / std: C++ 17 на случай, если это имеет значение.
Вот полный вывод ошибки при простой попытке изменить размер (первые две строки выше).
1>------ Build started: Project: testcore, Configuration: Debug x64 ------
1>testcard.cpp
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\xmemory0(881): error C2280: 'NonCopyable::NonCopyable(const NonCopyable &)': attempting to reference a deleted function
1>[...]\testcard.cpp(30): note: see declaration of 'NonCopyable::NonCopyable'
1>[...]\testcard.cpp(30): note: 'NonCopyable::NonCopyable(const NonCopyable &)': function was explicitly deleted
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(711): note: see reference to function template instantiation 'void std::_Default_allocator_traits<_Alloc>::construct<_Ty,const NonCopyable&>(_Alloc &,_Objty *const ,const NonCopyable &)' being compiled
1> with
1> [
1> _Alloc=std::allocator<std::_List_node<NonCopyable,std::_Default_allocator_traits<std::allocator<NonCopyable>>::void_pointer>>,
1> _Ty=NonCopyable,
1> _Objty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(716): note: see reference to function template instantiation 'void std::_Default_allocator_traits<_Alloc>::construct<_Ty,const NonCopyable&>(_Alloc &,_Objty *const ,const NonCopyable &)' being compiled
1> with
1> [
1> _Alloc=std::allocator<std::_List_node<NonCopyable,std::_Default_allocator_traits<std::allocator<NonCopyable>>::void_pointer>>,
1> _Ty=NonCopyable,
1> _Objty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(947): note: see reference to function template instantiation 'std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *std::_List_buy<_Ty,_Alloc>::_Buynode<const NonCopyable&>(std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *,std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *,const NonCopyable &)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Alloc=std::allocator<NonCopyable>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(950): note: see reference to function template instantiation 'std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *std::_List_buy<_Ty,_Alloc>::_Buynode<const NonCopyable&>(std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *,std::_List_node<_Ty,std::_Default_allocator_traits<_Alloc>::void_pointer> *,const NonCopyable &)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Alloc=std::allocator<NonCopyable>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(1308): note: see reference to function template instantiation 'void std::list<NonCopyable,std::allocator<_Ty>>::_Insert<const NonCopyable&>(std::_List_unchecked_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>,std::_Iterator_base0>,const NonCopyable &)' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(1308): note: see reference to function template instantiation 'void std::list<NonCopyable,std::allocator<_Ty>>::_Insert<const NonCopyable&>(std::_List_unchecked_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>,std::_Iterator_base0>,const NonCopyable &)' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(1265): note: see reference to function template instantiation 'void std::list<NonCopyable,std::allocator<_Ty>>::_Insert_range<_Iter>(std::_List_unchecked_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>,std::_Iterator_base0>,_Iter,_Iter,std::forward_iterator_tag)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Iter=std::_List_const_iterator<std::_List_val<std::_List_simple_types<NonCopyable>>>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(1264): note: see reference to function template instantiation 'void std::list<NonCopyable,std::allocator<_Ty>>::_Insert_range<_Iter>(std::_List_unchecked_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>,std::_Iterator_base0>,_Iter,_Iter,std::forward_iterator_tag)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Iter=std::_List_const_iterator<std::_List_val<std::_List_simple_types<NonCopyable>>>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(800): note: see reference to function template instantiation 'std::_List_iterator<std::_List_val<std::_List_simple_types<_Ty>>> std::list<_Ty,std::allocator<_Ty>>::insert<std::_List_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>>,void>(std::_List_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>>,_Iter,_Iter)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Iter=std::_List_const_iterator<std::_List_val<std::_List_simple_types<NonCopyable>>>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(800): note: see reference to function template instantiation 'std::_List_iterator<std::_List_val<std::_List_simple_types<_Ty>>> std::list<_Ty,std::allocator<_Ty>>::insert<std::_List_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>>,void>(std::_List_const_iterator<std::_List_val<std::_List_simple_types<_Ty>>>,_Iter,_Iter)' being compiled
1> with
1> [
1> _Ty=NonCopyable,
1> _Iter=std::_List_const_iterator<std::_List_val<std::_List_simple_types<NonCopyable>>>
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\list(796): note: while compiling class template member function 'std::list<NonCopyable,std::allocator<_Ty>>::list(const std::list<_Ty,std::allocator<_Ty>> &)'
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\xmemory0(881): note: see reference to function template instantiation 'std::list<NonCopyable,std::allocator<_Ty>>::list(const std::list<_Ty,std::allocator<_Ty>> &)' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\vector(1812): note: see reference to class template instantiation 'std::list<NonCopyable,std::allocator<_Ty>>' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\vector(1811): note: while compiling class template member function 'std::list<NonCopyable,std::allocator<_Ty>> *std::vector<std::list<_Ty,std::allocator<_Ty>>,std::allocator<std::list<_Ty,std::allocator<_Ty>>>>::_Udefault(std::list<_Ty,std::allocator<_Ty>> *,const unsigned __int64)'
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\vector(1479): note: see reference to function template instantiation 'std::list<NonCopyable,std::allocator<_Ty>> *std::vector<std::list<_Ty,std::allocator<_Ty>>,std::allocator<std::list<_Ty,std::allocator<_Ty>>>>::_Udefault(std::list<_Ty,std::allocator<_Ty>> *,const unsigned __int64)' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>[...]\testcard.cpp(37): note: see reference to class template instantiation 'std::vector<std::list<NonCopyable,std::allocator<_Ty>>,std::allocator<std::list<_Ty,std::allocator<_Ty>>>>' being compiled
1> with
1> [
1> _Ty=NonCopyable
1> ]
1>Done building project "testcore.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 2 up-to-date, 0 skipped ==========





Как отметил @ j6t, кажется, что это невозможно. Вектор с std::list с некопируемыми элементами не может быть расширен по стандарту (...). Если бы это был мой частный проект, я бы на самом деле рассмотрел что-то вроде этого:
#include <iostream>
#include <vector>
#include <list>
namespace xxx {
// <flame_bait>
template<class T, class Enable = void>
struct list : std::list<T> {
list() : std::list<T>() {}
list(list&&) = default;
list& operator=(list&&) = default;
list(const list&) = delete;
list& operator=(const list&) = delete;
};
template<class T>
struct list<T, typename std::enable_if<std::is_copy_constructible<T>::value>::type> : std::list<T> {};
// </flame_bait>
}
struct NonCopyable
{
int m_id;
NonCopyable(int id = 0) : m_id(id) {}
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator = (const NonCopyable&) = delete;
NonCopyable(NonCopyable&&) noexcept = default;
NonCopyable& operator = (NonCopyable&&) noexcept = default;
~NonCopyable() = default;
friend std::ostream& operator<<(std::ostream&, const NonCopyable&);
};
std::ostream& operator<<(std::ostream& os, const NonCopyable& nc) {
os << "I am not a free man, I am number " << nc.m_id;
return os;
}
int main() {
std::vector<xxx::list<NonCopyable>> v;
v.resize(4); // now working
int id = 0;
for (auto& l : v) {
l.emplace_back(++id); // emplace_back one NonCopyable per list
}
xxx::list<NonCopyable> li; // Create a separate list
li.emplace_back(++id); // create one...
v.emplace_back(std::move(li)); // and move list to vector is now working
for (auto& l : v) {
for (auto& nc : l) {
std::cout << nc << "\n";
}
}
}
Ожидаемый результат:
I am not a free man, I am number 1
I am not a free man, I am number 2
I am not a free man, I am number 3
I am not a free man, I am number 4
I am not a free man, I am number 5
error C2280: 'NonCopyable::NonCopyable(const NonCopyable &)': attempting to reference a deleted function. Я добавил в сообщение полный вывод об ошибке.
Здорово! Как ни странно ... :-) Я попробую в VS, когда вернусь домой.
В довершение всего: v.reserve(4); выдает ту же ошибку.
Я добавил xxx::list, заменяющий std::list, который, надеюсь, должен работать как контейнер как для копируемых, так и для некопируемых объектов. Я плохо разбираюсь в шаблонах, так что имейте в виду ...
std::list - это не noexcept-подвижный, но его можно копировать. Следовательно, std::vector в любом случае предпочитает конструктор копирования list своему конструктору перемещения.
Чтобы обойти эту проблему, вы должны поместить свой std::list в класс, который нельзя копировать.
Спасибо. Что, если класс NonCopyable также не был перемещаем? (Я считаю, что перемещение объектов между списками все еще возможно, поскольку это не делает ссылки недействительными)
@UriZarfaty Можно было бы перемещать узлы списка с помощью splice, но std::vector не знает об этом.
Так что это про QoI. Неудивительно, что Clang и GCC его принимают. github.com/llvm-mirror/libcxx/blob/master/include/list#L1315
@StoryTeller Это не совсем QoI, это вопрос буквального следования Стандарту. Я не вижу спецификации noexcept для конструктора перемещения в синопсисе списка. Тем не менее, я согласен с тем, что поведение Clang и GCC предпочтительнее.
@ j6t - Стандарт тоже имеет спорное значение QoI. Что ж, точнее было бы качество спецификации, но суть все равно остается в силе.
list от MSVC использует динамически выделяемый контрольный узел, поэтому конструктор перемещения не может быть noexcept. (Компромисс в том, что такие вещи, как swap, не делают недействительными конечные итераторы.)
Действительно ли std::list<T> должен быть копируемым, если T не является Cpp17CopyInsertable? Я раньше не читал стандарты и у меня небольшие проблемы с пониманием container.requirements.
@TedLyngmo Невозможно, чтобы list<T> был копируемым, если T не копируемый. Дело в том, что vector<T> очень параноик и не использует конструкцию перемещения T, если такая конструкция перемещения может вызывать исключения.
Хорошо, но если std::list<T> действительно нельзя копировать, а T - нет, я действительно не понимаю, как мой хак (xxx::list ниже) может заставить его работать? Единственное, что я сделал, это скопировал delete, если T не копируется, а затем vector использовал конструкцию перемещения.
@TedLyngmo Причина, по которой он компилируется, заключается в том, что, когда T не копируется, но все еще может быть сконструирован для перемещения (например, ваш xxx::list), тогда vector<T> должен использовать конструктор перемещения T, даже с риском того, что он вызовет исключение, потому что нет более безопасного выбора .
@ j6t да, но почему std::is_copy_constructible<std::list<NonCopyable>>::value верен (даже в gcc / clang)?
@TedLyngmo Поскольку нигде в спецификации std::list<T> не сказано, что «копирующий конструктор исключается из набора перегрузки, если T не может быть скопирован». Следовательно, конструктор копирования std::list всегда доступен, и это все, на что обращает внимание std::is_copy_constructible.
Хорошо, вот где у меня проблемы с пониманием того, как читать стандартные шоу. X(a) и X u(a); оба Requires: T is Cpp17CopyInsertable into X. Я интерпретировал это как то, что они не должны быть доступны в этом случае.
Эти предложения Requires являются директивами для программиста, а не для компилятора. Если условия нарушаются, вы получаете по заслугам; в лучшем случае он не компилируется, в худшем - это неопределенное поведение.
Кажется, я пропустил ту часть, где
v.resize(4);не работает. Что за сообщение об ошибке в этой строке?