我有一段 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::map
是Move- 和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-idT
if and only ifT
is allowed by the exception-specification of a function directly invoked byf
's implicit definition;f
allows all exceptions if any function it directly invokes allows all exceptions, andf
has the exception-specificationnoexcept(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/