2015-09-02 67 views
10

Brief:確保派生自父類CRTP類的類實現函數

我想確保派生類實現父CRTP類中函數所需的成員函數。

詳細信息:

我有這樣

class Base 
{ 
public: 
    class Params 
    { 
    public: 
     virtual ~Params() {} 
    }; 

    virtual void myFunc(Params& p) = 0; 
}; 

template< typename T > 
class CRTP : public Base 
{ 
public: 
    virtual void myFunc(Base::Params& p) override 
    { 
     typename T::Params& typedParams = dynamic_cast<typename T::Params&>(p); 
     static_cast<T*>(this)->myFunc(typeParams); 
    } 

}; 

class Imp : public CRTP<Imp> 
{ 
public: 
    class Params : public CRTP<Imp>::Params 
    { 
    public: 
     virtual ~Params() {} 

     int x, y, z; 
    }; 

    virtual void myFunc(Imp::Params& p); 
}; 

意圖一些代碼是,我可以在myFunc多個Imp子類都在做不同的事情,並接受自己所需的參數。由Base提供的接口然後被更高級別的功能使用,其僅需要具有類型爲Base::ParamsBase的指針/參考。我的問題是確保任何Imp提供專門的myFunc。爲避免無限遞歸,Imp必須實現myFunc

我的第一次嘗試是將純虛函數CRTP

virtual void myFunc(typename T::Params& p) = 0; 

Imp尚未完全時,被定義CRTP定義不起作用。 This question使用static_assert,這讓我想到CRTP::myFunc中的static_assert也是這樣。除非我不確定靜態斷言中對於非靜態函數應該是什麼表達式。

  1. 我可以使用static_assert來滿足需要嗎?
  2. 這是確保派生類具有所需功能的最佳/最乾淨的方法嗎?
  3. 我有沒有被我的班級設計帶走,還有更好的做事方式?

謝謝。

+0

難道你不能用一些SFINAE魔法來確定'Imp :: Param'是否與'Base :: Param'不同,並且'Imp :: myFunc()'將'Imp :: Param'作爲參數? – Walter

+0

@沃爾特第二,如果有繼承,它會有誤報。首先,你可能不想要這樣做。 – Yakk

+1

另外:爲什麼你從非''constst'方法靜態強制轉換爲'T const *'?在'Base'和'CRTP'中'myFunc'應該是'const',或者你應該在'CRTP'實現中調用'static_cast '。 – Yakk

回答

5

爲什麼不爲該功能使用不同的名稱?然後,在沒有和實現的情況下,每個派生類CRTP都會出現編譯錯誤。試想一下:

class Base 
{ 
public: 
    class Params 
    { 
    public: 
     virtual ~Params() {} 
    }; 

    virtual void myFunc(Params& p) = 0; 
}; 

template< typename T > 
class CRTP : public Base 
{ 
public: 
    virtual void myFunc(Base::Params& p) final override 
    { 
     typename T::Params& typedParams = dynamic_cast<typename T::Params&>(p); 
     static_cast<const T*>(this)->myFuncImp(typedParams); 
    } 

}; 

class Imp : public CRTP<Imp> 
{ 
public: 
    class Params : public CRTP<Imp>::Params 
    { 
    public: 
     virtual ~Params() {} 

     int x, y, z; 
    }; 
}; 

int main(int argc, char** argv) 
{ 
    Imp imp; 
} 

編譯失敗,因爲沒有通過Imp提供myFuncImp

+2

在'CRTP'中將'final'添加到'myFunc'以避免混淆。 – Yakk

+1

@Walter我可能誤解了,但是如果'myFunc'調用一個專門的'myFuncImp',這有什麼不同?調用'Base :: myFunc'仍然會歸結爲正確的'CRTP <> :: myFuncImp'? –

+0

@Yakk現場,讓我編輯。 –

0

爲派生類成員使用不同名稱的想法(如在Rudolfs Bundulis答案中)是很好的。但是,我會將此作爲protected方法,以便用戶不會嘗試使用它。

