今天来聊聊 C++ Eigen 中的一个技巧——奇异递归模板模式(curiously recurring template pattern),简写为CRTP。
这篇博客也是我边看、边查、边理解的一个过程。如有错误,希望大神留言指正。
原来已经稍微接扫了几眼这个技术,在我粗浅的认识中,他在编译期通过模板,实现了大家用的比较多的运行时多态。我们来看看这个代码:
class Base
{
public:
virtual void Do() = 0;
};
class Derived_1 : public Base
{
public:
virtual void Do() override
{
cout << "DoWork 1" << endl;
}
};
class Derived_2 : public Base
{
public:
virtual void Do() override
{
cout << "DoWork 2" << endl;
}
};
Base* p = new Derived_1();
p->Do();// output "DoWork 1"
p = new Derived_2();
p->Do();// output "DoWork 2"
众所周知,运行时多态的类保存了一个虚函数表,运行时通过查表确定具体调用的成员函数实现多态。增加了内存开销及CPU时间开销。那么通过模板,我们可以这样:
template<typename Derived>
class Base
{
public:
void Do()
{
static_cast(this)->Do_imp();
}
};
class Derived_1 : public Base
{
public:
void Do_imp()
{
cout << "DoWork 1" << endl;
}
};
class Derived_2 : public Base
{
public:
void Do_imp()
{
cout << "DoWork 2" << endl;
}
};
Derived_1* p1 = new Derived_1();
p1->Do();// output "DoWork 1"
Derived_2* p2 = new Derived_2();
p2->Do();// output "DoWork 2"
感觉跟运行时多态有那么一点点不太一样,但是问题不大,至少最后都是调用 Do 接口来获得了不同的性状。我们可以看到这样避免了虚函数的开销。
但这里有一个问题,我们的子类的实现 是一个 Public 的,这样破坏了类的封装性。我们其实并不想让用户看到我们 Do 函数的实现细节。
那么简单的想法是在子类中添加父类的友元。但这样在父类很多的时候照样很麻烦。
为了解决这样一个问题,我们引入一个新的类继承于Derived.
template<typename Derived>
class Base
{
public:
void Do()
{
trait::Do(derived());
}
Derived& derived() { return static_cast(*this); }
private:
struct trait : public Derived
{
static void Do(Derived& d)
{
auto funcp = &Derived::Do_imp;
return (d.*funcp)();
}
};
};
class Derived_1 : public Base
{
protected:
void Do_imp()
{
cout << "DoWork 1" << endl;
}
};
class Derived_2 : public Base
{
protected:
void Do_imp()
{
cout << "DoWork 2" << endl;
}
};
这样 , 就可以在 Do 接口中调用 Derived 类中的 protected 函数了。