2016-03-03 42 views
18

在C++ 11及更高版本中,如何确定抽象基类的构造函数是否为noexcept?下面的方法不起作用:确定抽象基类的构造函数是否为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; 
}; 

有关如何archieve这还是有可能的任何想法都无需修改的抽象基类? PS:所有对ISO C++缺陷报告或正在进行的工作的引用也是受欢迎的。

编辑:正如指出的两次,= default使得noexcept拖欠Derived构造继承。但这并不能解决一般情况下的问题。

+0

如果你知道抽象方法,你可以像[this]一样做(http://coliru.stacked-crooked.com/a/99ee522df4a1c7c7)。如果你想要一个完全通用的解决方案,我认为你运气不好 – sp2danny

回答

4

基于skypjack's answer一个更好的解决方案,它不需要Derived构造的签名更改是定义的Base一个模拟的子类为Derived私有类型的成员,并且使用的是建设在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; 

}; 
+1

好吧,我也探讨了这个解决方案,这里的问题是,只要你有一个虚拟方法就很容易做到,但是当它们数量增加时它很烦人,因为你必须定义一个新的完全无用的类来仅与'noexcept'子句一起使用。无论如何,upvoted因为它仍然是一个可行的解决方案。 – skypjack

+0

是的,但我认为保持'Derived'构造函数签名不变和干净是一个很好的权衡。如果“派生”的公共接口会发生变化(更糟糕的情况),那么将所有构造函数调用改为考虑此变通办法可能会很乏味。 – jotik

+3

你可以做的另一件事是,不要为虚拟函数提供实现,甚至虚拟实现。对于未评估的上下文,他们只需要声明 - 如果完全没有定义,可能更好。 –

1

一个天真但工作的例子是引入一个非虚拟基类,并通过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(); 
} 
+1

这个算作“修改抽象基类”,但可能是某些情况下的解决方法。以前的'Base'构造函数依赖的所有字段和功能必须包含在'BaseBase'构造函数或类中,并且依赖于纯虚拟方法的所有内容都必须保留在'Base'中。如果我们认为纯粹的方法称为坏事,那么看起来这种重构实际上是有用的。但在一般情况下它仍然没有帮助。我会尽力为你付出努力。 – jotik

+0

@jotik你是对的,绝对,这就是为什么我说*天真,但工作*。 ;-) ...无论如何,真的是一个有趣的问题,我仍然试图找出如何去做。 – skypjack

+0

@jotik我添加了另一个更合适的答案,也许你会发现它不是一种解决方法。让我知道它是否解决了这个问题。确实是一个很好的问题。 – skypjack

7

[更新:这是值得跳到编辑 SECTION]

好吧,我找到了解决办法,即使它并不适用于所有的编译器,因为编译GCC中的错误(详情请参阅this question)。

该解决方案基于继承的构造函数以及解析函数调用的方式。
请看下面的例子:

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

我想这是很清楚的。
无论如何,让我知道如果你发现需要更多细节,我会很乐意更新答案。
基本思想是我们可以直接继承基类的noexcept定义以及它的构造函数,所以我们不再需要在noexcept语句中引用那个类。

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

[编辑]

如从评论,示例中的问题的遭受如果基类和派生一个的构造具有相同的签名。
感谢Piotr Skotnicki指出了它。
我要提及这些评论,我将复制并粘贴与他们一起提出的代码(在需要时提及作者)。

首先,here我们可以看到,这个例子并不像预期的那样工作(感谢Piotr Skotnicki的链接)。
该代码与以前发布的代码几乎相同,因此不值得将其复制并粘贴到此处。
此外,同一作者,它遵循一个例子that shows that the same solution works as expected under certain circumstances(见furter详情评论):

#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代表,这也是在评论由彼得·Skotnicki提出的一样:如果派生类的构造函数胜在重载

这是不够的。

它下面的代码中提到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; 
} 

由于再次向彼得·Skotnicki对他的帮助和意见,非常感谢。

+1

http://coliru.stacked-crooked.com/a/fe0fb0d62f8d2b95 –

+0

@PiotrSkotnicki非常聪明,比我肯定。 :-) ...凯斯利错过了打破一切的“公共”。那么,在等公交车上班时,我用编译器在编程器上编写了程序,并感到羞愧,因为在工作中我没有检查过两次。抱歉。无论如何,也[那](http://coliru.stacked-crooked.com/a/8b55cca1ee3d1176)不能按预期工作,但应该是'D(int)'不可达? – skypjack

+0

问题在于,您的解决方案是侵入性的,因为它将基类的构造函数导入派生类的范围,因此它们参与重载解析,这可能会带来意想不到的结果。除此之外,[你的解决方案适用于其他案件](http://coliru.stacked-crooked.com/a/7a7b48ef686cdc2a) –

0

我正面临同样的问题,我发现的解决方案是实现其他特性。

您可以看看我的帖子here