带有受保护派生成员的CRTP

时间:2011-12-15 16:57:49

标签: c++ crtp

CRTP pattern中,如果我们想要将派生类中的实现函数保持为受保护,我们会遇到问题。我们必须将基类声明为派生类的朋友或使用something like this(我没有尝试过链接文章的方法)。是否有其他(简单)方法允许将派生类中的实现函数保持为受保护状态?

编辑:这是一个简单的代码示例:

template<class D> 
class C {
public:
    void base_foo()
    {
        static_cast<D*>(this)->foo();
    }
};


class D:  public C<D> {
protected: //ERROR!
    void foo() {
    }   
};

int main() {
    D d;
    d.base_foo();
    return 0;
}

上面的代码为error: ‘void D::foo()’ is protected提供了g ++ 4.5.1,但如果protectedpublic替换,则会进行编译。

3 个答案:

答案 0 :(得分:28)

这根本不是问题,可以在派生类中使用一行来解决:

friend class Base< Derived >;

#include <iostream>

template< typename PDerived >
class TBase
{
 public:
  void Foo( void )
  {
   static_cast< PDerived* > ( this )->Bar();
  }
};

class TDerived : public TBase< TDerived >
{
  friend class TBase< TDerived > ;
 protected:
  void Bar( void )
  {
   std::cout << "in Bar" << std::endl;
  }
};

int main( void )
{
 TDerived lD;

 lD.Foo();

 return ( 0 );
}

答案 1 :(得分:0)

按照lapk的建议,可以通过简单的朋友类声明来解决问题:

class D:  public C<D> {
    friend class C<D>;      // friend class declaration
protected:
    void foo() {
    }
};

但是,它公开了派生类的所有受保护/私有成员,并且需要每个派生类声明的自定义代码。

以下解决方案基于linked article

template<class D>
class C {
public:
    void base_foo() { Accessor::base_foo(derived()); }
    int base_bar()  { return Accessor::base_bar(derived()); }

private:
    D& derived() { return *(D*)this; }

    // accessor functions for protected functions in derived class
    struct Accessor : D
    {
        static void base_foo(D& derived) {
            void (D::*fn)() = &Accessor::foo;
            (derived.*fn)();
        }
        static int base_bar(D& derived) {
            int (D::*fn)() = &Accessor::bar;
            return (derived.*fn)();
        }
    };
};

class D : public C<D> {
protected: // Success!
    void foo() {}
    int bar() { return 42; }
};

int main(int argc, char *argv[])
{
    D d;
    d.base_foo();
    int n = d.base_bar();
    return 0;
}

PS:如果您不信任编译器来优化引用,则可以将derived()函数替换为以下#define(使用MSVC 2013可使反汇编代码行减少20%) ):

    int base_bar() { return Accessor::base_bar(_instance_ref); }

    private:
    #define _instance_ref *static_cast<D*>(this)   //D& derived() { return *(D*)this; }

答案 2 :(得分:0)

经过一些讨论,我想到了一个解决方案,该解决方案适用于模板化派生类的 private 成员。它没有解决不将派生类的所有成员都暴露给基数的问题,因为它在整个类上使用了friend声明。另一方面,对于简单的情况,这不需要重复基本名称,也不需要模板参数,并且将始终有效。

首先,当派生为非模板时的简单情况。底座使用了额外的void模板参数,只是为了表明在底座的额外模板参数的情况下一切仍然有效。根据CRTP,唯一需要的是typename Derived

//Templated variadic base
template <typename Derived, typename...>
struct Interface
{
    using CRTP = Interface; //Magic!
    void f() { static_cast<Derived*>(this)->f(); }
};

//Simple usage of the base with extra types
//This can only be used when the derived is NON templated
class A : public Interface<A, void>
{
    friend CRTP;
    void f() {}
};

要做到这一点,唯一需要做的就是基数中的using CRTP = Interface;声明和派生中的friend CRTP;声明。

对于派生本身是模板的情况,情况更加棘手。我花了一些时间才找到解决方案,但我确信它仍然不完美。

大多数魔术发生在这些模板内:

