Visual Studio 2017是否需要显式的move构造函数声明?

时间:2018-11-06 09:14:53

标签: c++ visual-studio-2017 move-constructor

以下代码可以使用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显式需要移动构造函数声明。是什么原因?

4 个答案:

答案 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.

的throw move构造函数。

答案 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_vtrue。该声明使复制构造函数被删除。

libstdc ++以类似的方式实现Node,但是std::vectorstd::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成功编译示例代码。