移动哪些投掷? [英] Move which throws?

查看:122
本文介绍了移动哪些投掷?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据我的理解,move-constructors和move-assign必须标记为noexcept,以便编译器在例如重新分配向量时使用它们。



>

例如,在构造时不能抛出移动的类具有已分配的资源。

解决方案


但是,有没有一个真实世界的情况, b move-construct(或swap)可能会抛出?


是的。考虑 std :: list 的实现。 end 迭代器必须在列表中指向一个超过最后一个元素。存在 std :: list 的实现,其中 end 指向是动态分配的节点。即使默认的构造函数分配这样一个节点,当你调用 end(),有一些东西要指向。



<在这种实现中,每个构造函数必须为 end()分配一个节点,以指向...甚至move构造函数。该分配可能会失败,并抛出异常。



这种行为可以扩展到任何基于节点的容器。



也有这些基于节点的容器的实现做一个短字符串优化:它们将端节点嵌入在容器类本身中,而不是动态分配。因此,默认构造函数(和移动构造函数)不需要分配任何东西。



移动赋值运算符可以为任何容器< X& c $ c>如果对于容器的分配器 propagate_on_container_move_assignment :: value 为false,并且lhs中的分配器不等于rhs中的分配器。在这种情况下,移动分配运算符被禁止将内存所有权从rhs传输到lhs。这不会发生,如果你使用 std :: allocator ,因为 std :: allocator 的所有实例等于



这是一个符合标准的可移植示例当 propagate_on_container_move_assignment :: value 为false时的情况。它已经针对最新版本的VS,gcc和clang测试。

  #include< cassert> 
#include< cstddef>
#include< iostream>
#include< vector>

template< class T>
class allocator
{
int id_;
public:
使用value_type = T;

allocator(int id)noexcept:id_(id){}
template< class U> allocator(allocator< u> const& u)noexcept:id_(u.id_){}

value_type *
allocate(std :: size_t n)
{
return static_cast< value_type *>(:: operator new(n * sizeof(value_type)));
}

void
deallocate(value_type * p,std :: size_t)noexcept
{
:: operator delete(p);
}

template< class U,class V>
friend
bool
operator ==(allocator< U> const& x,allocator< V> const& y)noexcept
{
return x.id_ == y.id_;
}
};

template< class T,class U>
bool
operator!=(allocator< T> const& x,allocator< U> const& y)noexcept
{
return!(x == y);
}

template< class T>使用vector = std :: vector< T,allocator< T>

struct A
{
static bool time_to_throw;

A()= default;
A(const A&){if(time_to_throw)throw 1;}
A& operator =(const A&){if(time_to_throw)throw 1; return * this;}
};

bool A :: time_to_throw = false;

int
main()
{
vector< A> v1(5,A {},allocator< A> {1});
vector< A> v2(allocator< A> {2});
v2 = std :: move(v1);
try
{
A :: time_to_throw = true;
v1 = std :: move(v2);
assert(false);
}
catch(int i)
{
std :: cout< i<< '\\\
';
}
}

此程序输出:

  1 

propagate_on_container_move_assignment :: value 为false时,向量 移动赋值运算符是复制/所讨论的两个分配器不比较相等。如果任何这些副本/移动抛出,那么容器移动赋值将抛出。


To my understanding, move-constructors and move-assign must be marked noexcept in order for the compiler to utilize them when, for example, reallocating inside a vector.

However, is there any real-world case where a move-assign, move-construct might actually throw?

Update:

Classes that for example has an allocated resource when constructed cant be no-throw move.

解决方案

However, is there any real-world case where a move-assign, move-construct (or swap) might actually throw?

Yes. Consider an implementation of std::list. The end iterator must point "one past the last element" in the list. There exist implementations of std::list where what end points to is a dynamically allocated node. Even the default constructor allocates such a node so that when you call end(), there is something to point to.

In such an implementation, every constructor must allocate a node for end() to point to… even the move constructor. That allocation may fail, and throw an exception.

This same behavior can extend to any node-based container.

There are also implementations of these node-based containers that do a "short-string" optimization: They embed the end node within the container class itself, instead of dynamically allocating. Thus the default constructor (and move constructor) need not allocate anything.

The move assignment operator can throw for any container<X> if for the container's allocator propagate_on_container_move_assignment::value is false, and if the allocator in the lhs is not equal to the allocator in the rhs. In that case the move assignment operator is forbidden from transferring memory ownership from the rhs to the lhs. This can not happen if you are using std::allocator, as all instances of std::allocator are equal to one another.

Update

Here is a conforming and portable example of the case when propagate_on_container_move_assignment::value is false. It has been tested against the latest version of VS, gcc and clang.

#include <cassert>
#include <cstddef>
#include <iostream>
#include <vector>

template <class T>
class allocator
{
    int id_;
public:
    using value_type    = T;

    allocator(int id) noexcept : id_(id) {}
    template <class U> allocator(allocator<U> const& u) noexcept : id_(u.id_) {}

    value_type*
    allocate(std::size_t n)
    {
        return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
    }

    void
    deallocate(value_type* p, std::size_t) noexcept
    {
        ::operator delete(p);
    }

    template <class U, class V>
    friend
    bool
    operator==(allocator<U> const& x, allocator<V> const& y) noexcept
    {
        return x.id_ == y.id_;
    }
};

template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
    return !(x == y);
}

template <class T> using vector = std::vector<T, allocator<T>>;

struct A
{
    static bool time_to_throw;

    A() = default;
    A(const A&) {if (time_to_throw) throw 1;}
    A& operator=(const A&) {if (time_to_throw) throw 1; return *this;}
};

bool A::time_to_throw = false;

int
main()
{
    vector<A> v1(5, A{}, allocator<A>{1});
    vector<A> v2(allocator<A>{2});
    v2 = std::move(v1);
    try
    {
        A::time_to_throw = true;
        v1 = std::move(v2);
        assert(false);
    }
    catch (int i)
    {
        std::cout << i << '\n';
    }
}

This program outputs:

1

which indicates that the vector<T, A> move assignment operator is copy/moving its elements when propagate_on_container_move_assignment::value is false and the two allocators in question do not compare equal. If any of those copies/moves throws, then the container move assignment throws.

这篇关于移动哪些投掷?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