33

CRTP 模式中,如果我们想将派生类中的实现函数保持为受保护的状态,就会遇到问题。我们必须要么将基类声明为派生类的朋友,要么使用类似的东西(我没有尝试过链接文章中的方法)。是否有其他(简单)方法可以将派生类中的实现函数保持为受保护的状态?

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

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 给出,但如果protected被替换为public.

4

3 回答 3

35

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

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 );
}
于 2011-12-15T18:08:00.220 回答
3

正如lapk 所推荐的,问题可以通过简单的朋友类声明来解决:

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

但是,这会暴露派生类的所有受保护/私有成员,并且需要为每个派生类声明自定义代码。

以下解决方案基于链接的文章

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; }
于 2019-04-30T21:20:39.070 回答
2

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

首先是派生非模板的简单情况。base 需要一个额外的void模板参数,只是为了表明在 base 的额外模板参数的情况下一切仍然有效。根据 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。可选参数的指定与它们在类声明中出现的完全一样。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() {}
};

注意A, B, C&D被声明为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

欢迎评论和指正。

于 2019-10-17T15:16:12.730 回答