在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,但如果protected
被public
替换,则会进行编译。
答案 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() {}
};
请注意,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
欢迎评论和更正。