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

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显式需要移动构造函数声明.是什么原因?

Dan*_*ica 9

最小的例子:

#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.


Oli*_*liv 6

声明移动构造函数时,隐式声明的复制构造函数被定义为已删除.另一方面,当您不声明移动构造函数时,编译器会在需要时隐式定义复制构造函数.而这个隐含的定义是不正确的.

unique_ptr不在CopyInsertable使用标准分配器的容器中,因为它不是可复制构造的,因此复制构造函数map_是错误的(它可能已被声明为已删除,但标准不要求这样做).

正如您的示例代码向我们展示的那样,对于较新版本的MSVC,使用此示例代码生成了这种格式错误的定义.我认为标准中没有禁止它的东西(即使这真的令人惊讶).

因此,您确实应该确保将Node的复制构造函数声明或隐式定义为已删除.

  • "正确"执行此操作的类型特质魔术令人惊讶地复杂.我实现了一个开放式寻址哈希映射(和集合),它主要与`unordered_map`兼容,最终不得不使用来自`std :: conditional <std :: is_copy_constructible <...> :: value &&的私有继承. ...,AllowCopy,DisallowCopy> :: type`其中`AllowCopy`为空,"DisallowCopy"删除了复制操作(以及其他内容).我还检查了默认的c'tible allocator,并且所有这些仍然不太正确,例如我既不支持复制上的分配器传播也不支持在分配器中复制元素. (3认同)
  • `is_nothrow_move_constructible_v <Node>`在MSVC中是'false`,所以它试图复制并失败; 它在gcc中是'true`,所以它会移动.如果`is_nothrow_move_constructible_v <Node>`在gcc中被迫变为`false`,它也会尝试复制并失败.为什么MSVC和gcc在`is_nothrow_move_constructible_v <Node>`值上不一致? (2认同)
  • @Evg和`is_copy_constructible <Node>`即使无法复制它也是如此!这是正确的,因为`is_copy_constructible_v <unordered_map <int,std :: unique_ptr ... >>`是真的,而标准应该要求它是假的.那么如果你检查is_nothrow_copy_constructible会发生什么只是混乱. (2认同)

Evg*_*Evg 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>>>>{});
}
Run Code Online (Sandbox Code Playgroud)

如果Nodeno-throw move-constructible或者不是copy-constructible,_Uninitialized_move则调用,否则_Uninitialized_copy调用.

问题是如果你没有明确声明一个移动构造函数,那么类型特征std::is_copy_constructible_vtrue适用的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)