为什么 noexcept 说明符的作用域不在声明的方法内?

Why isn't the noexcept specifier scoped within the declared method?

本文关键字:方法 声明 noexcept 说明符 作用域 为什么      更新时间:2023-10-16

尝试设计一些无异常类,我有一个与此类似的继承结构,但我发现 noexcept 说明符在使用成员函数时几乎没有帮助,因为说明符的范围不在函数内。

class Base
{
protected:
    Base() noexcept {}
};
class Derived : public Base
{
public:
    // error: 'Base::Base()' is protected
    Derived() noexcept(noexcept(Base{})) : Base{} {}
    // error: 'foo' was not declared in this scope
    Derived(int) noexcept(noexcept(foo())) {}
    // error: invalid use of 'this' at top level
    Derived(float) noexcept(noexcept(this->foo())) {}
    void foo() noexcept {}
};

演示

这也许是在 C++17 中得到改进的吗?尝试搜索此内容未产生相关结果。现在我已经放弃了一些非常丑陋(可能不正确)的尝试,例如 noexcept(noexcept(static_cast<Derived*>(nullptr)->foo())) ,但这对受保护的基类构造函数没有帮助。

目前是否可以声明引用像这样的受保护基类方法的 noexcept 说明符? noexcept(auto) 可能是相关的,但当然还不可能。我是否忽略了任何其他允许我包含此说明符的内容,或者在这种情况下我只需要省略它?

可以使用 Base 构造函数在作用域中的表达式来解决它,如下所示:

struct test_base : public Base {};
Derived() noexcept(noexcept(test_base())) : Base() {}

我相信你不能直接使用Base()的原因与这个问题有关。

受保护的访问说明符的工作方式,它允许派生类 B 仅在以下情况下访问基类 A 的对象的内容 类 A 的对象是类 B 的子对象。这意味着唯一的 您可以在代码中执行的操作是通过访问 A 的内容 B:可以通过类型 B * 的指针访问 A 的成员(或 B型参考文献&)。但是您不能通过以下方式访问相同的成员 类型 A * 的指针(或引用 A &)。

这就像你有一个这样的成员函数一样:

void badfunc()
{
    B b;
}

您正在尝试直接使用 Base 的构造函数,而不是通过 Derived 。当您在构造函数初始化列表中初始化基时,这是一个特殊的上下文,允许您调用构造函数,因为您是在初始化 Derived 的过程中执行此操作的。

这实际上是多个问题合二为一。

关于this...

根据我的理解,this的使用应该是完全多余的,但编译器对C++11的支持并不完全普遍。 根据 C++11 标准,这应该有效:

struct Base {
    void func() noexcept;
};
struct Derived() {
    void func() noexcept(noexcept(Base::func())) {}
};

请注意,base_func()是一个非静态成员函数,但由于它出现在"未计算的操作数"中,所以没关系。 从 n3337 第 4.1.1 秒开始:

只能使用表示类的非静态数据成员或非静态成员函数的 id 表达式

  • 如果该 id-expression 表示非静态数据成员,并且它出现在未计算的操作数中。

但是,某些编译器不支持此功能。 然后,您将被迫使用std::declval

#include <utility>
struct Base {
    void func() noexcept;
};
struct Derived() {
    void func() noexcept(noexcept(std::declval<Base>().func())) {}
};

关于辅助功能...

我通读了标准中关于"未评估的操作数"和"成员访问控制"的相关部分,得出的结论是该标准有点模棱两可。 它提到protected名称只能由成员、朋友和派生类使用。 问题是未计算的操作数是否"使用"出现在其中的成员名称。 他们当然不会使用成员名称,如果没有提供定义,甚至可以使用成员名称,而这种歧义正是术语"odr-use"甚至存在的原因! 例如

int f(); // No definition anywhere in program
int x = sizeof(f()); // Completely valid, even without definition of f
struct X {
    X() = delete; // Cannot use this constructor
};
int xsize = sizeof(X{}); // Completely valid

尽管有些不清楚,但我很难想象C++委员会可能打算让您在未计算的操作数中使用已删除的成员函数,但不是无法访问的成员函数。 但是,我不确定。

请注意,上面的代码在 GCC 和 Clang 中编译时都没有错误。 但是,以下代码不是:

class X {
    X(){}
};
class Y {
    Y() = delete;
};
bool xokay = noexcept(X{}); // Error!
bool yokay = noexcept(Y{}); // Ok

GCC和Clang都接受Y,但不接受X,至少可以说这似乎有点奇怪。 以下代码被 Clang 接受,但不接受 GCC,使用 std::declval 无济于事:

class Base {
protected:
    void func();
};
class Derived : public Base {
    // Clang accepts this, GCC does not.
    void func2() noexcept(noexcept(Base::func())) {}
};

真是一团糟。

结论

这里的结论是,似乎有很多不一致的地方需要解决,当前编译器和 C++11 规范之间也有很多差距。