奇异递归模板模式(CRTP: Curiously Recurring Template Pattern)

学无止境,不断更新。。。

 

奇异递归模板模式(curiously recurring template pattern,CRTP)是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。

一般形式

// The Curiously Recurring Template Pattern (CRTP)
template
class Base
{
    // methods within Base can use template to access members of Derived
};
class Derived : public Base
{
    // ...
};

静态多态

template  
struct Base
{
    void interface()
    {
        // ...
        static_cast(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : Base
{
    void implementation();
    static void static_sub_func();
};

基类模板利用了其成员函数体(即成员函数的实现)将不被实例化直至声明很久之后(实际上只有被调用的模板类的成员函数才会被实例化);并利用了派生类的成员,这是通过{{ilh|lang={{langname|Type conversion}}|lang-code=Type conversion|1=类型转化|2=类型转化|d=|nocat=}}。

在上例中,Base::interface(),虽然是在struct Derived之前就被声明了,但未被编译器实例化直至它被实际调用,这发生于Derived声明之后,此时Derived::implementation()的声明是已知的。

这种技术获得了类似于虚函数的效果,并避免了动态多态的代价。也有人把CRTP称为“模拟的动态绑定”。

考虑一个基类,没有虚函数,则它的成员函数能够调用的其它成员函数,只能是属于该基类自身。当从这个基类派生其它类时,派生类继承了所有未被覆盖(overridden)的基类的数据成员与成员函数。如果派生类调用了一个被继承的基类的函数,而该函数又调用了其它成员函数,这些成员函数不可能是派生类中的派生或者覆盖的成员函数。也就是说,基类中是看不到派生类的。但是,基类如果使用了CRTP,则在编译时派生类的覆盖的函数可被选中调用。这效果相当于编译时模拟了虚函数调用但避免了虚函数的尺寸与调用开销(VTBL结构与方法查找、多继承机制)等代价。但CRTP的缺点是不能在运行时做出动态绑定。

不通过虚函数机制,基类访问派生类的私有或保护成员,需要把基类声明为派生类的友元(friend)。如果一个类有多个基类都出现这种需求,声明多个基类都是友元会很麻烦。一种解决技巧是在派生类之上再派生一个accessor类,显然accessor类有权访问派生类的保护函数;如果基类有权访问accessor类,就可以间接调用派生类的保护成员了。

不用虚函数机制,通过基类访问派生类private/protected数据和成员的实现代码如下

#include 

template
class Base
{
private:
    struct accessor : DerivedT
    {                                      // accessor类没有数据成员,只有一些静态成员函数
        static int foo(DerivedT& derived)
        {
            int (DerivedT:: * fn)() = &DerivedT::do_foo; //获取DerivedT::do_foo的成员函数指针
            return (derived.*fn)();        // 通过成员函数指针的函数调用
        }

        static int bar(DerivedT& derived)
        {
            int (DerivedT:: * fn)() const = &DerivedT::do_bar;  //这里的const是必须的,因为DerivedT::do_bar也是const函数
            return (derived.*fn)();
        }
    };                                     // accessor类仅是Base类的成员类型,而没有实例化为Base类的数据成员。
public:
    DerivedT& derived()                    // 该成员函数返回派生类的实例的引用
    {
        return static_cast(*this);
    }
    int foo()
    {                                       //  该函数具体实现了业务功能
        return accessor::foo(this->derived());
    }

    int bar()
    {
        return accessor::bar(this->derived());
    }
};

struct Derived : Base             //  派生类不需要任何特别的友元声明
{
  protected:
      int do_foo()
      {
          // ... 具体实现
          std::cout << "Derived::do_foo()" << std::endl;
          return 1;
      }

//private:
      int do_bar() const
      {
          std::cout << "Derived::do_bar()" << std::endl;
          return value;
      }
private:
    int value{-1};
};

int main()
{

    Base b;
    b.foo();
    b.bar();

    return 0;
}

例子1:对象计数

统计一个类的实例对象创建与析构的数据。使用CRTP很容易可以解决这个问题。

#include 

template 
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  int counter::objects_created( 0 );
template  int counter::objects_alive( 0 );

class X : counter
{
public:
    static int getCreatedX()
    {
        return objects_created;
    }

    static int getAliveX()
    {
        return objects_alive;
    }
};

class Y : counter
{
public:
    static int getCreatedX()
    {
        return objects_created;
    }

    static int getAliveX()
    {
        return objects_alive;
    }
};

void foo(int count = 5)
{
    for (int i = 0; i < count; ++i)
    {
        X x;
        Y y;
    }

}

int main()
{
    X x1;
    Y y1;
    {
        X x;
    }
    foo();

    std::cout << "X created: " << x1.getCreatedX() << ", X alived: " << X::getAliveX() << std::endl;

    return 0;
}

例子2:多态复制构造

当使用多态时,常需要基于基类指针创建对象的一份拷贝。常见办法是增加clone虚函数在每一个派生类中。使用CRTP,可以避免在派生类中增加这样的虚函数。

#include 

// Base class has a pure virtual function for cloning
class Shape
{
public:
    virtual ~Shape() = default;
    virtual Shape *clone() const = 0;
};

// This CRTP class implements clone() for Derived
template 
class Shape_CRTP : public Shape
{
public:
    Shape *clone() const override
    {
        return new Derived(static_cast(*this));
    }
};

// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(DerivedClass) class DerivedClass: public Shape_CRTP

// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

int main()
{
    Square s;
    auto s1 = s.clone();    //s1是Shape*类型指向Square
    auto s2 = s1->clone();  //s21是Shape*类型指向Square

    return 0;
}

这样就可以通过shapePtr->clone()的方式,就可以得到Square,Circle等对象的副本了。

例子3:不可派生的类

一个类如果不希望被继承,C++11中可以使用finall来实现,C++98中可以用虚继承来实现:

template class MakeFinally
{
private:
   MakeFinally(){}//只有MakeFinally的友类才可以构造MakeFinally
   ~MakeFinally(){}
   friend T;
};

class MyClass:public virtual  MakeFinally//MyClass是不可派生类
{
};

//由于虚继承,所以D要直接负责构造MakeFinally类,从而导致编译报错,所以D作为派生类是不合法的。
class D: public MyClass{};
//另外,如果D类没有实例化对象,即没有被使用,实际上D类是被编译器忽略掉而不报错

int main()
{
    MyClass var;
    //D d;  //这一行编译将导致错误,因为D类的默认构造函数不合法
    return 0;
}

如果定义D类型的对象,visual studio2019报错如下:

error: 'MakeFinally::~MakeFinally() [with T = MyClass]' is private within this context

例子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对象。

#include 
#include 
#include 

class mySharedClass:public  std::enable_shared_from_this
{
public:
    mySharedClass() = default;
    mySharedClass(int value): value{value}
    {
    }
    int getValue() const
    {
        return value;
    }
private:
    int value{-1};
  // ...
};

int main()
{
    std::vector> spv;
    spv.push_back(std::make_shared());
    std::shared_ptr p(new mySharedClass(10));
    mySharedClass &r=*p;//这里r是p所管理mySharedClass对象的引用别名
    spv.emplace_back(r.shared_from_this());//p是管理的mySharedClass对象的std::shared_ptr指针,所以可以使用shared_from_this

    for (const auto& vi : spv)
    {
      std::cout << "use_count=" << vi.use_count() << ", value=" << vi->getValue() << std::endl;
    }
    return 0;
}

关于enable_shared_from_this请阅读。

本文参考:https://zh.wikipedia.org/wiki/%E5%A5%87%E5%BC%82%E9%80%92%E5%BD%92%E6%A8%A1%E6%9D%BF%E6%A8%A1%E5%BC%8F

你可能感兴趣的:(C++)