繁体   English   中英

确保从父CRTP类派生的类实现功能

[英]Ensure that class derived from parent CRTP class implements function

简要:

我想确保派生类实现父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 );
};

目的是我可以让多个Imp子类在myFunc都做不同的事情,并接受它们自己的必需参数。 然后,由Base提供的接口可被更高级别的函数使用,这些函数仅需要具有类型为Base::ParamsBase的指针/引用。 我的问题是确保任何Imp提供专门的myFunc 为了避免无限递归, Imp必须实现myFunc

我的第一次尝试是向CRTP添加纯虚函数

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

但这不起作用,因为在定义CRTP时还没有完全定义Imp 这个问题使用了static_assert ,这让我想到了对CRTP::myFuncstatic_assert进行相同的操作。 除非我不确定非静态函数的静态断言中的表达式是什么。

  1. 我可以根据需要使用static_assert吗?
  2. 这是确保派生类具有所需功能的最佳/最简洁方法吗?
  3. 我是否对我的课堂设计感到厌烦,并且有更好的做事方法?

谢谢。

为什么不只为函数使用其他名称? 那么对于没有和实现的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

您可以打破动态多态性并切换到静态多态性:

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

但是,我的看法是:基类设计是失败的,上面的代码只是解决方法。

对派生类的成员使用其他名称的想法是很好的(如Rudolfs Bundulis的回答)。 但是,我会将其设置为protected方法,以使用户不会尝试使用它。

另外,在CRTP基址中使用Derived::Params会带来更多的复杂性(因为在使用CRTP<Imp>未完全声明Derived=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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM