前段时间我听说我不应该创建具有std::string
类型字段的异常类。这就是Boost website says。基本原理是std::string
复制构造函数可以在内存分配失败时抛出异常,并且如果在捕获当前处理的异常之前抛出异常,则程序终止。
但是,它仍然存在于移动构造函数的世界中吗?抛出异常时,不会使用移动构造函数而不是复制构造函数吗?我是否理解正确使用C ++ 11将不会进行内存分配,不存在异常的可能性,std::string
现在在异常类中绝对正常?
答案 0 :(得分:13)
答案是:
是的,您仍然不希望在您的例外类型中嵌入std::string
。例外情况经常被复制,有时候是在你不知情的情况下。例如,在某些平台上std::rethrow_exception
将复制异常(有些则不会)。
为了获得最佳实践,请保留您的复制构造函数noexcept
。
然而一切都没有丢失。一个鲜为人知的事实是,C ++在标准中总是有一个不可变的引用计数字符串类型(带有非抛出的复制构造函数),只是带有模糊的名称。实际上有两个名字:
logic_error
runtime_error
这些类型的规范必须包含一个不可变的引用计数字符串对象。好吧,不是完全不变的。您可以使用赋值替换字符串。但是你不能以其他方式修改字符串。
我的建议是从这些类型之一派生,或者如果这是不可接受的,嵌入其中一种类型并将其视为不可变的引用计数字符串类型:
#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>{}, "");
答案 1 :(得分:1)
抛出异常时是否复制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();
}
您不会在A
或test1
中复制test2
,但最终会在test3
中复制一份。