[英]C++11 introduced exception constructors taking `const char*`. But why?
标准库缺陷#254 ,它涵盖了新的异常构造函数的添加:
std::logic_error::logic_error(const char* what_arg);
std::runtime_error::runtime_error(const char* what_arg);
// etc.
作为基本原理,存储std::string
s的想法打开了一些与可能存在问题的内存分配有关的蠕虫。
然而, 在休息室中orlp开始讨论之后,我觉得除非标准要求what_arg
只是一个字符串文字(或指向静态存储持续时间的其他缓冲区的指针),否则它必须执行反正是C-string的副本,以保持成员函数的良好定义what()
。
那是因为:
void bar() {
char buf[] = "lol";
throw std::runtime_error(buf);
}
void foo() {
try {
bar();
}
catch (std::exception& e) {
std::cout << e.what() << '\n'; // e.what() points to destroyed data!
}
}
但我看不到任何这样的任务。 实际上,异常对象是否深层复制what_arg
似乎完全没有指定。
如果他们这样做 ,那么首先添加重载(消除额外分配)的大部分理由似乎完全是空的。
这可能是标准缺陷,还是我在这里遗漏了什么?
这只是“程序员:不要在任何地方传递悬空指针”的情况吗?
这允许(或者至少显然是为了方便 - 在下面看到更多)实现,以消除副本,如果它可以检测到(通过本身不是标准化的方式)传递的是字符串文字或静态的其他内容储存期限。
例如,假设编译器将所有字符串文字汇集到一个由__string_literals_begin
和__string_literals_end
分隔的范围内。 然后在std::exception
的构造函数中的某个地方,它可以具有以下常规顺序的代码:
namespace std {
exception::exception(char const *s) {
if (in_range(s, __string_literals_begin, __string_literals_end)) {
stored_what = s;
destroy_stored_what = false;
}
else {
stored_what = dupe(s);
destroy_stored_what = true;
}
// ...
}
exception::~exception() {
if (destroy_stored_what)
delete_string(stored_what);
}
链接DR中的最终评论指出:
[Oxford:提议的解决方案只是解决了使用const char *和字符串文字构造异常对象的问题,而无需显式包含或构造std :: string。 ]
因此,根据当时的评论,委员会意识到这些超载并未满足所有需求,但确实解决了(至少被认为是)需求。
(几乎)确实如果实施可以提供这些超载,即使它们没有被标准规定 - 尽管如此,委员会似乎已经确信添加它们对于上述情况主要(如果不是唯一的话)是有用的 - - 当字符串文字传递给ctor时,只有一个浅拷贝。
我不确定它是否是原因,但有一点是runtime_error保证它的复制构造函数不会抛出,这表明某种引用计数机制。
额外的构造函数意味着它只需要制作一个副本,从char *到底层mechinsm,而不是两次。 一旦进入一个字符串然后进入参考机制
缺陷的解决方案解决了另一个问题。 缺陷的底部有这样的说明:
[Oxford:提议的解决方案只是解决了使用const char *和字符串文字构造异常对象的问题,而无需显式包含或构造std :: string。 ]
libc ++如何处理这个问题?
他们目前使用ref-counting字符串来存储消息:
class _LIBCPP_EXCEPTION_ABI logic_error
: public exception
{
private:
_VSTD::__libcpp_refstring __imp_;
其中__imp_
初始化如下:
logic_error::logic_error(const string& msg) : __imp_(msg.c_str()) {}
logic_error::logic_error(const char* msg) : __imp_(msg) {}
这个字符串__libcpp_refstring
在存储新的char const*
时会分配一个新的缓冲区:
explicit __libcpp_refstring(const char* msg) {
std::size_t len = strlen(msg);
_Rep_base* rep =
static_cast<_Rep_base *>(::operator new(sizeof(*rep) + len + 1));
但是,当然, __libcpp_refstring
的复制构造函数不会分配新的缓冲区。
(是的,这不能回答这个问题,但它应该对我认为的问题有所了解。例如,如果在logic_error
中只有一个std::string const&
ctor,则必须为ref添加一个额外的分配-计数器。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.