在地图中使用unique_ptr时删除std :: pair中的函数

H. *_*ich 10 c++ icc c++14

我有一段C++代码,我不确定它是否正确.请考虑以下代码.

#include <memory>
#include <vector>
#include <map>

using namespace std;

int main(int argc, char* argv[])
{
    vector<map<int, unique_ptr<int>>> v;
    v.resize(5);

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

GCC编译这段代码没有问题.但是,英特尔编译器(版本19)因错误而停止:

/usr/local/ [...] /include/c++/7.3.0/ext/new_allocator.h(136): error: function "std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2> &) [with _T1=const int, _T2=std::unique_ptr<int, std::default_delete<int>>]" (declared at line 292 of "/usr/local/ [...] /include/c++/7.3.0/bits/stl_pair.h") cannot be referenced -- it is a deleted function
    { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
                            ^
      detected during:

[...]

instantiation of "void std::vector<_Tp, _Alloc>::resize(std::vector<_Tp, _Alloc>::size_type={std::size_t={unsigned long}}) [with _Tp=std::map<int, std::unique_ptr<int, std::default_delete<int>>, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, std::default_delete<int>>>>>, _Alloc=std::allocator<std::map<int, std::unique_ptr<int, std::default_delete<int>>, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, std::default_delete<int>>>>>>]"
                  at line 10 of "program.cpp"
Run Code Online (Sandbox Code Playgroud)

两个编译器都编译以下代码没有问题.

#include <memory>
#include <vector>
#include <map>

using namespace std;

int main(int argc, char* argv[])
{
    vector<unique_ptr<int>> v;
    v.resize(5);

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

第一个代码因英特尔编译器而失败,因为它试图创建unique_ptr的副本,该副本仅定义移动构造函数.但是,我不确定第一个程序是否是合法的C++程序.

我想知道第一个代码是错误的还是英特尔编译器中是否存在错误.如果第一个代码是错误的,为什么第二个代码是正确的?或者第二个也错了?

Pio*_*cki 9

问题从以下后条件起源std::vector<T>::resize,[vector.capacity] :

备注:如果除非由非CopyInsertable 的移动构造函数抛出异常T,则不会产生任何影响.

也就是说,如果重定位失败,向量必须保持不变.重定位可能失败的原因之一是由于异常,特别是当用于将元素从旧存储转移到新存储的复制或移动构造函数抛出异常时.

复制元素是否以任何方式更改原始存储?没有1.移动元素是否会更改原始存储?是.哪种操作更有效?移动.矢量总是更喜欢复制吗?不总是.

如果移动构造函数可以抛出异常,则不可能恢复旧存储的原始内容,因为尝试将已经移位的元素移回旧块可能会再次失败.在这种情况下,只有当移动构造函数保证它不会抛出异常时,向量将使用移动构造函数将其元素从旧存储重定位到新存储(或者当复制构造函数是复制构造函数时,移动构造函数是唯一的选项.无法使用).一个函数如何保证它不会抛出异常?一个将使用说明noexcept符进行注释并使用noexcept运算符进行测试.

使用icc测试以下代码:

std::map<int, std::unique_ptr<int>> m;
static_assert(noexcept(std::map<int, std::unique_ptr<int>>(std::move(m))), "!");
Run Code Online (Sandbox Code Playgroud)

断言失败了.这意味着,m nothrow- MoveConstructible.

标准是否要求它noexcept[map.overview]:

// [map.cons], construct/copy/destroy:
map(const map& x);
map(map&& x);
Run Code Online (Sandbox Code Playgroud)

std::mapMoveCopyConstructible.也不要求不抛出异常.

但是,允许实施提供此保证{{citation needed}}.您的代码使用以下定义:

map(map&&) = default;
Run Code Online (Sandbox Code Playgroud)

是否需要隐式生成的移动构造函数noexcept[except.spec]:

继承构造函数([class.inhctor])和隐式声明的特殊成员函数(Clause [special])具有异常规范.如果f是继承构造函数或隐式声明的默认构造函数,复制构造函数,移动构造函数,析构函数,复制赋值运算符或移动赋值运算符,则其隐式异常规范指定type-id T if且仅当if 例外T允许时由隐式定义直接调用的函数; 允许所有异常,如果它直接调用的任何函数允许所有异常,并且如果它直接调用的每个函数都不允许异常,则具有异常规范.fff noexcept(true)

此时,很难说是否应该由icc move构造函数隐式生成noexcept.无论哪种方式,std::map本身并不需要不是 - MoveConstructible,因此它更多的是实现问题的质量(库的实现或构造函数的隐式生成的实现),并且icc 随之而来,无论这是否是实际的错误.

最终,std::vector将回退到使用更安全的选项,这是一个复制构造函数来重新定位其元素(唯一指针的映射),但由于std::unique_ptr不是CopyConstructible,因此报告错误.

在另一方面,std::unique_ptr此举构造要求为noexcept,[unique.ptr.single.ctor] :

unique_ptr(unique_ptr&& u) noexcept;
Run Code Online (Sandbox Code Playgroud)

当需要重定位时,唯一指针的向量可以安全地移动其元素.


在较新版本中,stl_map.h有以下用户提供的map的移动构造函数定义:

map(map&& __x)
  noexcept(is_nothrow_copy_constructible<_Compare>::value)
  : _M_t(std::move(__x._M_t)) { }
Run Code Online (Sandbox Code Playgroud)

它明确地noexcept依赖于复制比较器是否抛出.


1从技术上讲,接受非const l值引用的复制构造函数可以更改原始对象,例如std :: auto_ptr,但MoveInsertable要求向量元素可以从r值构造,不能绑定到非const l-价值参考.