此外,在CRTP基地使用Derived::Params引發額外的複雜性(因爲Derived=Imp沒有完全在CRTP<Imp>其使用點聲明的),所以最好保持Base::Params作爲整個函數參數。

struct Base           // public user interface 
{ 
    struct Params { virtual ~Params() {} }; 
    virtual void myFunc(Params&) = 0; 
}; 

namespace details {         // deter clients from direct access 
    template< typename Derived > 
    struct CRTP : Base 
    { 
    virtual void myFunc(Params& p) final   // cannot be overridden 
    { 
     static_cast<Derived*>(this)->myFuncImp(p); 
    } 
    }; 

    class Imp : public CRTP<Imp> 
    { 
    struct Params : CRTP<Imp>::Params { int x, y, z; }; 
    void myFuncImpDetails(Params*); 
    protected:           // protected from clients 
    void myFuncImp(Base::Params& p) 
    { 
     auto pars=dynamic_cast<Params*>(&p); 
     if(pars) 
     myFuncImpDetails(pars); 
     else 
     throw std::runtime_error("invalid parameter type provided to Imp::myFunc()"); 
    } 
    }; 
} // namespace details 
+0

「unwieldy dynamic_cast <>」是相當需要的,因爲無法知道傳遞的p是否是正確的類型。 CRTP的要點是避免複製(多個)Imp類中的動態轉換。 – user2746401

+0

哦,並且使該功能受保護(自行)不起作用,因爲CRTP 無法訪問Imp中的私有/受保護成員。但是,我添加了CRTP 作爲Imp的朋友來解決這個問題。 – user2746401

1

您可能打破動態的多態性,並切換到靜態多態性:

#include <iostream> 
#include <type_traits> 

class Base 
{ 
    public: 
    class Params 
    { 
     public: 
     virtual ~Params() {} 
    }; 

    virtual ~Base() {} 
    virtual void myFunc(Params& p) = 0; 
}; 


namespace Detail { 
    // Helper for the static assertion 
    // Omit this if "‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private" is good enough 
    struct is_MyFunc_callable_implementation 
    { 
     template<typename Object, typename Params> 
     static decltype(std::declval<Object>().myFunc(std::declval<Params&>()), std::true_type()) 
     test(int); 

     template<typename Object, typename Params> 
     static std::false_type 
     test(...); 
    }; 

    template<typename Object, typename... A> 
    using is_MyFunc_callable = decltype(is_MyFunc_callable_implementation::test<Object, A...>(0)); 

    // Helper function to break recursion 
    template<typename Object, typename Params> 
    inline void invokeMyFunc(Object& object, Params& params) { 
     static_assert(is_MyFunc_callable<Object, Params>::value, "The derived class is missing 'MyFunc'"); 
     object.myFunc(params); 
    } 
} // namespace Detail 

template<typename T> 
class CRTP: public Base 
{ 
    private: 
    // Make this final! 
    virtual void myFunc(Base::Params& p) override final 
    { 
     static_assert(std::is_base_of<Base, T>::value, "T must derive from CRTP"); 
     typename T::Params& typeParams = dynamic_cast<typename T::Params&>(p); 
     Detail::invokeMyFunc(static_cast<T&>(*this), typeParams); 
    } 
}; 

class Imp: public CRTP<Imp> 
{ 
    public: 
    class Params: public CRTP<Imp>::Params 
    { 
     public: 
     int x = 1; 
     int y = 2; 
     int z = 3; 
    }; 

    // Without this function: 
    // error: static assertion failed: The derived class is missing 'MyFunc' 
    // error: ‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private 
    #if 0 
    void myFunc(Params& p) { 
     std::cout << p.x << p.y << p.z << '\n'; 
    } 
    #endif 
}; 

int main() 
{ 
    Imp imp; 
    Base* base = &imp; 
    Imp::Params params; 
    base->myFunc(params); 
} 

但是,我的看法是:基類的設計是失敗的和上面的代碼只是一個變通。

+0

你能詳細說明基類設計的「失敗」嗎?如何改進? – user2746401

+0

@ user2746401我不想像這樣的用例。最接近的是事件處理,但不應該切換到靜態多態。 –