namespace CRTP
{
    template <template <typename, typename...> class _Base, typename _Derived, typename... _BaseArgs>
    struct Friend { using Base = _Base<_Derived, _BaseArgs...>; };

    template <template <typename, typename...> class _Base, typename ..._BaseArgs>
    struct Base
    {
        template <template <typename...> class _Derived, typename... _DerivedArgs>
        struct Derived : public _Base<_Derived<_DerivedArgs...>, _BaseArgs...> {};
    };
}

它们的用法或多或少简单明了。两个人使用上述模板需要几个步骤。

首先,在派生类中继承继承自的基类及其可选参数时,需要提供这些信息。这是通过CRTP::Base<MyBase, BaseOptional....>完成的,其中MyBase是用于CRTP的类的名称,而BaseOptional...是模板参数,在传递完后立即按原样传递给基类下一步提供的我们的派生类。当基类不接受任何其他模板参数时,可以将它们完全省略:CRTP::Base<MyBase>

下一步是引入派生类(CRTP的全部内容)。这是通过在上述CRTP::Base<...>之后加上::Derived<ThisDerived, DerivedOptional...>来完成的。其中ThisDerived是定义该类的类,而DerivedOptional...是该类的template声明中声明的模板参数 all 。可选参数完全被指定,就像它们在类template声明中一样。

最后一步是将基类声明为friend。这是通过在类中的某个地方声明friend typename CRTP::Friend<MyBase, ThisDerived, BaseOptional...>::Base来完成的。 BaseOptional...模板性能必须与继承自CRTP::Base<MyBase, BaseOptional...>的外观完全相同。

以下是使用不依赖于模板类型的基础而派生的模板的示例(但它仍可以采用其他模板参数,在此示例中为void)。

//Templated derived with extra, non-dependant types, passed to the base
//The arguments passed to CRTP::Base::Derived<, ARGS> must exactly match
//  the template
template <typename T, typename... Args>
class B : public CRTP::Base<Interface, void>::Derived<B, T, Args...>
{
    friend typename CRTP::Friend<Interface, B, void>::Base;
    void f() {}
};

接下来是一个示例,该示例说明了基础是否依赖于派生的模板参数。与上一个示例的唯一区别是template关键字。实验表明,如果为以前的非依赖性关键字指定了关键字,则代码也可以完全符合要求。

//Templated derived with extra dependant types passed to the base
//Notice the addition of the "template" keyword
template <typename... Args>
class C : public CRTP::Base<Interface, Args...>::template Derived<C, Args...>
{
    friend typename CRTP::Friend<Interface, C, Args...>::Base;
    void f() {}
};

请注意,这些模板对非模板派生类起作用。找到解决方案后,我将更新此答案,因此可以在所有情况下使用统一的语法。可以做的最接近的事情就是使用一些伪造的模板参数。请注意,它仍然必须被命名并传递给CRTP机制。例如:

template <typename Fake = void>
class D : public CRTP::Base<Interface>::Derived<D, Fake>
{
    friend typename CRTP::Friend<Interface, D>::Base;
    void f() {}
};

请注意,ABCD被声明为class。也就是说,他们的所有成员都是私有

以下是使用上述类的一些代码。

template <typename... Args>
void invoke(Interface<Args...> & base)
{
    base.f();
}

int main(int, char *[])
{
    {
        A derived;

        //Direct invocation through cast to base (derived.f() is private)
        static_cast<A::CRTP &>(derived).f();

        //Invocation through template function accepting the base
        invoke(derived);
    }

    {
        B<int> derived;
        static_cast<B<int>::CRTP &>(derived).f();
        invoke(derived);
    }

    {
        C<void> derived;
        static_cast<C<void>::CRTP &>(derived).f();
        invoke(derived);
    }

    {
        D<void> derived;
        static_cast<D<>::CRTP &>(derived).f();
        invoke(derived);
    }

    return 0;
}

invoke独立的模板化函数适用于从基础派生的任何类。 还显示了如何将派生对象转换为基础,而无需实际指定基础名称。 令人惊讶的是,这不依赖于任何系统头文件。

完整代码可在此处找到:https://gist.github.com/equilibr/b27524468a0519aad37abc060cb8bc2b

欢迎评论和更正。