2016-03-03 59 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