Rus*_*lan 5 c++ qstring qt vector unique-ptr
我正在尝试编译一些代码,这减少到这个:
#include <memory>
#include <vector>
#include <QString>
class Category
{
std::vector<std::unique_ptr<int>> data;
QString name;
};
int main()
{
std::vector<Category> categories;
categories.emplace_back();
};
Run Code Online (Sandbox Code Playgroud)
按原样编译,它会导致g ++和clang ++类似的以下错误:
In file included from /opt/gcc-4.8/include/c++/4.8.2/memory:64:0,
from test.cpp:1:
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = std::unique_ptr<int>; _Args = {const std::unique_ptr<int, std::default_delete<int> >&}]’:
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:75:53: required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*; bool _TrivialValueTypes = false]’
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:117:41: required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*]’
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:258:63: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<int>*, std::vector<std::unique_ptr<int> > >; _ForwardIterator = std::unique_ptr<int>*; _Tp = std::unique_ptr<int>]’
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_vector.h:316:32: required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = std::unique_ptr<int>; _Alloc = std::allocator<std::unique_ptr<int> >]’
test.cpp:5:7: [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:117:41: required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = Category*; _ForwardIterator = Category*]’
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:258:63: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = Category*; _ForwardIterator = Category*; _Tp = Category]’
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_uninitialized.h:281:69: required from ‘_ForwardIterator std::__uninitialized_move_if_noexcept_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = Category*; _ForwardIterator = Category*; _Allocator = std::allocator<Category>]’
/opt/gcc-4.8/include/c++/4.8.2/bits/vector.tcc:415:43: required from ‘void std::vector<_Tp, _Alloc>::_M_emplace_back_aux(_Args&& ...) [with _Args = {}; _Tp = Category; _Alloc = std::allocator<Category>]’
/opt/gcc-4.8/include/c++/4.8.2/bits/vector.tcc:101:54: required from ‘void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = Category; _Alloc = std::allocator<Category>]’
test.cpp:14:29: required from here
/opt/gcc-4.8/include/c++/4.8.2/bits/stl_construct.h:75:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
^
In file included from /opt/gcc-4.8/include/c++/4.8.2/memory:81:0,
from test.cpp:1:
/opt/gcc-4.8/include/c++/4.8.2/bits/unique_ptr.h:273:7: error: declared here
unique_ptr(const unique_ptr&) = delete;
^
Run Code Online (Sandbox Code Playgroud)
name从中删除成员Category,它编译得很好.data只做一个unique_ptr<int>而不是一个指针向量,它编译得很好.Category的main(),而不是创建一个矢量和做emplace_back(),它编译罚款.QString用std::string,它编译罚款.这是怎么回事?是什么让这个代码格式不正确?这是g ++&clang ++中的错误的结果吗?
这里的关键问题是std::vector试图为尽可能多的操作提供强大的异常安全保证,但是,为此,它需要元素类型的支持。对于push_back,emplace_back和朋友们,主要的问题是,如果再分配是必要的,因为现有的元素需要被复制/移动到新的存储会发生什么。
相关的标准措辞在[23.3.6.5p1]中:
备注:如果新大小大于旧容量,则导致重新分配。如果没有发生重新分配,则插入点之前的所有迭代器和引用均保持有效。如果通过复制构造函数,移动构造函数,赋值运算符或移动赋值运算符
T或其他InputIterator操作抛出异常,则没有任何效果。如果在在端部插入单个元件和抛出异常T是CopyInsertable或is_nothrow_move_constructible<T>::value是true,没有影响。否则,如果non-的move构造函数抛出异常CopyInsertableT,则效果未指定。
(C ++ 11的原始措辞已通过LWG 2252的决议得到澄清。)
请注意,is_nothrow_move_constructible<T>::value == true这不一定意味着它T具有noexceptmove构造函数。一个noexcept拷贝构造函数回吐const T&也将这样做。
实际上,这意味着从概念上讲,vector实现通常会尝试为以下解决方案之一生成代码,以将优先级从高到低的顺序复制/移动现有元素到新存储中(T是元素类型,我们很感兴趣。在此处的类类型中):
T具有可用的(存在的,未删除的,不模糊的,可访问的等)noexcept移动构造函数,请使用它;在新存储中构造元素时,不会引发异常,因此无需还原到先前的状态。T有可用的复制构造函数,noexcept则const T&使用;即使复制引发异常,我们也可以恢复到先前的状态,因为原始文件仍在那儿,未经修改。T有一个可用的move构造函数可能引发异常,请使用该构造函数;否则,请使用该构造函数。但是,不再提供强大的异常安全保证。以上可以通过使用std::move_if_noexcept或类似的方法来实现。
让我们看看Category在构造函数方面有什么好处。没有显式声明任何内容,因此隐式声明了默认构造函数,复制构造函数和move构造函数。
复制构造函数使用成员的相应复制构造函数:
data是std::vector,并且vector的复制构造函数不能为noexcept(通常需要分配新的内存),因此Category的复制构造函数noexcept无论具有什么都不能为QString。std::vector<std::unique_ptr<int>>的复制构造函数的定义调用std::unique_ptr<int>的复制构造函数,该函数被显式删除,但这仅影响定义,该定义仅在需要时实例化。重载解析仅需要声明,因此Category具有隐式声明的副本构造函数,如果调用该构造函数,则会导致编译错误。move构造函数:
std::vector具有noexcept移动构造函数(请参见下面的注释),所以data这不是问题。QString(在Qt 5.2之前):
Category将使用QString的复制构造函数,该拷贝构造函数采用const QString&,可以绑定到右值(使用重载分辨率选择子对象的构造函数)。QString的copy构造函数未指定为noexcept,因此Categorymove构造函数也不能为noexcept。QString具有一个显式声明的move构造函数,它将由Category的move构造函数使用。但是,在Qt 5.5之前,QString的move构造函数不是noexcept,因此Category的move构造器也不可以noexcept。QString的move构造函数指定为noexcept,因此Category的move构造函数也指定为noexcept。请注意,Category在所有情况下都具有move构造函数,但它可能不会移动name,也可能不会noexcept。
鉴于以上所有内容,我们可以看到在categories.emplace_back()使用CategoryQt 4时(OP的情况)不会生成使用move构造函数的代码,因为不是noexcept。(当然,有没有在这种情况下,将现有的元素,但是这是一个运行时决定; emplace_back必须包括一个代码路径,处理一般情况下,和代码路径具有编译。)因此,生成的代码调用Category的复制构造函数,这会导致编译错误。
一种解决方案是为其提供一个移动构造函数Category并对其进行标记noexcept(否则将无济于事)。QString无论如何都使用写时复制,因此复制时不太可能抛出。
这样的事情应该起作用:
class Category
{
std::vector<std::unique_ptr<int>> data;
QString name;
public:
Category() = default;
Category(const Category&) = default;
Category(Category&& c) noexcept : data(std::move(c.data)), name(std::move(c.name)) { }
// assignment operators
};
Run Code Online (Sandbox Code Playgroud)
QString如果声明了它,它将使用的move构造函数,否则将使用copy构造函数(就像隐式声明的move构造函数一样)。现在,构造函数是用户声明的,赋值运算符也必须考虑在内。
问题中对项目符号1、3和4的解释现在应该很清楚了。Bullet 2(data仅制作一个unique_ptr<int>)更有趣:
unique_ptr具有已删除的副本构造函数;这Category也会导致将隐式声明的copy构造函数也定义为delete。Category的move构造函数仍按上述声明(noexcept在OP中不这样)。emplace_back不能使用Category的复制构造函数,因此即使可以抛出该异常,也必须使用move构造函数(请参见上面的第一部分)。代码可以编译,但是不再提供强大的异常安全保证。注意:由于在工作草案中采用了N4258,因此C ++ 14之后vector才noexcept在Standard中指定了move构造函数。但是实际上,从C ++ 0x开始,libstdc ++和libc ++都提供了move构造函数。与标准的规范相比,允许实现增强例外规范,所以可以。noexceptvector
libc ++实际上noexcept(is_nothrow_move_constructible<allocator_type>::value)用于C ++ 14及以下版本,但是自C ++ 11起(在[17.6.3.5]中的表28),就要求分配器不能移动和复制可构造,因此对于符合标准的分配器来说是多余的。
注意(已更新):关于强异常安全保证的讨论不适用于MSVC 2017之前的标准库实现:在Visual Studio 2015 Update 3(包括Visual Studio 2015 Update 3)之前,无论是否存在nono,它始终会尝试移动规格。
根据Stephan T.Lavavej的这篇博客文章,MSVC 2017中的实现已经过全面检查,现在可以如上所述正确运行。
除非另有说明,否则标准引用为N4567工作草案。
| 归档时间: |
|
| 查看次数: |
243 次 |
| 最近记录: |