zoukankan      html  css  js  c++  java
  • CRTP有什么用?

    什么是CRTP?

    The curiously recurring template pattern (CRTP) is a C++ idiom in which a class X derives from a class template instantiation using X itself as template argument.

    类X继承了一个以X作为模板参数的的模板,这就是CRTP,具体介绍请参看维基百科

    CRTP简介

    CRTP的意义是父类(接下来我们称之为CRTP父类,相应的子类成为CRTP子类)知道子类的类型,可以做一些虚函数做不到的事,比如维基百科里面提到的类计数,clone函数

    这两个东西用虚函数做起来都不甚方便,本文的目的是探讨CRTP的通用场景

    CRTP可以代替虚函数吗

    不可以,虚函数实现了动态多态,也就是让基类指针指向子类,CRTP做不到这一点

    根据维基,CRTP实现了"静态多态",关于"静态多态",我的理解是在编译时就根据基类指针转换为子类指针,达到类似多态的效果,但本质上跟动态多态还是两码事

    所谓"静态多态"似乎不符合多态的定义:CRTP产生了不同的父类,所以不存在同样的基类指针指向不同子类这样的情况

    或许"静态多态"应该换一个更直观的名字

    那么CRTP有什么用呢?

    在网上看了很多文章,大多语焉不详,举的例子大部分是类计数器,单例模式等看起来很trick,不实用的东西,不可否认这些东西确实"有用",但是你真的会用在在自己的项目中吗?

    笔者以前写代码的时候也遇到过基类需要知道子类类型的情况,那个时候很自然的时候就用了CRTP,当时我并不知道这是CRTP,但是CRTP有没有普适性更强的用法呢?

    包括荣毅的一篇文章http://wenku.baidu.com/view/a14844a1b0717fd5360cdcb2.html,也没有举实际的例子,看不懂

    看看WTL

    但是幸好有WTL,WTL中大量使用了CRTP,我们以CDoubleBufferImpl来看看WTL是如何运用CRTL的

    CDoubleBufferImpl看名字猜测一个双缓冲的渲染封装,核心函数是OnPaint,DoPaint

    CDoubleBufferImpl的定义:

    template <class T>
    class CDoubleBufferImpl
    {
    public:
    // Overrideables
        void DoPaint(CDCHandle /*dc*/)        //子类需要覆盖此函数,没有这个函数也行,但是会出现天书般的模板编译信息
        {
            // must be implemented in a derived class
            ATLASSERT(FALSE);                 
        }
    
    // Message map and handlers
        BEGIN_MSG_MAP(CDoubleBufferImpl)
            MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
            MESSAGE_HANDLER(WM_PAINT, OnPaint)
    #ifndef _WIN32_WCE
            MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
    #endif // !_WIN32_WCE
        END_MSG_MAP()
    
        LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
        {
            return 1;   // no background painting needed
        }
    
        LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
        {
            T* pT = static_cast<T*>(this);          //典型CRTP代码,转换为子类指针
            ATLASSERT(::IsWindow(pT->m_hWnd));        //1
    
            if(wParam != NULL)
            {
                RECT rect = { 0 };
                pT->GetClientRect(&rect);          //2
                CMemoryDC dcMem((HDC)wParam, rect);
                pT->DoPaint(dcMem.m_hDC);          //3
            }
            else
            {
                CPaintDC dc(pT->m_hWnd);            //4
                CMemoryDC dcMem(dc.m_hDC, dc.m_ps.rcPaint);
                pT->DoPaint(dcMem.m_hDC);          //5
            }
    
            return 0;
        }
    };

    编号1,2,3,4,5表明子类需要实现的接口

    实现一个双缓冲窗口的典型代码:

    class TCtrl: 
            public CWindowImpl< TCtrl>,  
            public WTL::CDoubleBufferImpl<TCtrl>  // 继承双缓冲类

    这样TCtrl从CWindowimpl获得了窗口的行为,从CDoubleBufferWindowImpl获得了双缓冲的行为,从而得到了一个双缓冲窗口

    MSG_MAP妨碍了我们的分析,我们抛开MSG_MAP,简化TCtrl的工作流程:

    CWindowimpl接收到PAINT消息,这个消息又发给了CDoubleBufferImpl,CDoubleBufferImpl进行一些处理然后调用TCtrl的DoPaint完成绘制

    在这里WTL使用多重继承+CRTP来拓展类的行为,而不是组合或者单继承

    在这里使用CRTP有一个明显的好处:可以少写很多琐碎的代码,CDoubleBufferImpl模板知道子类的的类型,可以直接使用子类的接口

    不用CRTP如何拓展类?

    如果使用组合:

      我们需要定义一个CDoubleBufferImpl类,这个类实现了双缓冲,注意它用到了GetClientRect之类的东西,所以我们的TCtrl需要把这些数据push到CDoubleBufferImpl,或者定义一些接口让CDoubleBufferImpl使用,
    然后我们调用CDoubleBufferImpl类完成工作

    如果使用单继承:

      TCtrl需要重写一部分CWindowimpl的方法,在这些方法中实现双缓冲

      组合的方法要写很多代码,虽然让CDoubleBufferImpl和CWindowimpl解耦,但是写这么多代码增加了很多复杂度

      单继承的方法看起来非常不错似乎比多继承+CRTP还要简单,但是这样就把CWindowimpl和双缓冲的实现耦合起来了,如果我现在需要给CWindowimpl增加另一种特性,比如"ReSize",为了满足可变的需求,我们需要把库

    设计得足够全面那么,我肯定需要把ReSize和双缓冲两个属性进行组合,这样就会产生4个类,而且会有重复代码(这个时候你肯定会想用组合来实现),如果我再想为添加另一种行为呢?结果是越来越多的类,代码很快就难以维护了

    但是多继承+CRTP提供了另一种方式

      使用多继承+CRTP比组合的代码少,比单继承易于拓展,要添加行为,只需要继承一个类就好了,多个行为相互组合?再继承几个

      多继承+CRTP也有缺点:

      1,使用模板,牵一发而动全身,改动模板会引起大量重编,做过大型c++项目的都明白这实在是一个难以忍受的过程,笔者所在的项目曾经有一个用得比较多的模板类(其实这个类完全没有必要使用模板),笔者有一段时间需要去改动

    这个2000行的庞然大物,每一次改动都要编译几十分钟,苦不堪言

        2,代码比较难读,特别是对于新手,这变相增加了维护成本

      3,多继承+CRTP很灵活,但是封装性不如组合

    总结:

      1 CRTP可以用在任何基类需要子类类型的场合

        2 多继承+CRTP提供了灵活构造类的方式

      笔者水平有限,欢迎指正

     

  • 相关阅读:
    10.22(day11) Object类 异常
    10.19(day10)
    10.18(day9)内部类 抽象类 接口
    10.17(day8) Static关键字 包的使用 访问修饰符的权限 设计模式
    paho-mqtt error1: incorrect protocol version解决方法
    Python进阶-pickle/eval/exec
    关联分析算法Apriori和FP-Growth
    LOF聚类分析
    Python进阶-迭代器和生成器
    Linux常见坑
  • 原文地址:https://www.cnblogs.com/mightofcode/p/2996323.html
Copyright © 2011-2022 走看看