c++ - 在 map 中使用 unique_ptr 时删除 std::pair 中的函数

标签 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编译器有bug。如果第一个代码是错误的,为什么第二个是正确的?还是第二个也错了?

最佳答案

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

Remarks: If an exception is thrown other than by the move constructor of a non-CopyInsertable T there are no effects.

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

复制元素是否会以任何方式改变原始存储?否1移动元素会改变原始存储吗?是的。哪种操作效率更高?移动。 vector 总是喜欢移动而不是复制吗?不总是。

如果移动构造函数可以抛出异常,则不可能恢复旧存储的原始内容,因为尝试将已经移动的元素移回旧 block 可能再次失败。在这种情况下,如果移动构造函数保证它不会抛出异常(或者移动构造函数是复制构造函数不可用时的唯一选项)。一个函数如何保证它不会抛出异常?一个将被注释为 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 不是 nothrow-MoveConstructible

标准要求是noexcept吗? [map.overview] :

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

std::mapMove-CopyConstructible。两者都不需要不抛出异常。

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

map(map&&) = default;

隐式生成的移动构造函数必须是 noexcept[except.spec] :

An inheriting constructor ([class.inhctor]) and an implicitly declared special member function (Clause [special]) have an exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

至此,icc移动构造函数隐式生成的是否应该是noexcept就不好说了。或不。无论哪种方式,std::map本身不需要是 nothrow-MoveConstructible,所以它更像是一个实现质量问题(库的实现或构造函数的隐式生成的实现)并且 icc 摆脱它而不管这是一个实际的错误与否。

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

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

unique_ptr(unique_ptr&& u) noexcept;

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


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

map(map&& __x)
  noexcept(is_nothrow_copy_constructible<_Compare>::value)
  : _M_t(std::move(__x._M_t)) { }

这明确地使 noexcept仅取决于复制比较器是否抛出。


1 从技术上讲,接受非 const 左值引用的复制构造函数可以更改原始对象,例如 std::auto_ptr,但MoveInsertable要求 vector 元素可以从右值构造,不能绑定(bind)到非常量左值引用。

关于c++ - 在 map 中使用 unique_ptr 时删除 std::pair 中的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53527682/

相关文章:

c++ - 在 map 中使用 unique_ptr 时 std::pair 中的已删除函数

c++ - std::vector<int> 到 std::vector<enum>

compiler-errors - 编译错误: “_for_stop_core” not found

c - 内联汇编中的 Jknzd,编译错误

c++ - 命名空间中的两个对象由不同的函数默认初始化并由命名空间中的类使用

python - Cython 中的这个声明是什么? cdef PyObject ** worker 。它是指向指针的指针吗?

c++ - 用于 xml 标签内容的单一匹配的正则表达式

c++ - 在 mac os x (10.8) 中链接

c++ - 函数的引用限定符有什么实际用例吗?

c - 使用带有千位分组格式(撇号)的 ICC 和 printf 的警告