当前位置:  开发笔记 > 编程语言 > 正文

为什么QString和vector <unique_ptr <int >>在这里看起来不兼容?

如何解决《为什么QString和vector<unique_ptr<int>>在这里看起来不兼容?》经验,为你挑选了1个好方法。

我正在尝试编译一些代码,这减少到这个:

#include 
#include 
#include 

class Category
{
    std::vector> data;
    QString name;
};

int main()
{
    std::vector categories;
    categories.emplace_back();
};

按原样编译,它会导致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; _Args = {const std::unique_ptr >&}]’:
/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*, std::vector > >; _ForwardIterator = std::unique_ptr*; 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*, std::vector > >; _ForwardIterator = std::unique_ptr*]’
/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*, std::vector > >; _ForwardIterator = std::unique_ptr*; _Tp = std::unique_ptr]’
/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; _Alloc = std::allocator >]’
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]’
/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]’
/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]’
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]’
     { ::new(static_cast(__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;
       ^

如果我name从中删除成员Category,它编译得很好.

如果我data只做一个unique_ptr而不是一个指针向量,它编译得很好.

如果我创建一个单一Categorymain(),而不是创建一个矢量和做emplace_back(),它编译罚款.

如果我更换QStringstd::string,它编译罚款.

这是怎么回事?是什么让这个代码格式不正确?这是g ++&clang ++中的错误的结果吗?



1> bogdan..:

这里的关键问题是std::vector试图为尽可能多的操作提供强大的异常安全保证,但是,为此,它需要元素类型的支持。对于push_backemplace_back和朋友们,主要的问题是,如果再分配是必要的,因为现有的元素需要被复制/移动到新的存储会发生什么。

相关的标准措辞在[23.3.6.5p1]中:

备注:如果新大小大于旧容量,则导致重新分配。如果没有发生重新分配,则插入点之前的所有迭代器和引用均保持有效。如果通过复制构造函数,移动构造函数,赋值运算符或移动赋值运算符T或其他InputIterator 操作抛出异常,则没有任何效果。如果在在端部插入单个元件和抛出异常TCopyInsertableis_nothrow_move_constructible::valuetrue,没有影响。否则,如果non-的move构造函数抛出异常CopyInsertable T,则效果未指定。

(C ++ 11的原始措辞已通过LWG 2252的决议得到澄清。)

请注意,is_nothrow_move_constructible::value == true这不一定意味着它T具有noexceptmove构造函数。一个noexcept拷贝构造函数回吐const T&也将这样做。

实际上,这意味着从概念上讲vector实现通常会尝试为以下解决方案之一生成代码,以将优先级从高到低的顺序复制/移动现有元素到新存储中(T是元素类型,我们很感兴趣。在此处的类类型中):

如果T具有可用的(存在的,未删除的,不模糊的,可访问的等)noexcept移动构造函数,请使用它;在新存储中构造元素时,不会引发异常,因此无需还原到先前的状态。

否则,如果T有可用的复制构造函数,noexceptconst T&使用;即使复制引发异常,我们也可以恢复到先前的状态,因为原始文件仍在那儿,未经修改。

否则,如果T有一个可用的move构造函数可能引发异常,请使用该构造函数;否则,请使用该构造函数。但是,不再提供强大的异常安全保证。

否则,代码将无法编译。

以上可以通过使用std::move_if_noexcept或类似的方法来实现。


让我们看看Category在构造函数方面有什么好处。没有显式声明任何内容,因此隐式声明了默认构造函数,复制构造函数和move构造函数。

复制构造函数使用成员的相应复制构造函数:

datastd::vector,并且vector的复制构造函数不能为noexcept(通常需要分配新的内存),因此Category的复制构造函数noexcept无论具有什么都不能为QString

std::vector>的复制构造函数的定义调用std::unique_ptr的复制构造函数,该函数被显式删除,但这仅影响定义,该定义仅在需要时实例化。重载解析仅需要声明,因此Category具有隐式声明的副本构造函数,如果调用该构造函数,则会导致编译错误。

move构造函数:

std::vector具有noexcept移动构造函数(请参见下面的注释),所以data这不是问题。

旧版本QString(在Qt 5.2之前):

一招构造方法中没有明确地表明(见上面的Praetorian的评论),所以,因为有一个显式声明的拷贝构造函数,此举构造不会被隐式地声明,在所有

隐式声明的move构造函数的定义Category将使用QString的复制构造函数,该拷贝构造函数采用const QString&,可以绑定到右值(使用重载分辨率选择子对象的构造函数)。

在这些旧版本中,QString的copy构造函数未指定为noexcept,因此Categorymove构造函数也不能为noexcept

从Qt 5.2开始,QString具有一个显式声明的move构造函数,它将由Category的move构造函数使用。但是,在Qt 5.5之前,QString的move构造函数不是noexcept,因此Category的move构造器也不可以noexcept

从Qt 5.5开始,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> 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
};

QString如果声明了它,它将使用的move构造函数,否则将使用copy构造函数(就像隐式声明的move构造函数一样)。现在,构造函数是用户声明的,赋值运算符也必须考虑在内。

问题中对项目符号1、3和4的解释现在应该很清楚了。Bullet 2(data仅制作一个unique_ptr)更有趣:

unique_ptr具有已删除的副本构造函数;这Category也会导致将隐式声明的copy构造函数也定义为delete。

Category的move构造函数仍按上述声明(noexcept在OP中不这样)。

这意味着为生成的代码emplace_back不能使用Category的复制构造函数,因此即使可以抛出该异常,也必须使用move构造函数(请参见上面的第一部分)。代码可以编译,但是不再提供强大的异常安全保证。


注意:由于在工作草案中采用了N4258,因此C ++ 14之后vectornoexcept在Standard中指定了move构造函数。但是实际上,从C ++ 0x开始,libstdc ++和libc ++都提供了move构造函数。与标准的规范相比,允许实现增强例外规范,所以可以。noexceptvector

libc ++实际上noexcept(is_nothrow_move_constructible::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工作草案。

推荐阅读
拾味湖
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有