判断一个抽象基class的构造函数是否为noexcept?

Determine whether a constructor of an abstract base class is noexcept?

在C++11及之后的版本中,如何判断抽象基类class的构造函数是否为noexcept?以下方法不起作用:

#include <new>
#include <type_traits>
#include <utility>

struct Base { Base() noexcept; virtual int f() = 0; };

// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_constructible<Base>::value, "");

// static assertion fails, because !std::is_constructible<Base>::value:
static_assert(std::is_nothrow_default_constructible<Base>::value, "");

// invalid cast to abstract class type 'Base':
static_assert(noexcept(Base()), "");

// invalid new-expression of abstract class type 'Base'
static_assert(noexcept(new (std::declval<void *>()) Base()), "");

// cannot call constructor 'Base::Base' directly:
static_assert(noexcept(Base::Base()), "");

// invalid use of 'Base::Base':
static_assert(noexcept(std::declval<Base &>().Base()), "");

一个简单的用法是:

int g() noexcept;
struct Derived: Base {
    template <typename ... Args>
    Derived(Args && ... args)
            noexcept(noexcept(Base(std::forward<Args>(args)...)))
        : Base(std::forward<Args>(args)...)
        , m_f(g())
    {}

    int f() override;

    int m_f;
};

关于如何实现这个或者是否可以在不修改抽象基础的情况下实现这个的任何想法class?

PS:也欢迎任何对 ISO C++ 缺陷报告或正在进行的工作的引用。

编辑: 正如两次指出的那样,将 Derived 构造函数默认为 = default 会使 noexcept 被继承。但这并不能解决一般情况下的问题。

一个简单但可行的示例是引入一个非虚拟基 class 并通过 using 指令导出其构造函数。这是一个例子:

#include<utility>

struct BaseBase {
    BaseBase() noexcept { }
};

struct Base: public BaseBase {
    using BaseBase::BaseBase;
    virtual int f() = 0;
};

struct Derived: public Base {
    template <typename ... Args>
    Derived(Args && ... args)
        noexcept(noexcept(BaseBase(std::forward<Args>(args)...)))
        : Base(std::forward<Args>(args)...)
    { }

    int f() override { }
};

int main() {
    Derived d;
    d.f();
}

[更新:值得跳到编辑部分]

好的,我找到了一个解决方案,尽管由于 GCC 中的一个错误,它不能与所有编译器一起编译(有关详细信息,请参阅 )。

解决方案基于继承的构造函数和解析函数调用的方式。
考虑以下示例:

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y} { }
    virtual void f() = 0;
    int x;
};

struct D: public B {
private:
    using B::B;

public:
    template<typename... Args>
    D(Args... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    { }

    void f() override { std::cout << x << std::endl; }
};

int main() {
    B *b = new D{42};
    b->f();
}

我想已经很清楚了。
无论如何,如果您发现某些内容需要更多详细信息,请告诉我,我很乐意更新答案。
基本思想是我们可以直接从基础 class 继承 noexcept 定义及其构造函数,这样我们就不必再在 [=13= 中引用那个 class ] 语句。

Here你可以看到上面提到的工作示例。

[编辑]

根据评论,如果基 class 的构造函数和派生的构造函数具有相同的签名,则该示例会出现问题。
感谢 Piotr Skotnicki 指出。
我将提及这些评论,我将复制并粘贴与它们一起提出的代码(在需要时提及作者)。

首先,here 我们可以看到示例没有按预期工作(感谢 Piotr Skotnicki 的 link)。
该代码与之前发布的代码几乎相同,因此不值得将其复制并粘贴到此处。
此外,来自同一作者,它遵循一个示例 that shows that the same solution works as expected under certain circumstances(有关更多详细信息,请参阅评论):

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y}
    {
        std::cout << "B: Am I actually called?\n";
    }
    virtual void f() = 0;
    int x;
};

struct D: private B {
private:
    using B::B;

public:
    template<typename... Args>
    D(int a, Args&&... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    {
        std::cout << "D: Am I actually called?\n";
    }
    void f() override { std::cout << x << std::endl; }
};

int main()
{
    D* d = new D{71, 42};
    (void)d;
}

此外,我提出了一个更具侵入性的替代解决方案,它基于 std::allocator_arg_t 所代表的想法,这也与 Piotr Skotnicki 在评论中提出的相同:

it's enough if derived class' constructor wins in overload resolution.

它遵循提到的代码 here:

#include <utility>
#include <iostream>

struct B {
    B(int y) noexcept: x{y}
    {
        std::cout << "B: Am I actually called?\n";
    }
    virtual void f() = 0;
    int x;
};

struct D: public B {
private:
    using B::B;

public:
    struct D_tag { };

    template<typename... Args>
    D(D_tag, Args&&... args)
    noexcept(noexcept(D{std::forward<Args>(args)...}))
        : B{std::forward<Args>(args)...}
    {
        std::cout << "D: Am I actually called?\n";
    }
    void f() override { std::cout << x << std::endl; }
};

int main()
{
    D* d = new D{D::D_tag{}, 42};
    (void)d;
}

再次感谢 Piotr Skotnicki 的帮助和评论,非常感谢。

基于 一个不需要更改 Derived 构造函数签名的更好的解决方案是将 Base 的模拟子类定义为 Base 的私有类型成员=11=],并使用 Derived 构造函数 noexcept 规范中的构造:

class Derived: Base {

private:

    struct MockDerived: Base {
        using Base::Base;

        // Override all pure virtual methods with dummy implementations:
        int f() override; // No definition required
    };

public:

    template <typename ... Args>
    Derived(Args && ... args)
            noexcept(noexcept(MockDerived(std::forward<Args>(args)...)))
        : Base(std::forward<Args>(args)...)
        , m_f(g())
    {}

    int f() override { return 42; } // Real implementation

    int m_f;

};

我遇到了同样的问题,我找到的解决方案是实施附加特征。

你可以看看我的post .