6

考虑以下代码:

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

问:标准中有一个段落可以解决这种行为吗?

4

1 回答 1

4

2. 和 3. 的区别在于 2. 使用动态(== 虚拟)调度(== 调用)。当调用虚函数时,会隐式使用动态调度(有关异常,请参见后面的段落)。因此 2. 调用最派生的覆盖,它根据构造函数的后置条件std::runtime_error::what打印"user_error"提供给构造函数的消息:

[运行时错误]

runtime_error(const char* what_arg);

4 效果:构造类 runtime_error 的对象。

5 后置条件:strcmp(what(), what_arg) == 0.


即使函数是虚拟的,使用范围解析运算符的函数调用也会进行静态分派。

[类.虚拟]

15 使用范围运算符(5.1)的显式限定抑制了虚拟调用机制。

因此,覆盖对于 3 无关紧要。重要的是名称解析。using 声明与任何其他成员声明一样,因为它隐藏了本来可以从父级解析的相同名称。

所以,user_error::what隐藏std::runtime_error::what。并且,user_error::what由 定义std::exception::what


现在,std::exception::what根据标准,这个非虚拟调用应该返回什么?(由我注释)

[例外]

7 返回:实现定义的 NTBS。(以空结尾的字符串)

显然,不需要打印任何特别的东西,例如打印传递给派生类的构造函数的字符串,该派生类包含这个作为子对象。任何字符串都符合标准。


该行为的一个最小示例,不涉及异常:

#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;
}

两条线的输出必须不同。

于 2017-08-10T12:29:57.080 回答