请考虑以下代码:
class user_error : public std::runtime_error
{
public:
using std::exception::what;
explicit user_error(const std::string& what_arg):std::runtime_error(what_arg){}
};
class with_overriden_what : public user_error {
public:
with_overriden_what(const std::string& val) : user_error("user_error"), message(val) { }
std::string message;
virtual const char* what() const noexcept {
return message.c_str();
}
};
通过这个电话:
with_overriden_what ex("thrown");
std::cout << "1. direct result: " << ex.what() << "\n";
std::cout << "2. sliced result: " << static_cast<user_error>(ex).what() << "\n";
std::cout << "3. ranged result: " << ex.user_error::what() << "\n";
令我惊讶的是,2和3的结果不同:
1. direct result: thrown
2. sliced result: user_error
3. ranged result: std::exception
问:标准中是否有解决此问题的段落?
答案 0 :(得分:4)
2.和3.之间的区别在于2.使用动态(==虚拟)调度(==调用)。当调用虚函数时,隐式使用动态调度(请参阅后面的段落以了解异常)。因此2.调用最派生的覆盖,即std::runtime_error::what
,它打印给构造函数的消息"user_error"
,这是构造函数的post条件所要求的:
[runtime.error]
runtime_error(const char* what_arg);
4 效果:构造类runtime_error的对象。
5 后置条件:
strcmp(what(), what_arg) == 0
。
即使函数是虚函数,使用范围解析运算符的函数调用也会执行静态调度。
[class.virtual]
15范围运算符(5.1)的显式限定禁止虚拟调用机制。
因此,覆盖与3无关。重要的是名称解析。 using声明就像任何其他成员声明一样,它隐藏与从父进程中解析的相同名称。
因此,user_error::what
隐藏std::runtime_error::what
。并且user_error::what
由std::exception::what
定义。
现在,根据标准,这个非虚拟名称std::exception::what
应该返回什么? (由我注释):
[例外]
7返回:实现定义的NTBS。 (以null结尾的字符串)
显然,不需要特别打印任何内容,例如打印传递给包含此子对象的派生类的构造函数的字符串。任何字符串都符合标准。
行为的最小示例,不涉及异常:
#include <iostream>
struct A {
virtual void x() {
std::cout << "A\n";
}
};
struct B : A {
void x() {
std::cout << "B\n";
}
};
struct C : B {
using A::x;
};
int main() {
C c;
c.x();
c.C::x();
return 0;
}
两条线的输出必须不同。