为什么noexcept修饰符不在声明的方法范围内?

6

在设计一些无异常类时,我有一个类似于这样的继承结构,但是我发现当使用成员函数时,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) 可能是相关的,但目前还不可行。我是否忽略了其他任何允许我包括此指定符的内容,或者在那种情况下我必须省略它?

2个回答

2
您可以通过使用一个表达式来解决这个问题,其中Base构造函数处于范围内,如下所示:
struct test_base : public Base {};

Derived() noexcept(noexcept(test_base())) : Base() {}

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

受保护访问限定符的工作方式是,只有当A类的对象是B类的子对象时,派生类B才能访问A类对象的内容。这意味着在您的代码中,您唯一可以做的是通过B来访问A的内容:您可以通过类型为B *(或类型为B&的引用)的指针访问A的成员。但是您无法通过类型为A *(或类型为A&的引用)的指针访问同样的成员。

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

void badfunc()
{
    B b;
}

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


我忘了提到,否则这段代码在clang中可以正确编译。其他情况可能是gcc中的一个bug,但我仍在研究标准。 - Kurt Stutsman
我已经更新了我的回复,希望能够阐明Base()不可访问的原因。 - Kurt Stutsman

2

这实际上是多个问题的综合。

关于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-expression只能用于:

...

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

然而,一些编译器不支持这种方式。您将被迫使用std::declval

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

关于无障碍性的介绍...

我阅读了有关“未评估操作数”和“成员访问控制”的标准相关部分,并得出结论,该标准有些模糊不清。它提到,protected名称只能被成员、友元和派生类使用。问题是未评估操作数是否会“使用”其中出现的成员名称。它们显然不会odr-use成员名称,甚至可以在没有定义的情况下使用成员名称,而这种模糊性正是术语“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规范之间存在很多差距。


我非常确定你想要使用 std::declval 而不是 std::decltype - Barrett Adair

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接