C++ template —— 模板与继承(八)
16.1 命名模板参数
许多模板技术往往让类模板拖着一长串类型参数;不过许多参数都设有合理的缺省值,如:
template <typename policy1 = DefaultPolicy1,
typename policy2 = DefaultPolicy2,
typename policy3 = DefaultPolicy3,
typename policy4 = DefaultPolicy4>
class BreadSlicer
{
.......
};
一般情况下使用缺省模板实参BreadSlicer<>就足够了。不过,如果必须指定某个非缺省的实参,还必须明白地指定在它之前的所有实参(即使这些实参正好是缺省类型,也不能偷懒)。 跟这样的BreadSlicer<DefaultPolicy1, DefaultPolicy2, Custom>相比,BreadSlicer<Policy3 = Custom>显然更有吸引力。这也是本节要介绍的内容。
我们的考虑主要是设法将缺省类型值放到一个基类中,再根据需要通过派生覆盖掉某些类型值。这样,我们就不再直接指定类型实参了,而是通过辅助类完成,如BreadSlicer<Policy3_is<Custom> >。既然用辅助类做模板参数,每个辅助类都可以描述上述4个policy中的任意一个,故所有模板参数的缺省值均相同:
template <typename PolicySetter1 = DefaultPolicyArgs,
typename PolicySetter2 = DefaultPolicyArgs,
typename PolicySetter3 = DefaultPolicyArgs,
typename PolicySetter4 = DefaultPolicyArgs>
class BreadSlicer
{
typedef PolicySelector<PolicySetter1, PolicySetter2,
PolicySetter3, PolicySetter4>
Policies;
// 使用Policies::P1, Policies::P2, ……来引用各个Policies
};
剩下的麻烦事就是实现模板PolicySelector。这个模板的任务是利用typedef将各个模板实参合并到一个单一的类型(即Discriminator),该类型能够根据指定的非缺省类型(如policy1-is的Policy),改写缺省定义的typedef成员(如Default Policies的DefaultPolicy1)。其中合并的事情可以让继承来干:
// PolicySelector<A, B, C, D>生成A, B, C, D作为基类
// Discriminator<>使Policy Selector可以多次继承自相同的基类
// PolicySelector不能直接从Setter继承
template <typename Base, int D>
class Discriminator : public Base{
}; template <typename Setter1, typename Setter2,
typename Setter3, typename Setter4>
class PolicySelector : public Discriminator<Setter1, >,
public Discriminator<Setter2, >,
public Discriminator<Setter3, >,
public Discriminator<Setter4, >{
};
注意,由于中间模板Discriminator的引入,我们就可以一致处理各个Setter类型(不能直接从多个相同类型的基类继承,但可以借助中间类间接继承)。
如前所述,我们还需要把缺省值集中到一个基类中:
// 分别命名缺省policies为P1, P2, P3, P4
class DefaultPolicies
{
public:
typedef DefaultPolicy1 P1;
typedef DefaultPolicy2 P2;
typedef DefaultPolicy3 P3;
typedef DefaultPolicy4 P4;
};
不过由于会多次从这个基类继承,我们必须小心以避免二义性,故用虚拟继承:
// 一个为了使用缺省policy值的类
// 如果我们多次派生自DefaultPolicies,下面的虚拟继承就避免了二义性
class DefaultPolicyArgs : virtual public DefaultPolicies{
};
最后,我们只需要写几个模板覆盖掉缺省的policy参数:
template <typename Policy>
class Policy1_is : virtual public DefaultPolicies
{
public:
typedef Policy P1; //改写缺省的typedef
}; template <typename Policy>
class Policy2_is : virtual public DefaultPolicies
{
public:
typedef Policy P2; //改写缺省的typedef
}; template <typename Policy>
class Policy3_is : virtual public DefaultPolicies
{
public:
typedef Policy P3; //改写缺省的typedef
}; template <typename Policy>
class Policy4_is : virtual public DefaultPolicies
{
public:
typedef Policy P4; //改写缺省的typedef
};
最后,我们把模板BreadSlicer实例化为:
BreadSlicer<Policy3_is<CustomPolicy> > bc;
这时模板BreadSlicer中的类型Polices被定义为:
PolicySelector<Policy3_is<CustomPolicy>,
DefaultPolicyArgs,
DefaultPolicyArgs,
DefaultPolicyArgs>
由类模板Discriminator的帮助,我们得到了图16.1所示的类层次。从中可以看出,所有的模板实参都是基类,而它们有共同的虚基类DefaultPolicies,正是这个共同的虚基类定义了P1, P2, P3和P4的缺省类型;不过,其中一个派生类Policy3_is<>重定义了P3。根据优势规则,重定义的类型隐藏了基类中的定义,这里没有二义性问题。
在模板BreadSlicer中,我们可以使用诸如Policies::P3等限定名称来引用这4个policy,例如:
template <... >
class BreadSlicer
{
...
public:
void print(){
Policies::P3::doPrint();
}
...
};
16.2 空基类优化
C++类常常为“空”,这就意味着在运行期其内部表示不耗费任何内存。这常见于只包含类型成员、非虚成员函数和静态数据成员的类,而非静态数据成员、虚函数和虚基类则的确在运行期耗费内存。
即使是空类,其大小也不会是0。在某些对于对齐要求更严格系统上也会有差异。
16.2.1 布局原则
C++的设计者们不允许类的大小为0,其原因很多。比如由它们构成的数组,其大小必然也是0,这会导致指针运算中普遍使用的性质失效。
虽然不能存在“0大小”的类,但C++标准规定,当空类作为基类时,只要不会与同一类型的另一个对象或子对象分配在同一地址,就不需要为其分配任何空间。我们通过实例来看看这个所谓的空基类优化(empty base class optimization, EBCO)技术:
// inherit/ebco1.cpp
#include <iostream> class Empty
{
typedef int Int; // typedef 成员并不会使类成为非空
}; class EmptyToo : public EmptyToo
{
}; class EmptyThree : public EmptyToo
{
}; int main()
{
std::cout << "sizeof(Empty) : " << sizeof(Empty) << '\n';
std::cout << "sizeof(EmptyToo) : " << sizeof(EmptyToo) << '\n';
std::cout << "sizeof(EmptyThree) : " << sizeof(EmptyThree) << '\n';
}
如果编译器支持空基类优化,上述程序所有的输出结果相同,但均不为0(见图16.2)。也就是说,在类EmptyToo中的类Empty没有分配空间。注意,带有优化空基类的空类(没有其他基类),其大小亦为0;这也是类EmptyThree能够和类Empty具有相同大小的原因所在。然而,在不支持EBCO的编译器上,结果就大相径庭(见图16.3)。
想想在空基类优化下,下例的结果如何?
// inherit/ebco2.cpp
#include <iostream>
class Empty
{
typedef int Int; // typedef 成员并没有使一个类变成非空
}; class EmptyToo : public Empty
{
}; class NonEmpty : public Empty, public EmptyToo
{
}; int main()
{
std::cout << "sizeof(Empty) : " << sizeof(Empty) << '\n';
std::cout << "sizeof(EmptyToo) : " << sizeof(EmptyToo) << '\n';
std::cout << "sizeof(NonEmpty) : " << sizeof(NonEmpty) << '\n';
}
也许你会大吃一惊,类NonEmpty并非真正的“空”类,但的的确确它和它的基类都没有任何成员。不过,NonEmpty的基类Empty和EmptyToo不能分配到同一地址空间,否则EmptyToo的基类Empty会和NonEmpty的基类Empty撞在同一地址空间上。换句话说,两个相同类型的子对象偏移量相同,这是C++对象布局规则不允许的。有人可能会认为可以把两个Empty子对象分别放在偏移0和1字节处,但整个对象的大小也不能仅为1.因为在一个包含两个NonEmpty的数组中,第一个元素和第二个元素的Empty子对象也不能撞在同一地址空间(见图16.4)。
对空基类优化进行限制的根本原因在于,我们需要能比较两个指针是否指向同一对象,由于指针几乎总是用地址作内部表示,所以我们必须保证两个不同的地址(即两个不同的指针值)对应两个不同的对象。
虽然这种约束看起来并不非常重要,但是在实际应用中的许多类都是继承自一组定义公共typedefs的基类,当这些类作为子对象出现在同一对象中时,问题就凸现出来了,此时优化应该被禁止。
16.2.2 成员作基类
书中介绍了将成员作基类的技术。但对于数据成员,则不存在类似空基类优化的技术,否则遇到指向成员的指针时就会出问题。
将成员变量实现为(私有)基类的形式,在模板中考虑这个问题特别有意义,因为模板参数常常可能就是空类(虽然我们不可以依赖这个规则)。
16.3 奇特的递归模板模式
奇特的递归模板模式(Curiously Recurring Template Pattern, CRTP)这个奇特的名字代表了类实现技术中一种通用的模式,即派生类将本身作为模板参数传递给基类。最简单的情形如下:
template <typename Derived>
class CuriousBase
{
....
}; class Curious : public CuriousBase<Curious> // 普通派生类
{
....
};
在第一个实例中,CRTP有一个非依赖型基类:类Curious不是模板,因此免于与依赖型基类的名字可见性等问题纠缠。不过,这并非CRTP的本质特征,请看:
template <typename Derived>
class CuriousBase
{
....
}; template <typename T>
class CuriousTemplate : public CuriousBase<CuriousTemplate<T> > // 派生类也是模板
{
...
};
从这个示例出发,不难再举出使用模板的模板参数的方式:
template <template<typename> class Derived>
class MoreCuriousBase
{
....
}; template <typename T>
class MoreCurious : public MoreCuriousBase<MoreCurious>
{
....
};
CRTP的一个简单应用是记录某个类的对象构造的总个数。数对象个数很简单,只需要引入一个整数类型的静态数据成员,分别在构造函数和析构函数中进行递增和递减操作。不过,要在每个类里都这么写就很繁琐了。有了CRTP,我们可以先写一个模板:
// inherit/objectcounter.hpp #include <stddef.h> template <typename CountedType>
class ObjectCounter
{
private:
static size_t cout; // 存在对象的个数 protected:
//缺省构造函数
ObjectCounter(){
++ObjectCounter<countedType>::count;
}
// 拷贝构造函数
ObjectCounter(ObjectCounter<countedType> const&){
++ObjectCounter<countedType>::count;
}
// 析构函数
~ObjectCounter(){
--ObjectCounter<countedType>::count;
} public:
// 返回存在对象的个数:
static size_t live(){
return ObjectCounter<countedType>::count;
}
}; // 用0来初始化count
template <typename CountedType>
size_t ObjectCounter<CountedType>::count = ;
如果想要数某个类的对象存在的个数,只需让该类从模板ObjectCounter派生即可。以一个字符串类为例:
//inherit/testcounter.cpp #include "objectcounter.hpp"
#include <iostream> template <typename CharT>
class MyString : public ObjectCounter<MyString<CharT> >
{
....
}; int main()
{
MyString<char> s1, s2;
MyString<wchar_t> ws; std::cout << "number of MyString<char> : " << MyString<char>::live() << std::endl;
std::cout << "number of MyString<wchar_t> : " << MyString<wchar_t>::live() << std::endl;
}
一般地,CRTP适用于仅能用作成员函数的接口(如构造函数、析构函数和小标运算operator[]等)的实现提取出来。
16.4 参数化虚拟性
C++允许通过模板直接参数化3种实体:类型、常数(nontype)和模板。同时,模板还能间接参数化其他属性,比如成员函数的虚拟性。
// inherit/virtual.cpp #include <iostream>
class NotVirtual
{
}; class Virtual
{
public:
virtual void foo(){
}
}; template <typename VBase>
class Base : private VBase
{
public:
// foo()的虚拟性依赖于它在基类VBase(如果存在基类的话)中声明
void foo(){
std::cout << "Base::foo() " << '\n';
}
}; template <typename V>
class Derived : public Base<V>
{
public:
void foo(){
std::cout << "Derived::foo() " << '\n';
}
}; int main()
{
Base<NotVirtual>* p1 = new Derived<NotVirtual>;
p1->foo(); // 调用Base::foo() Base<Virtual>* p2 = new Derived<Virtual>;
p2->foo(); // 调用Derived::foo()
}
C++ template —— 模板与继承(八)的更多相关文章
- Django框架之第五篇(模板层) --变量、过滤器、标签、自定义标签、过滤器,模板的继承、模板的注入、静态文件
模板层 模板层就是html页面,Django系统中的(template) 一.视图层给模板传值的两种方法 方式一:通过键值对的形式传参,指名道姓的传参 n = 'xxx'f = 'yyy'return ...
- C++ template —— 模板中的名称(三)
第9章 模板中的名称------------------------------------------------------------------------------------------ ...
- 设计模式之——Template模板模式
Template模式又叫模板模式,是在父类中定义处理流程的框架,在子类中实现具体处理逻辑的模式.当父类的模板方法被调用时程序行为也会不同,但是,不论子类的具体实现如何,处理的流程都会按照父类中所定义的 ...
- django基础2: 路由配置系统,URLconf的正则字符串参数,命名空间模式,View(视图),Request对象,Response对象,JsonResponse对象,Template模板系统
Django基础二 request request这个参数1. 封装了所有跟请求相关的数据,是一个对象 2. 目前我们学过1. request.method GET,POST ...2. reques ...
- Flask 的 template模板 与 jinja2语法
Flask 的 template模板 与 jinja2语法 Flask使用的是Jinja2模板,所以其语法和Django基本无差别 1.模板基本数据的渲染 变量 {{..}} 列表 {% for it ...
- Web框架之Django_04 模板层了解(过滤器、标签、自定义过滤器、标签、inclusion_tag、模板的继承与导入)
摘要: 模版层(模板语法) 模板语法 过滤器 标签 自定义过滤器.标签 inclusion_tag 模板的继承 模板的导入 一.模板语法: 常用语法:{{ }} 变量相关{% %} ...
- Django 你需要掌握的模型层(标签、过滤器、模板的继承与导入)
Django 模型层(标签.过滤器.模板的继承与导入) 好文章来自超哥:https://www.cnblogs.com/guanchao/p/11006062.html 过滤器/自定义过滤器 模板 ...
- Template(模板)模式
第三章:模板模式 Template模式比较简单,是基于继承关系的一种设计模式,由父类定义处理流程框架,由子类中实现具体处理. Code: package example.template; /*** ...
- web框架--tornado框架之模板引擎继承
使用模板的继承可以重复使用相同结构的模板, 可以大大减少代码量 入门实例 一.demo目录结构 注解: master.html为模板内容,被index.html,account.html引用 二.各文 ...
随机推荐
- mysql小题趣事
题一 答案: case when +条件 +then 显示什么 +else+显示另外什么+end
- Unity导入3D模型的过程与方法
一.介绍 资源是游戏开发中的原材料,也就是组成游戏的模块. Unity只是一个游戏开发引擎,而并不是一个资源开发软件.这就意味着在游戏中需要的资源通常是由一些设计者使用其他软件开发出来的,然后设计者会 ...
- Git -- 远程仓库简介
到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了. 可是有用过集中式版本控制系统SVN的童鞋会站出来说,这些功能在SVN里早就有了,没看出Gi ...
- xml根据属性去重。如csprj去重
public static void distinct(string filePath) { //1.创建XML文档对象 XmlDocument doc = new XmlDocument(); // ...
- Docker命令之 run
docker run :创建一个新的容器并运行一个命令 语法 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTIONS说明: -a stdin: 指定 ...
- DShow + OpenGL播放视屏
#include <DShow.h> #pragma include_alias( "dxtrans.h", "qedit.h" ) #define ...
- iOS: block参数
先看一下 NSArray 是怎么传递 block 参数的 ... @interface NSArray (NSExtendedArray) … #if NS_BLOCKS_AVAILABLE - (v ...
- touch事件的分发和消费机制
Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent ev). ...
- TXT文件用法大全【荐】--------按键精灵
来源:全文链接 (3)读取TXT文件指定某一行的第?到第?个字 UserVar t=2 "读出txt第几行文本" UserVar i=5 "从第几个字开始读取" ...
- svn管理码云项目
1.设置SVN管理项目 进入项目->管理 2.获取SVN地址 3.SVN添加项目.单击右键 -> 检出->版本库Url(这里填写svn地址)