Le code ci-dessous peut être compilé avec succès à l'aide de Visual Studio 2015, mais il a échoué avec Visual Studio 2017. Visual Studio 2017 indique:
erreur C2280: “std :: pair :: pair (const std :: pair &)”: tentative de référencer une fonction supprimée
#include <unordered_map>
#include <memory>
struct Node
{
std::unordered_map<int, std::unique_ptr<int>> map_;
// Uncommenting the following two lines will pass Visual Studio 2017 compilation
//Node(Node&& o) = default;
//Node() = default;
};
int main()
{
std::vector<Node> vec;
Node node;
vec.Push_back(std::move(node));
return 0;
}
Il semble que Visual Studio 2017 explicit nécessite une déclaration du constructeur du déplacement. Quelle est la raison?
Regardons le code source std::vector
(j'ai remplacé pointer
et _Ty
par des types réels):
void _Umove_if_noexcept1(Node* First, Node* Last, Node* Dest, true_type)
{ // move [First, Last) to raw Dest, using allocator
_Uninitialized_move(First, Last, Dest, this->_Getal());
}
void _Umove_if_noexcept1(Node* First, Node* Last, Node* Dest, false_type)
{ // copy [First, Last) to raw Dest, using allocator
_Uninitialized_copy(First, Last, Dest, this->_Getal());
}
void _Umove_if_noexcept(Node* First, Node* Last, Node* Dest)
{ // move_if_noexcept [First, Last) to raw Dest, using allocator
_Umove_if_noexcept1(First, Last, Dest,
bool_constant<disjunction_v<is_nothrow_move_constructible<Node>, negation<is_copy_constructible<Node>>>>{});
}
Si Node
est no-throw move-constructible ou not-copy-constructible , _Uninitialized_move
est appelé, sinon, _Uninitialized_copy
est appelé.
Le problème est que le trait trait std::is_copy_constructible_v
est true
pour Node
si vous ne déclarez pas explicitement un constructeur de déplacement. Cette déclaration rend le constructeur de copie supprimé.
libstdc ++ implémente std::vector
de la même manière, mais std::is_nothrow_move_constructible_v<Node>
est true
contrairement à MSVC, où il s'agit de false
. Donc, la sémantique de déplacement est utilisée et le compilateur n'essaie pas de générer le constructeur de copie.
Mais si on force is_nothrow_move_constructible_v
à devenir false
struct Base {
Base() = default;
Base(const Base&) = default;
Base(Base&&) noexcept(false) { }
};
struct Node : Base {
std::unordered_map<int, std::unique_ptr<int>> map;
};
int main() {
std::vector<Node> vec;
vec.reserve(1);
}
la même erreur se produit:
/usr/include/c++/7/ext/new_allocator.h:136:4: error: use of deleted function ‘std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const int; _T2 = std::unique_ptr<int>]’
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Exemple minimal:
#include <memory>
#include <unordered_map>
#include <vector>
int main() {
std::vector<std::unordered_map<int, std::unique_ptr<int>>> vec;
vec.reserve(1);
}
Démo en direct sur GodBolt: https://godbolt.org/z/VApPkH .
Un autre exemple:
std::unordered_map<int, std::unique_ptr<int>> m;
auto m2 = std::move(m); // ok
auto m3 = std::move_if_noexcept(m); // error C2280
METTRE &AGRAVE; JOUR
Je crois que l'erreur de compilation est légale. La fonction de réallocation de Vector peut transférer (le contenu de) des éléments à l'aide de std::move_if_noexcept
, préférant par conséquent les constructeurs de copie à ceux de déplacement.
Dans libstdc ++ (GCC)/libc ++ (clang), le constructeur de déplacement de std::unordered_map
est (apparemment) noexcept
. Par conséquent, le constructeur de mouvement de Node
est également noexcept
et son constructeur de copie n'est pas du tout impliqué.
D'autre part, la mise en œuvre à partir de MSVC 2017 ne spécifie apparemment pas que le constructeur de mouvement de std::unordered_map
est noexcept
. Par conséquent, le constructeur de déplacement de Node
n'est pas non plus noexcept
, et la fonction de réallocation de vector via std::move_if_noexcept
tente d'appeler le constructeur de copie de Node
.
Le constructeur de copie de Node
est défini implicitement de manière à invoquer le constructeur de copie de std::unordered_map
. Toutefois, cette dernière ne peut pas être invoquée ici, car le type de valeur de la carte (std::pair<const int, std::unique_ptr<int>>
dans ce cas) n'est pas copiable.
Enfin, si vous définissez le constructeur de déplacement de Node
, son constructeur de copie implicitement déclaré est défini comme étant supprimé. Et, IIRC, le constructeur de copie supposé implicitement supprimé ne participe pas à la résolution de surcharge. Cependant, le constructeur de copie supprimé n'est pas considéré par std::move_if_noexcept
, il utilisera donc le constructeur de mouvements de projection de Node.
Lorsque vous déclarez un constructeur de déplacement, le constructeur de copie déclaré implicitement est défini comme étant supprimé. D'autre part, lorsque vous ne déclarez pas de constructeur de déplacement, le compilateur définit implicitement le constructeur de copie lorsqu'il en a besoin. Et cette définition implicite est mal formée.
unique_ptr
n'est pas CopyInsertable
dans un conteneur qui utilise un allocateur standard car il n'est pas constructible en copie, le constructeur de copie de map_
est donc mal formé (il aurait pu être déclaré comme supprimé, mais le standard ne l'exige pas.
Comme votre exemple de code nous le montre, dans les versions plus récentes de MSVC, cette définition mal formée est générée avec cet exemple de code. Je ne pense pas qu'il y ait quelque chose dans la norme qui l'interdit (même si c'est vraiment surprenant).
Vous devez donc vous assurer que le constructeur de la copie de Node est déclaré ou implicitement défini comme étant supprimé.