不在异常中嵌入std::字符串的规则是否仍然适用于move构造函数

Does rule of not embedding std::string in exceptions still hold with move constructors?

本文关键字:是否 规则 构造函数 move 适用于 字符串 异常 std      更新时间:2023-10-16

我之前听说不应该创建具有std::string类型字段的异常类。Boost网站就是这么说的。其原理是,如果内存分配失败,std::string复制构造函数可以抛出异常,如果在捕获当前处理的异常之前抛出异常,则程序将终止。

然而,它仍然适用于移动构造函数的世界吗?抛出异常时,不会使用move构造函数而不是copy构造函数吗?我是否正确地理解,使用C++11将不会进行内存分配,不存在异常的可能性,并且std::string现在在异常类中绝对可以?

答案是:

是的,您仍然不希望在异常类型中嵌入std::string。例外情况经常被复制,有时是在你不知情的情况下。例如,在一些平台上,std::rethrow_exception将复制异常(在某些平台上则不会(。

为了获得最佳实践,请保留副本构造函数noexcept

然而,并没有失去一切。一个鲜为人知的事实是,C++在标准中总是有一个不可变的ref计数字符串类型(带有非抛出复制构造函数(,只是有一个模糊的名称。实际上有两个名字:

logic_error
runtime_error

这些类型的规范必须包含一个不可变的ref计数字符串类对象。嗯,不是完全一成不变的。可以用赋值替换字符串。但是您不能在其他地方修改字符串。

我的建议是从其中一种类型派生,或者如果这是不可接受的,则嵌入其中一种,并将其视为不可变的ref计数字符串类型:

#include <stdexcept>
#include <iostream>
class error1
    : public std::runtime_error
{
    using msg_ = std::runtime_error;
public:
    explicit error1(std::string const& msg)
        : msg_(msg)
    {}
};
class error2
{
    std::runtime_error msg_;
public:
    explicit error2(std::string const& msg)
        : msg_(msg)
    {}
    char const* what() const noexcept {return msg_.what();}
};
void
test_error1()
{
    try
    {
        throw error1("test1");
    }
    catch (error1 const& e)
    {
        std::cout << e.what() << 'n';
    }
}
void
test_error2()
{
    try
    {
        throw error2("test2");
    }
    catch (error2 const& e)
    {
        std::cout << e.what() << 'n';
    }
}
int
main()
{
    test_error1();
    test_error2();
}

std::lib将为您负责所有的字符串处理和内存管理,并且您可以在交易中获得noexcept复制:

static_assert(std::is_nothrow_copy_constructible<error1>{}, "");
static_assert(std::is_nothrow_copy_assignable   <error1>{}, "");
static_assert(std::is_nothrow_copy_constructible<error2>{}, "");
static_assert(std::is_nothrow_copy_assignable   <error2>{}, "");

抛出异常时是否制作string的副本取决于catch块。

举个例子:

#include <iostream>
struct A
{
};
void foo()
{
   throw A();
}
void test1()
{
   try
   {
      foo();
   }
   catch (A&& a)
   {
   }
}
void test2()
{
   try
   {
      foo();
   }
   catch (A const& a)
   {
   }
}
void test3()
{
   try
   {
      foo();
   }
   catch (A a)
   {
   }
}
int main()
{
   test1();
   test2();
   test3();
}

您不会在test1test2中复制A,但最终会在test3中复制。