繁体   English   中英

如果没有 noexcept 移动构造函数,为什么带有 std::vector 的代码不能编译,但带有 std::unique_ptr 的代码却可以编译?

[英]Why does code with std::vector not compile but with std::unique_ptr it does, if there is no noexcept move constructor?

为什么下面的程序不能编译?

注意:something_t 的移动构造函数不是 noexcept。

#include <memory>
#include <vector>

class something_t {
public:
    constexpr something_t() = default;

    constexpr something_t(const something_t& other)
        : field_(other.field_) {
    }

    constexpr something_t(something_t&& other)
        : field_(other.field_) {
    }

private:
    unsigned int field_{ 0 };
};

struct data_t {
    something_t something;
    std::vector<std::unique_ptr<int>> move_only; // <-- this line
};

int main() {
    std::vector<data_t> result;
    data_t data;
    result.push_back(std::move(data));
    return 0;
}

错误是(在 g++ 内):

/usr/include/c++/9/bits/stl_uninitialized.h:127:72: error: static assertion failed: result type must be constructible from value type of input range
127 |       static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
    |                                                                        ^~~~~        

(与 clang 和 MSVC 几乎相同)。

如果我用std::unique_ptr<int> move_only的“这一行”注释替换该行,那么代码编译得很好:

    struct data_t {
        something_t something;
        std::unique_ptr<int> move_only;
    };

为什么删除std::vector有帮助? 如果我使something_t移动构造函数为 noexcept,它也可以使用或不使用std::vector进行编译。

注意:将noexcept添加到something_t的移动构造函数会有所帮助,但这不是问题。

问题是:

为什么这样:

    struct data_t {
        something_t something;
        std::unique_ptr<int> move_only;
    };

程序是否编译?

但随着

struct data_t {
    something_t something;
    std::vector<std::unique_ptr<int>> move_only; // <-- this line
};

程序不编译吗?

事实上, std::unique_ptr<int>std::vector<std::unique_ptr<int>>

  • 不可复制
  • noexcept-可移动

所以它们具有相同的性质。

更新:我尝试比较两种变体的 type_traits:

                                                data_t(vector)          data_t(unique_ptr):
is_constructible:                               true                    true
is_trivially_constructible:                     false                   false
is_nothrow_constructible:                       true                    true
is_default_constructible:                       true                    true
is_trivially_default_constructible:             false                   false
is_nothrow_default_constructible:               true                    true
is_copy_constructible:                          true                    false
is_trivially_copy_constructible:                false                   false
is_nothrow_copy_constructible:                  false                   false
is_move_constructible:                          true                    true
is_trivially_move_constructible:                false                   false
is_nothrow_move_constructible:                  false                   false
is_assignable:                                  false                   false
is_trivially_assignable:                        false                   false
is_nothrow_assignable:                          false                   false
is_copy_assignable:                             false                   false
is_trivially_copy_assignable:                   false                   false
is_nothrow_copy_assignable:                     false                   false
is_move_assignable:                             false                   false
is_trivially_move_assignable:                   false                   false
is_nothrow_move_assignable:                     false                   false
is_destructible:                                true                    true
is_trivially_destructible:                      false                   false
is_nothrow_destructible:                        true                    true
is_swappable:                                   false                   false
is_nothrow_swappable:                           false                   false

唯一的区别是:

is_copy_constructible:                          true                    false

即,带有vectordata_t是可复制构造的,而对于unique_ptr则不是。 但是这种差异如何影响编译?

这里的重要区别是:

std::is_copy_constructible<std::vector<std::unique_ptr<int>>>::value == true
std::is_copy_constructible<std::unique_ptr<int>>::value == false

第一个可能令人惊讶。 但请注意, is_copy_constructible和大多数类似的类型特征只要求声明它们测试的操作,而不是实际使用它是有效的。 不幸的是, std::vector在这里缺少一些“SFINAE 正确性”,但这可能是为了向后兼容而有意的。

标准对template <class T, class Allocator> class vector in [vector.overview]/2的描述简单地说它声明了一个成员vector(const vector& x); . 以下各节对复制构造函数只字未提。 特别是, std::vector没有类似于[optional.ctor]/6中关于std::optional<T>的复制构造函数的这句话:

 constexpr optional(const optional& rhs);

备注:除非is_copy_constructible_v<T>为真,否则该构造函数应定义为已删除。

由于对std::vector<T>的各种要求,它的函数(如push_backinsertemplace需要处理重新分配和填充向量中已有元素的新 memory 的可能性)被迫像这样实现:

  • 如果std::is_nothrow_move_constructible<T>::value为真,则使用T的移动构造函数,并且函数提供强异常保证。
  • 如果std::is_nothrow_move_constructible<T>::value为 false 且std::is_copy_constructible<T>::value为 true,则使用T的复制构造函数,并且函数提供强异常保证。
  • 如果std::is_nothrow_move_constructible<T>::valuestd::is_copy_constructible<T>::value都为假,则使用T的移动构造函数,但函数不能提供强异常保证。

T必须是可移动构造的,这实际上可能意味着使用复制构造函数,作为这些容器函数的一般要求。)

因此,当data_t具有std::vector<std::unique_ptr<int>>成员时,它“错误地”具有未删除的隐式声明的复制构造函数。 这导致std::vector<data_t>::push_back从上面的列表中选择第二个选项,但实际使用复制构造函数会导致错误。

data_tstd::unique_ptr<int>成员时,其删除的复制构造函数意味着data_t隐式声明的复制构造函数也被删除。 所以在这种情况下, std::vector<data_t>::push_back使用移动构造函数从上面的列表中选择第三个选项,但如果它确实抛出,则向量将留在未指定的 state 中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM