2017-06-13 26 views
0

這裏是我的代碼,在這裏你看到的類型爲Base的對象具有接口函數Do(),我總是使用類型爲Base的對象(使用更多類型的模板)並始終調用Do()接口。因此,對於ImplBaseDummy1ImplBaseDummy2,在ImplBaseDummy1的情況下,我得到f()的具體實現ImplBaseDummy2Base的默認實現。到目前爲止,所有這些工作都很好,我有很多代碼可以按預期工作,我不想更改API,但如果你能說服我,我可能會這樣做。最近我不得不添加更多類型,如ImplBase我儘量不要複製代碼,所以我創建了一個類似的結構,希望它能繼續工作。問題是如何定義的,當執行調用的f具體實現,並使用默認的實現(從Base)在具體執行缺失,呼籲Base型(不知模板)的對象的接口功能Do()當這一切應該發生。如何驗證函數不會自行調用?

#include <iostream> 

template <class Impl> 
class Base 
{ 
public: 
void Do() { f_impl(); } 
void f() {std::cout << "Base::f" << std::endl; } 
protected: 
void f_impl() { static_cast<Impl*>(this)->f(); } 
}; 

class ImplBaseDummy1 : public Base<ImplBaseDummy1> 
{ 
}; 

class ImplBaseDummy2 : public Base<ImplBaseDummy2> 
{ 
public: 
void f() {std::cout << "ImplBaseDummy2::f" << std::endl; } 
}; 

template <class Actual> 
class ImplBase : public Base<ImplBase<Actual>> 
{ 
public: 
typedef Base<ImplBase<Actual>> Parent; 
void f() { static_cast<Actual*>(this)->f(); } 
}; 

class Derived1 : public ImplBase<Derived1> 
{ 
public: 
typedef ImplBase<Derived1> Parent; 
void f() {std::cout << "Derived1::f" << std::endl; } 
}; 

class Derived2 : public ImplBase<Derived2> 
{ 
public: 
typedef ImplBase<Derived2> Parent; 
using Parent::Parent::f; 
}; 

int main() 
{ 
    Base<ImplBaseDummy1> d01; 
    d01.Do(); 
    Base<ImplBaseDummy2> d02; 
    d02.Do(); 
    Base<ImplBase<Derived1>> d1; 
    d1.Do(); 
    Base<ImplBase<Derived2>> d2; 
    d2.Do(); 
    return 0; 
} 

此代碼編譯並按預期方式運行。我的問題關於安全機制,我想情況下實現我忘了寫在Derived2using Parent::Parent::f;(這是初步意向,在情況下,具體的實施缺少呼叫從Base默認實現)。在這種情況下發生的情況是ImplBase<Derived2>::f()無限期地調用自己,直到它崩潰(或編譯-O3優化級別時從不退出)。我想實現它類似於此

void f() { static_assert(&f != &Actual::f, "function calls itself"); static_cast<Actual*>(this)->f(); } 
void f() { static_assert(!std::is_same<decltype(f), decltype(Actual::f)>::value, "function calls itself"); static_cast<Actual*>(this)->f(); } 

但是兩個實現不編譯,任何其他的想法如何實現這個檢查?需要強調的是,運行時解決方案並不是我要找的,我最好關閉任何檢查,並在遇到問題時突然斷斷續續,而不是執行任何對Do接口的調用的額外測試。

+1

