fin*_*inn 14 c++ move-constructor visual-studio-2017
下面的代码可以使用Visual Studio 2015成功编译,但使用Visual Studio 2017失败.Visual Studio 2017报告:
错误C2280:"std :: pair :: pair(const std :: pair&)":尝试引用已删除的函数
#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;
}
Run Code Online (Sandbox Code Playgroud)
看起来Visual Studio 2017显式需要移动构造函数声明.是什么原因?
最小的例子:
#include <memory>
#include <unordered_map>
#include <vector>
int main() {
std::vector<std::unordered_map<int, std::unique_ptr<int>>> vec;
vec.reserve(1);
}
Run Code Online (Sandbox Code Playgroud)
关于GodBolt的现场演示:https://godbolt.org/z/VApPkH .
另一个例子:
std::unordered_map<int, std::unique_ptr<int>> m;
auto m2 = std::move(m); // ok
auto m3 = std::move_if_noexcept(m); // error C2280
Run Code Online (Sandbox Code Playgroud)
UPDATE
我认为编译错误是合法的.Vector的重新分配函数可以通过使用传递元素的内容std::move_if_noexcept,因此更喜欢复制构造函数来抛出移动构造函数.
在libstdc ++(GCC)/ libc ++(clang)中,移动构造函数std::unordered_map(貌似)noexcept.因此,移动构造函数Node也是noexcept如此,并且它的复制构造函数根本不涉及.
另一方面,MSVC 2017的实现似乎没有指定std::unordered_mapas的移动构造函数noexcept.因此,移动构造函数Node不太noexcept好,而矢量的重新分配函数则std::move_if_noexcept试图调用复制构造函数Node.
复制构造函数Node是隐式定义的,这样就可以调用复制构造函数std::unordered_map.但是,后者可能不会在此处调用,因为map的值类型(std::pair<const int, std::unique_ptr<int>>在这种情况下)是不可复制的.
最后,如果用户定义移动构造函数,则将Node其隐式声明的复制构造函数定义为已删除.并且,IIRC,删除隐式声明的复制构造函数不参与重载解析.但是,删除的拷贝构造函数不被考虑std::move_if_noexcept,因此它将使用throw move构造函数Node.
声明移动构造函数时,隐式声明的复制构造函数被定义为已删除.另一方面,当您不声明移动构造函数时,编译器会在需要时隐式定义复制构造函数.而这个隐含的定义是不正确的.
unique_ptr不在CopyInsertable使用标准分配器的容器中,因为它不是可复制构造的,因此复制构造函数map_是错误的(它可能已被声明为已删除,但标准不要求这样做).
正如您的示例代码向我们展示的那样,对于较新版本的MSVC,使用此示例代码生成了这种格式错误的定义.我认为标准中没有禁止它的东西(即使这真的令人惊讶).
因此,您确实应该确保将Node的复制构造函数声明或隐式定义为已删除.
让我们来看看std::vector源代码(我取代pointer和_Ty实际类型):
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>>>>{});
}
Run Code Online (Sandbox Code Playgroud)
如果Node是no-throw move-constructible或者不是copy-constructible,_Uninitialized_move则调用,否则_Uninitialized_copy调用.
问题是如果你没有明确声明一个移动构造函数,那么类型特征std::is_copy_constructible_v是true适用的Node.此声明使复制构造函数被删除.
的libstdc ++实现std::vector以类似的方式,但也有std::is_nothrow_move_constructible_v<Node>是true在对比MSVC,它在哪里false.因此,使用移动语义,编译器不会尝试生成复制构造函数.
但如果我们强迫is_nothrow_move_constructible_v成为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);
}
Run Code Online (Sandbox Code Playgroud)
发生同样的错误:
/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)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1163 次 |
| 最近记录: |