确定抽象基类的构造函数是否为noexcept?

jot*_*tik 19 c++ noexcept c++11 c++14 c++17

在C++ 11及更高版本中,如何确定抽象基类的构造函数是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()), "");
Run Code Online (Sandbox Code Playgroud)

一个简单的用途是:

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;
};
Run Code Online (Sandbox Code Playgroud)

关于如何在没有修改抽象基类的情况下实现这个或者是否可能的任何想法?

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

编辑:正如指出的两倍,拖欠Derived与构造= default品牌noexcept继承.但这并不能解决一般情况下的问题.

sky*_*ack 7

[更新:它非常值得编辑部分]

好吧,我发现了一个解决方案,即使它没有与所有编译器一起编译,因为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();
}
Run Code Online (Sandbox Code Playgroud)

我想这很清楚.
无论如何,如果你发现某些东西需要更多细节,请告诉我,我很乐意更新答案.
基本思想是我们可以直接noexcept从基类继承它的构造函数,以便我们不再在noexcept语句中引用该类.

在这里您可以看到上面提到的工作示例.

[编辑]

从注释开始,如果基类的构造函数和派生类的构造函数具有相同的签名,则该示例会遇到问题.
感谢Piotr Skotnicki指出了这一点.
我将提及这些评论,我将复制并粘贴与它们一起提出的代码(在必要时提及作者).

首先,在这里我们可以看到这个例子不能按预期工作(感谢Piotr Skotnicki的链接).
代码几乎与以前发布的相同,因此不值得在此处复制和粘贴它.
此外,来自同一作者,它遵循一个示例,显示在某些情况下相同的解决方案按预期工作(请参阅有关更多详细信息的注释):

#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;
}
Run Code Online (Sandbox Code Playgroud)

此外,我提出了一个更具侵入性的替代解决方案,并且基于std::allocator_arg_tPiotr Skotnicki在评论中提出的相同的想法:

如果派生类的构造函数在重载决策中获胜,那就足够了.

它遵循这里提到的代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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


jot*_*tik 5

根据skypjack 的答案,不需要更改构造函数签名的更好解决方案Derived是将 的模拟子类定义Base为 的私有类型成员,并在构造函数规范Derived中使用该构造函数的构造:Derivednoexcept

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;

};
Run Code Online (Sandbox Code Playgroud)

  • 您可以做的另一件事是,根本不用费心提供虚拟函数的实现,甚至是虚拟实现。为了未评估的上下文,它们只需要被声明——如果根本不定义它们可能会更好。 (3认同)