在地图内使用unique_ptr时,在std :: pair中删除了函数

时间:2018-11-28 20:36:03

标签: c++ c++14 icc

我有一段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;
}

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"

两个编译器都可以毫无问题地编译以下代码。

#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;
}

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

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

1 个答案:

答案 0 :(得分:8)

问题源于std::vector<T>::resize[vector.capacity]的以下后置条件:

  

备注:如果不是由非 CopyInsertable T的move构造函数引发的异常,则没有任何效果。

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

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

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

使用icc测试以下代码:

std::map<int, std::unique_ptr<int>> m;
static_assert(noexcept(std::map<int, std::unique_ptr<int>>(std::move(m))), "!");

对断言失败。这意味着m not nothrow- MoveConstructible

标准是否要求其为noexcept[map.overview]

// [map.cons], construct/copy/destroy:
map(const map& x);
map(map&& x);

std::map都是 Move- CopyConstructible 。都不要求不引发异常。

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

map(map&&) = default;

是否需要隐式生成的move构造函数为noexcept[except.spec]

  

继承构造函数([class.inhctor])和隐式声明的特殊成员函数(子句[special])具有 exception-specification 。如果f是继承的构造函数或隐式声明的默认构造函数,复制构造函数,移动构造子,析构函数,复制赋值运算符或移动赋值运算符,则其隐式 exception-specification < / em>仅在由{直接调用的函数的 exception-specification 允许T允许的情况下,指定 type-id T {1}}的隐式定义;如果f直接调用的任何函数都允许所有异常,则允许所有异常;如果f直接调用的每个函数都具有 exception-specification f 不允许例外。

在这一点上,很难说icc move构造函数隐式生成的是否应该为noexcept(true)。无论哪种方式,都不需要noexcept本身不是nothrow- MoveConstructible ,因此,这更多是实现质量问题(库的实现或构造函数的隐式生成的实现),并且icc摆脱了不管它是不是实际的错误。

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

另一方面,std::unique_ptr的移动构造函数 必须为std::unique_ptr[unique.ptr.single.ctor]

noexcept

唯一指针向量可以在需要重定位时安全地移动其元素。


unique_ptr(unique_ptr&& u) noexcept; 的较新版本中,有以下用户提供的map的移动构造函数定义:

stl_map.h

这明确使map(map&& __x) noexcept(is_nothrow_copy_constructible<_Compare>::value) : _M_t(std::move(__x._M_t)) { } 仅取决于复制比较器是否引发。


1 从技术上讲,接受非常量l值引用的副本构造函数可以更改原始对象,例如std :: auto_ptr,但是 MoveInsertable 要求向量元素必须能够根据r值构造,并且不能绑定到非常量l值引用。