c++ Template CRTP
Better Encapsulation for the Curiously Recurring Template
Pattern
使用CRTP做更好的封装
长久以来,C++一直突出于优秀的技巧和典范。老有名气的一个就是James
Coplien在1995年提出的奇异递归模板模式(CRTP)。自那以后,CRTP便开始流行并在多个库中使用,尤其是Boost。例如,你可以在Boost.Iterator,Boost.Python或者Boost.Serialization库中看到他们。
在这篇文章中,我假设读者已经熟悉了CRTP。如果你想温习一下的话,我推荐你去阅读《C++模板编程》的第17章。在www.上,你可以找到该章节的免费版本。
如果你抱着OO的观点去看CRTP的话,你会发现,他和OO框架的有着共同的特点,都是基类调用虚函数,
真正的实现在派生类中。下面是一个最简单的OO框架实现代码:
// Library code
class Base
{
public:
virtual
~Base();
int
foo() { return this->do_foo(); }
protected:
virtual
int do_foo() = 0;
};
这里,Base::foo调用了一个虚函数do_foo,他是声明在Base类中的一个纯虚函数,而且他必须在基类中实现。也就是说,do_foo的实体出现在Derived类中。
// User code
class Derived : public Base
{
private:
virtual
int do_foo() { return 0; }
};
这里有个有意思的地方是do_foo函数必须将访问符从保护修改成私有。这在C++中是比较好的访问控制,同时实现它只需要键入几个简单的字符。为什么要
在这里有意强调do_foo不是共有使用呢?理由是一个用户应该尽力隐藏类的实现细节从而使类更加简单。(用户如果觉得这个类没有对外暴露的价值,甚至应
该隐藏整个Derived类)。
现在让我们假设,有一些限制性的因素导致virtual函数不能胜任,同时框架的作者决定使用CRTP。
// Library code
template<class DerivedT>
class Base
{
public:
DerivedT& derived()
{
return
static_cast<DerivedT&>(*this);
}
int foo()
{
return this->derived().do_foo();
}
};
// User code
class Derived : public
Base<Derived>
{
public:
int
do_foo()
{
return 0;
}
};
尽管do_foo是同一个实现,但是它可以被任意访问。为什么不将它设置为私有或者保护?答案是在foo函数中调用了Derived::do_foo,或者说,基类直接调用了一个在派生类中的函数。
现在让我们找一个最简单方法,对于Derived的用户隐藏其实现细节。他应该足够简单,否则,用户将不会使用它。对于Base类的作者,这个稍微有些麻烦,但也应该是不难解决的。
最显而易见的方法是在Base类和Derived类之间建立一个友谊关系。
// User code
class Derived : public
Base<Derived>
{
private:
friend
class Base<Derived>;
int
do_foo() { return 0; }
};
这个解决方案并不是很完美,只因为一个简单的理由:每一个Base的模板参数类,都要定义一个friend声明。如果模板参数较多,那么这个声明列表将会很长。
为了解决这个问题,同时将友元列表的长度固定,我们引入一个非模板类Accessor来做一次前向调用。
// Library code
class Accessor
{
private:
template<class>
friend class Base;
template<class
DerivedT>
static
int foo(DerivedT& derived)
{
return
derived.do_foo();
}
};
函数Base::foo应该称为Accessor::foo,他用来转发调用至Derived::do_foo。
首先是这个调用链永远会成功,因为Base类是Accessor类的友元。
// Library code
template<class DerivedT>
class Base
{
public:
DerivedT&
derived() {
return
static_cast<DerivedT&>(*this);
}
int
foo()
{
return
Accessor::foo(this->derived());
}
};
其次是当do_foo为公有或者当do_foo是保护同时Accessor类是Derived类的一个友元时才会成功。我们只感兴趣第二种情况。
// User code
class Derived : public
Base<Derived>
{
private:
friend
class Accessor;
int
do_foo() { return 0; }
};
这种方法被boost的多个库使用,譬如:Boost.Python中的def_visitor_access和Boost.Iterator的
iterator_core_access都应该被声明为友元,以此来访问用户从def_visitor或者iterator_facade定义的私有函
数。
尽管这个解决方案很简单。但是我们还是会有一种方法可以省略友元声明这个列表。在这种情况下,do_foo不能是私有,你必须要把它修改成保护。这其实没
什么,因为这两者之间的访问控制差别对于CRTP的用户来说不重要。为什么呢?让我们看一下用户将如何派生于CRTP基类。
class Derived : public Base<Derived>
{ };
这里,将把最终类给模板参数列表。任何试图派生于Derived的类都没有太大意义,因为基类Base<Derived>仅仅知道Derived类,不能够定义生成Derived类的对象。
由于我们不用考虑派生问题了,那么我们现在的目标就是如何实现在Base类中访问声明为protected的函数Derived::do_foo。
// User code
class Derived : public
Base<Derived>
{
protected:
//
No friend declaration here!
int
do_foo() { return 0; }
};
|