在异常对象上调用 std::move 是不是正确?

Posted

技术标签:

【中文标题】在异常对象上调用 std::move 是不是正确?【英文标题】:Is it correct to call std::move on an exception object?在异常对象上调用 std::move 是否正确? 【发布时间】:2022-01-23 21:47:23 【问题描述】:

我很想知道将异常对象移动到某个局部变量是否正确。这个动作有没有可能导致UB?我的担心是由于引用捕获假定访问位于其他地方的异常对象(因为它必须一直存在到堆栈展开结束)。请参见下面的示例。

int main()

    std::pair<int,int> res; //may be heavy object, it's only example
    int a[3][5];// assume filled
    try
    
        for(int i = 0; i < 3; ++i)
        
            for(int j = 0; j < 5; ++j)
            
                if(a[i][j] % 2 ==0 )
                
                    throw std::pair<int,int>(i,j);
                
            
        
    catch(std::pair<int,int>& pair)
    
        res = std::move(pair);
    

【问题讨论】:

a pair&lt;int,int&gt; 仍然会被复制。 异常不应是“重”对象。 移动一个异常对象很好:结果无论如何都需要是可破坏的,并且在大多数情况下你不会关心对象的状态。当然,如果您打算重新抛出异常,它会将其状态更改为某个不确定的状态(尽管在 std::pair&lt;int, int&gt; 移动对象的情况下不会影响原始对象 - 它将始终被复制)。 虽然我看不出有什么优势,但我认为不会有问题。一点旁注:不应在正常执行流程中抛出异常,而应仅针对罕见的异常事件。 自 C++11 起,捕获异常对象并将其寿命延长到 catch 块之外的标准方法是使用 std::current_exception(),但我看不到任何访问它捕获的对象,您只能使用std::rethrow_exception() 重新抛出它。 【参考方案1】:

移出的对象仍处于有效状态。

即使你通过throw; 重新抛出异常,并再次捕获它,它也会起作用。

对于处于有效但未指定状态的对象(假设您使用这样的对象而不是一对整数),结果可能是意外的,但仍然没有 UB。

【讨论】:

【参考方案2】:

由于您的代码没有通过为预期结果抛出异常来遵循良好实践,因此问题的答案实际上并不重要,因为无论如何您都应该重写代码...

这是一个关于如何编写此类代码的示例。

#include <utility>

std::pair<int, int> find(int(&a)[3][5])

    for (int i = 0; i < 3; ++i)
    
        for (int j = 0; j < 5; ++j)
        
            if (a[i][j] % 2 == 0)
            
                return std::pair<int, int>(i, j);
            
        
    

    return ;


int main()

    int a[3][5];// assume filled
    auto res = find(a);

虽然您的初始代码可以工作,但抛出和捕获异常可能会很慢,因此即使移动,您的代码也可能比不使用异常返回结果的版本慢。

一个好的做法是只对意外错误使用异常。

【讨论】:

以上是关于在异常对象上调用 std::move 是不是正确?的主要内容,如果未能解决你的问题,请参考以下文章

空范围的 std::copy() 或 std::move() 是不是需要有效目的地?

什么时候用std::move()?

在 C++11 中对 boost::asio 套接字对象重复 std::move

如果 lambda 使用 std::move() 捕获不可复制的对象,为啥它是不可移动的?

std::emplace_back 调用向量中其他对象的构造函数?

move_iterator 是做啥用的