以下代码可以使用Visual Studio 2015成功编译,但使用Visual Studio 2017失败。VisualStudio 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;
}
看起来Visual Studio 2017显式需要移动构造函数声明。是什么原因?
答案 0 :(得分:9)
最小示例:
#include <memory>
#include <unordered_map>
#include <vector>
int main() {
std::vector<std::unordered_map<int, std::unique_ptr<int>>> vec;
vec.reserve(1);
}
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
更新
我认为编译错误是合法的。 Vector的重新分配功能可以使用std::move_if_noexcept
来传送元素的(内容),因此更喜欢复制构造函数而不是抛出move构造函数。
在libstdc ++(GCC)/ libc ++(clang)中,std::unordered_map
的move构造函数似乎是noexcept
。因此,Node
的move构造函数也为noexcept
,并且其拷贝构造函数完全没有涉及。
另一方面,MSVC 2017的实现似乎未将std::unordered_map
的move构造函数指定为noexcept
。因此,Node
的move构造函数也不是noexcept
,向量通过std::move_if_noexcept
的重新分配函数会尝试调用Node
的副本构造函数。
隐式定义了Node
的副本构造函数,从而调用了std::unordered_map
的副本构造函数。但是,由于映射的值类型(在这种情况下为std::pair<const int, std::unique_ptr<int>>
是不可复制的,因此此处可能不会调用后者。
最后,如果用户定义Node
的move构造函数,则其隐式声明的copy构造函数定义为delete。 而且,IIRC删除的隐式声明的副本构造函数不参与重载解析。。但是,std::move_if_noexcept
不考虑删除的副本构造函数,因此它将使用Node.
答案 1 :(得分:6)
当声明移动构造函数时,隐式声明的副本构造函数被定义为已删除。另一方面,当您不声明移动构造函数时,编译器会在需要时隐式定义副本构造函数。而且这个隐式定义格式不正确。
final AlertDialog alertDialog= new
SpotsDialog.Builder().setContext(POSTING.this).build();
alertDialog.setTitle("Uploading Post");
alertDialog.setMessage("Please wait.....");
alertDialog.show();
不是使用标准分配器的容器中的unique_ptr
,因为它不可复制构造,因此CopyInsertable
的复制构造函数格式错误(可能已声明为已删除,但这不是标准要求的)。
正如您的示例代码向我们展示的那样,在较新版本的MSVC中,此示例代码生成了格式错误的定义。我认为标准中没有禁止它的东西(即使这确实令人惊讶)。
因此,您确实应该确保将Node的副本构造函数声明为或隐式定义为Deleted。
答案 2 :(得分:5)
让我们看一下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>>>>{});
}
如果Node
是无掷动作可构造的或不可复制构造的,则调用_Uninitialized_move
,否则,{{1 }}被调用。
问题在于,如果您未显式声明move构造函数,则_Uninitialized_copy
的类型特征std::is_copy_constructible_v
为true
。该声明使复制构造函数被删除。
libstdc ++以类似的方式实现Node
,但是std::vector
是std::is_nothrow_move_constructible_v<Node>
,而MSVC是true
。因此,使用了移动语义,并且编译器不会尝试生成复制构造函数。
但是如果我们强迫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);
}
答案 3 :(得分:0)
Visual Studio 2017:
正如@Evg所示,Visual Studio 2017的矢量源代码最终会调用_Uninitialized_copy,因为隐式声明的Node的move构造函数被视为非空(is_nothrow_move_constructible<Node>
为假)并且is_copy_constructible<Node>
为true在Visual Studio 2017中。
1)关于is_nothrow_move_constructible<Node>
:
https://en.cppreference.com/w/cpp/language/move_constructor说:
隐式声明(或在其第一个声明中默认)移动构造函数具有一个异常规范,如dynamic exception specification(直到C ++ 17)exception specification(自C ++ 17起)中所述>
将is_nothrow_move_constructible<Node>
视为错误是合理的,因为Node
的数据成员std::unordered_map
的move构造函数未标记为noexcept。
2)关于is_copy_constructible<Node>
:
正如@Oliv所说,将is_copy_constructible<Node>
计算为true似乎不合逻辑,特别是考虑到Visual Studio 2017编译器已检测到Node
不是copy_constructible的事实并将其报告为编译错误。 Node
不可复制,因为std::unique_ptr
不可复制。
Visual Studio 2015:
Visual Studio 2015的向量具有不同的实现。 vec.push_back
-> _Reserve
-> _Reallocate
-> _Umove
-> _Uninitialized_move_al_unchecked
-> _Uninitialized_move_al_unchecked1
-> std::move(node)
。不涉及is_nothrow_move_constructible<Node>
和is_copy_constructible<Node>
。它仅调用std::move(node)
而不是复制构造函数。因此,可以使用Visual Studio 2015成功编译示例代码。