设计模式-基本概念
- 基本概念
- 奇异递归模板模式(CRTP)
- 说明
- 示例
- 例子1:对象计数
- 例子2:多态复制构造
- 例子4:std::enable_shared_from_this
- 例子5 树简单遍历
- 混合继承
- 属性
- SOLID 设计原则
- 参考
基本概念
奇异递归模板模式(CRTP)
奇异递归模板模式(curiously recurring template pattern,CRTP)是C++
模板编程时的一种惯用法(idiom
):把派生类作为基类的模板参数。
说明
静态多态
C++
语言的多态,原本是用虚函数来实现的,属于动态多态。安德烈在《Modern C++ Design》中提出了奇异递归模板模式,并称之为静态多态(static polymorphism)。
#include <iostream>
using namespace std;
template <typename Child>
struct Base {
void interface() { static_cast<Child*>(this)->implementation(); }
static void static_func() { Child::static_sub_func(); }
};
struct Derived : Base<Derived> {
void implementation() { cout << "Derived implementation\n"; }
static void static_sub_func() { cout << "child function " << endl; }
};
int main() {
Derived d;
d.interface(); // Prints "Derived implementation"
Base<Derived> m;
m.static_func();
}
运行结果
Derived implementation
child function
基类模板利用了其成员函数体(即成员函数的实现)在声明之后很久都不会被实例化(实际上只有被调用的模板类的成员函数才会被实例化),并利用了派生类的成员函数(通过类型转化)。
在上例中,Base<Derived>::interface()
,虽然是在struct Derived
之前就被声明了,但未被编译器实例化直至它被实际调用,这发生于Derived
声明之后,此时Derived::implementation()
的声明是已知的。
这种技术获得了类似于虚函数的效果,并避免了动态多态的代价。也有人把CRTP
称为“模拟的动态绑定”。
这种模式广泛用于Windows ATL
与WTL
库,以及Boost.Iterator
,Boost.Python
或者Boost.Serialization
等库中。
考虑一个基类,没有虚函数,则它的成员函数能够调用的其它成员函数,只能是属于该基类自身。当从这个基类派生其它类时,派生类继承了所有未被覆盖(overridden
)的基类的数据成员与成员函数 。如果派生类调用了一个被继承的基类的函数**,而该函数又调用了其它成员函数,这些成员函数不可能是派生类中的派生或者覆盖的成员函数。也就是说,基类中是看不到派生类的。但是,基类如果使用了CRTP
,则在编译时派生类的覆盖的函数可被选中调用。这效果相当于编译时模拟了虚函数调用但避免了虚函数的尺寸与调用开销(VTBL
结构与方法查找、多继承机制)等代价。但CRTP
的缺点是不能在运行时做出动态绑定。
不通过虚函数机制,基类访问派生类的私有或保护成员,需要把基类声明为派生类的友元(friend
)。如果一个类有多个基类都出现这种需求,声明多个基类都是友元会很麻烦。一种解决技巧是在派生类之上再派生一个accessor
类,显然accessor
类有权访问派生类的保护函数;如果基类有权访问accessor
类,就可以间接调用派生类的保护成员了。这种方法被boost
的多个库使用,如:Boost.Python
中的def_visitor_access
和Boost.Iterator的iterator_core_access
。原理示例代码如下:
template<class DerivedT> class Base {
private:
struct accessor : DerivedT { // accessor类没有数据成员,只有一些静态成员函数
static int foo(DerivedT& derived) {
int (DerivedT::*fn)() = &DeriveT::do_foo; //获取DerivedT::do_foo的成员函数指针
return (derived.*fn)(); // 通过成员函数指针的函数调用
}
}; // accessor类仅是Base类的成员类型,而没有实例化为Base类的数据成员。
public:
DerivedT& derived() {// 该成员函数返回派生类的实例的引用
return static_cast<DerivedT&>(*this);
}
int foo() {// 该函数具体实现了业务功能
return accessor::foo( this->derived());
}
};
struct Derived : Base<Derived> { // 派生类不需要任何特别的友元声明
protected:
int do_foo() {
// ... 具体实现
return 1;
}
};
示例
例子1:对象计数
统计一个类的实例对象创建与析构的数据。可以轻松地利用CRTP实现:
template <typename T>
struct counter
{
static int objects_created;
static int objects_alive;
counter()
{
++objects_created;
++objects_alive;
}
counter(const counter&)
{
++objects_created;
++objects_alive;
}
protected:
~counter() // objects should never be removed through pointers of this type
{
--objects_alive;
}
};
template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );
class X : counter<X>
{
// ...
};
class Y : counter<Y>
{
// ...
};
例子2:多态复制构造
当使用多态时,常需要基于基类指针创建对象的一份拷贝。常见办法是增加clone虚函数在每一个派生类中。使用CRTP,可以避免在派生类中增加这样的虚函数。
// Base class has a pure virtual function for cloning
class Shape {
public:
virtual ~Shape() {}
virtual Shape *clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape {
public:
virtual Shape *clone() const {
return new Derived(static_cast<Derived const&>(*this));
}
};
// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP<Type>
// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};
This allows obtaining copies of squares, circles or any other shapes by shapePtr->clone().
例子3:不可派生的类
一个类如果不希望被继承,类似于Java中的具有finally性质的类,这在C++中可以用虚继承来实现:
template<typename T> class MakeFinally{
private:
MakeFinally(){}//只有MakeFinally的友类才可以构造MakeFinally
~MakeFinally(){}
friend T;
};
class MyClass:public virtual MakeFinally<MyClass>{};//MyClass是不可派生类
//由于虚继承,所以D要直接负责构造MakeFinally类,从而导致编译报错,所以D作为派生类是不合法的。
class D: public MyClass{};
//另外,如果D类没有实例化对象,即没有被使用,实际上D类是被编译器忽略掉而不报错
int main()
{
MyClass var1;
// D var2; //这一行编译将导致错误,因为D类的默认构造函数不合法
}
例子4:std::enable_shared_from_this
在C++标准库头文件中,std::shared_ptr类封装了可被共享使用的指针或资源。一个被共享的对象不能直接把自身的原始指针(raw pointer)this传递给std::shared_ptr的容器对象(如一个std::vector),因为这会生成该被共享的对象的额外的共享指针控制块。为此,std::shared_ptr API提供了一种类模板设施std::enable_shared_from_this,包含了成员函数shared_from_this,从而允许从this创建一个std::shared_ptr对象。
class mySharedClass:public std::enable_shared_from_this<mySharedClass>{
public:
// ...
};
int main()
{
std::vector<std::shared_ptr<mySharedClass>> spv;
spv.push_back(new mySharedClass());
std::shared_ptr<mySharedClass> p(new mySharedClass());
mySharedClass &c=*p;
spv.emplace_back(c.shared_from_this());
}
继承者将自己作为模板参数传递给它的基类。原因之一是,以便于能够访问基类实现中的类型化 this 指针。
struct Foo : SomeBase<Foo>
{
...
}
例如,假设 SomeBase 的每个继承者都实现了迭代所需的 begin()/end() 对。那么,您将如何在 SomeBase 的成员中迭代该对象?直觉表明,您不能这样做,因为 SomeBase 本身没有提供 begin()/end() 接口。但是,如果您使用 CRTP,实际上是可以将 this 转换为派生类类型:
template <typename Derived>
struct SomeBase
{
void foo()
{
for (auto& item : *static_cast<Derived*>(this))
{
...
}
}
}
例子5 树简单遍历
#include <assert.h>
#include <iostream>
using namespace std;
struct TreeNode {
enum Kind { RED, BLUE, YELLOW };
TreeNode(Kind kind_, TreeNode* left_ = NULL, TreeNode* right_ = NULL)
: kind(kind_), left(left_), right(right_) {}
Kind kind;
TreeNode *left, *right;
};
template <typename Derived>
class GenericVisitor {
public:
void visit_preorder(TreeNode* node) {
if (node) {
dispatch_node(node);
visit_preorder(node->left);
visit_preorder(node->right);
}
}
void visit_inorder(TreeNode* node) {
if (node) {
visit_inorder(node->left);
dispatch_node(node);
visit_inorder(node->right);
}
}
void visit_postorder(TreeNode* node) {
if (node) {
visit_postorder(node->left);
visit_postorder(node->right);
dispatch_node(node);
}
}
void handle_RED(TreeNode* node) { cout << "Generic handle RED\n"; }
void handle_BLUE(TreeNode* node) { cout << "Generic handle BLUE\n"; }
void handle_YELLOW(TreeNode* node) { cout << "Generic handle YELLOW\n"; }
private:
// Convenience method for CRTP
Derived& derived() { return *static_cast<Derived*>(this); }
void dispatch_node(TreeNode* node) {
switch (node->kind) {
case TreeNode::RED:
derived().handle_RED(node);
break;
case TreeNode::BLUE:
derived().handle_BLUE(node);
break;
case TreeNode::YELLOW:
derived().handle_YELLOW(node);
break;
default:
assert(0);
}
}
};
class SpecialVisitor : public GenericVisitor<SpecialVisitor> {
public:
void handle_RED(TreeNode* node) { cout << "RED is special\n"; }
};
int main() {
TreeNode node(TreeNode::RED);
node.left = new TreeNode(TreeNode::BLUE);
node.right = new TreeNode(TreeNode::YELLOW);
SpecialVisitor d;
// d.handle_RED(&node); // Prints "Derived implementation"
// d.handle_BLUE(&node);
cout << "visit_postorder " << endl;
d.visit_postorder(&node);
cout << "\nvisit_preorder " << endl;
d.visit_preorder(&node);
cout << "\nvisit_inorder\n";
d.visit_inorder(&node);
delete (node.left);
delete (node.right);
}
运行结果:
visit_postorder
Generic handle BLUE
Generic handle YELLOW
RED is special
visit_preorder
RED is special
Generic handle BLUE
Generic handle YELLOW
visit_inorder
Generic handle BLUE
RED is special
Generic handle YELLOW
混合继承
在 C++ 中,类可以定义为继承自它自己的模板参数,例如:
template <typename T> struct Mixin : T
{
...
}
这种方法被称为混合继承(mixin inheritance),并允许类型的分层组合。例如,您可以允许Foo\<Bar\<Baz>> x;
声明一个实现所有三个类的特征的类型的变量,而不必实际构造一个全新的 FooBarBaz
类型。
属性
一个属性(property,通常是私有的)仅仅是字段以及 getter
和 setter
的组合。在标准 C++
中,一个属性如下所示:
class Person
{
int age;
public:
int get_age() const { return age; }
void set_age(int value) { age = value; }
};
大量的编程语言(例如,C#
、Kotlin
)通过直接将其添加到编程语言中,将属性的概念内化。虽然 C++
没有这样做(而且将来也不太可能这样做),但是有一个名为 property
的非标准声明说明符,您可以在大多数编译器(MSVC、Clang、Intel
)中使用:
class Person
{
int age_;
public:
int get_age() const { return age_; }
void set_age(int value) { age_ = value; }
__declspec(property(get=get_age, put=set_age)) int age;
};
这可以按如下所示使用:
Person person;
p.age = 20; // calls p.set_age(20)
SOLID 设计原则
SOLID 是一个首字母缩写,代表以下设计原则(及其缩写):
- 单一责任原则(SRP)
- 开闭原则(OCP)
- 里氏替换原则(LSP)
- 接口隔离原则(ISP)
- 依赖注入原则(DIP)
详细请翻阅设计模式之基本原则
参考
[1] CRTP