你可以使用[__PRETTY_FUNCTION__(https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html),看看哪個函數被調用,當 – Bl4ckb0ne

+0

您將無法檢測所有情況下的間接遞歸(例如,調用'foo'的函數調用調用函數的'bar')。 –

+0

@ Bl4ckb0ne,如果我可以在編譯時從ImplBase :: f和PRETTY_FUNCTION實際:: f來比較PRETTY_FUNCTION,但是我認爲它不能完成,那將是一件好事。 – e271p314

回答

2

這是不可能證明在一般再入在編譯時。

但它可以在運行時檢查:

static bool inside = false; 
assert(!inside); 
inside = true; 

// rest of function 

inside = false; 
return whatever; // if non void 

局部變量的析構函數仍然可以在技術上最終調用的函數。這可以用一個(可重複使用)RAII風格的對象部分解決:

class nonreentrant 
{ 
    bool& inside; 
    public: 
    nonreentrant(bool& inside): inside(inside) 
    { 
     assert(!this->inside); 
     this->inside = true; 
    } 
    ~nonreentrant() 
    { 
     this->inside = false; 
    } 
}; 

// usage 
static bool inside = false; 
nonreentrant guard(inside); 

// rest of function 

不幸的是,這並不與參數,它的析構函數將被運行後工作。

從技術上講,返回值所涉及的某個移動構造函數可能最終會調用該函數。無效函數和那些返回可移動對象應該是萬無一失的。

+0

運行時解決方案在這裏不起作用,我需要在編譯時驗證我的代碼,或者抓住我的機會,不使用任何驗證。我明白,通常在編譯時不可能證明重新進入,但我不是在談論一般情況,這是我想驗證的一個非常具體的實現。事實是'static_cast (this) - > f()'解決了正確的事情(在編譯時),所以我不明白我怎麼在編譯時不能驗證這個分辨率 – e271p314

1

您的代碼會執行未定義的行爲,因此無法「檢查它是否正常工作」。該標準對您的程序行爲置零要求;所有的行爲都是「正確的」。

ImplBase<Derived2>不是Derived2,所以static_cast<Derived2*>(this)->f()導致未定義的行爲。這裏的無限循環與你想要發生的一樣正確。

我已經解決了類似的問題,而沒有做出未定義的行爲。現在

template <class Impl> 
class Base { 
public: 
    void f_impl() {std::cout << "Base::f" << std::endl; } 
}; 
template <class Actual> 
class ImplBase : public Base<ImplBase<Actual>> { 
public: 
    void f() { static_cast<Actual*>(this)->f_impl(); } 
}; 

class Derived1 : public ImplBase<Derived1> { 
public: 
    void f_impl() {std::cout << "Derived1::f" << std::endl; } 
}; 

class Derived2 : public ImplBase<Derived2> { 
public: 
}; 

Derived1 d1; d1.f();Derived2 d2; d2.f();做什麼,我相信你想; d1已覆蓋f,而d2使用Base行爲。

我們從實現中分離接口,允許實現調度獨立於接口調度而存在。

+0

我不喜歡改變界面,但我可能會這樣做,如果我所做的是錯誤的。如果您不介意,我更新了我使用的代碼以進一步反映它的作用,請讓我知道如果您有另一個建議,因爲'Base'具有'Do()'功能作爲接口I' m使用哪個調用f()'所以我不得不使用這個名字 – e271p314

+0

@ e27你的代碼仍然有未定義的行爲。你不能將一個指針指向一個類型,那麼它就不會和它一起工作。 – Yakk

1

不要聲明使用Parent :: Parent :: f。 delcare一個f()顯式調用parent :: Parent :: f()。這將解決您的問題。

如果你想防止忘記了申報額外的保護,就應該保護繼承的聲明final類,如:

class Derived1 : protected ImplBase<Derived1> 
{ 
public: 
typedef ImplBase<Derived1> Parent; 
void f() {std::cout << "Derived1::f" << std::endl; } 
}; 

class Derived2 : protected ImplBase<Derived2> 
{ 
public: 
typedef ImplBase<Derived2> Parent; 
// void f() { Parent::Parent::f(); } // uncomment to avoid compile error. 
}; 

產生的錯誤信息是非常明確和點名故障類。

這段代碼很有人氣。我很難弄清楚這種繼承的用途是什麼,因爲直接直接聲明Derived1或Derived2對象類型的對象可以很好地工作,而不需要原始代碼中的ImplBase> :: f()。

與元編程有關嗎?我們非常感興趣。

下面是完整的解決方案,更改了4行代碼。

#include <iostream> 

template <class Impl> 
class Base 
{ 
public: 
    void Do() { f_impl(); } 
    void f() { std::cout << "Base::f" << std::endl; } 
protected: 
    void f_impl() { static_cast<Impl*>(this)->f(); } 
}; 

class ImplBaseDummy1 : public Base<ImplBaseDummy1> 
{ 
}; 

class ImplBaseDummy2 : public Base<ImplBaseDummy2> 
{ 
public: 
    static void f() { std::cout << "ImplBaseDummy2::f" << std::endl; } 
}; 

template <class Actual> 
class ImplBase : public Base<ImplBase<Actual>> 
{ 
public: 
    typedef Base<ImplBase<Actual>> Parent; 
    void f() { static_cast<Actual*>(this)->f(); } 
}; 

class Derived1 : protected ImplBase<Derived1> 
{ 
public: 
    typedef ImplBase<Derived1> Parent; 
    void f() { std::cout << "Derived1::f" << std::endl; } 
}; 

class Derived2 : protected ImplBase<Derived2> 
{ 
public: 
    typedef ImplBase<Derived2> Parent; 
    //using Parent::Parent::f; 
    //void f() { Parent::Parent::f(); } // uncomment to remove compile error 
}; 

int main() 
{ 
    Base<ImplBaseDummy1> d01; 
    d01.Do(); 
    Base<ImplBaseDummy2> d02; 
    d02.Do(); 
    Base<ImplBase<Derived1>> d1; 
    d1.Do(); 
    Base<ImplBase<Derived2>> d2; 
    d2.Do(); 
    return 0; 
} 
+0

問題是我會忘記在'Derived2'中實現'f'我需要一些東西來提醒我這個(編譯錯誤會很大) – e271p314

+0

我看到...讓ImplBase <> :: f()protected將生成如果你或者下一個開發者忘記了定義f(),那麼你想要的錯誤。 –

+0

@ e271p314這是什麼編譯器? –

相關問題