学习C++模板---模板类作为基类
使用模板类作为基类使用。
// 模板类-晋升.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <process.h>
#include <iostream>
using namespace std;
template< typename T, int nMax = 5 >
class CTestVector
{
public:
CTestVector(void)
{
cout << "CTestVector(void)" << "nMax=" << nMax << std::endl;
for (int i = 0; i < nMax; i++)
{
m_vec[i] = 0;
}
}
CTestVector(T vec)
{
cout << "CTestVector(T vec)" << "vec="<< vec <<" nMax=" << nMax << std::endl;
for (int i = 0; i < nMax; i++)
{
m_vec[i] = vec;
}
}
void print(void)
{
for (int i = 0; i < nMax; i++)
{
std::cout << m_vec[i] << std::endl;
}
}
protected:
private:
T m_vec[nMax];
};
//使用模板作为基类。
template< int nMax >
class CTestVectorInt : public CTestVector< int, nMax >
{
public:
CTestVectorInt(void) //:CTestVector< int, nMax >(void)
{
cout << "CTestVectorInt(void )" << "nMax=" << nMax << std::endl;
}
CTestVectorInt(int vec) :CTestVector< int, nMax >(vec)
{
cout << "CTestVectorInt(int vec) " << "nMax=" << nMax << " vec=" << vec << std::endl;
}
protected:
private:
};
/*在引入模板之后,我们面临一个新的问题,那就是如果继承自一个模板基类,是否跟继承一般的类有什么区别呢?
就是说,如果一个类继承自某个模板类,那么该类应该注意一些什么呢?其实,它与一般类的继承是很不一样的。
*/
template< typename T >
class Base
{
public:
void printBase() { cout << "call Base printBase() ..." << endl; }
};
template< typename T >
class Derived : public Base<T>
{
public:
void printDerived()
{
cout << "call Derived printDerived() ... " << endl;
//printBase(); //会报错:error C3861: 'printBase': identifier not found
/*方法一:this->, 除非Derived中也有一个printBase(),那么调用的就是Derived::printBase(),否则调用的是Base::printBase()*/
this->printBase();
//方法二:声明为基类的方法Base<T>::printBase(),调用的是Base::printBase()
Base<T>::printBase();
Base::printBase();
//方法三:using 声明基类的printBase,调用的是Base::printBase()
using Base::printBase;
printBase();
}
};
//
//程序入口点。
//
int main()
{
//使用继承模板。
//CTestVectorInt< 2 > testVectorInt(); //-------记住不要这样写当构造函数无参的时候,不会调用任何构造函数直接报错
//CTestVectorInt< 2 > testVectorInt = CTestVectorInt< 2 >() ;--------和下面的相同
CTestVectorInt< 2 > testVectorInt;
testVectorInt.print();
//CTestVectorInt< 2 > testVectorIntVal= CTestVectorInt< 2 >(8) ;--------和下面的相同
CTestVectorInt< 2 > testVectorIntVal(8);
testVectorIntVal.print();
//float实例。
CTestVector< float, 3 > testFloat(3.14159f);
testFloat.print();
//double实例。
double dA = 1.55233f;
CTestVector< double, 6 > testDouble(dA);
testDouble.print();
//
//暂停.
//
system("pause");
return 0;
}
运行结果
在引入模板之后,我们面临一个新的问题,那就是如果继承自一个模板基类,是否跟继承一般的类有什么区别呢?
就是说,如果一个类继承自某个模板类,那么该类应该注意一些什么呢?其实,它与一般类的继承是很不一样的。
先举个简单的例子:
template< typename T >
class Base
{
public:
void printBase() { cout << "call Base printBase() ..." << endl; }
};
template< typename T >
class Derived : public Base<T>
{
public:
void printDerived()
{
cout << "call Derived printDerived() ... " << endl;
printBase();
}
};
//>c:\users\administrator\source\repos\模板类-晋升\模板类-晋升\模板类-晋升.cpp(85): error C3861: 'printBase': identifier not found
1>c:\users\administrator\source\repos\模板类-晋升\模板类-晋升\模板类-晋升.cpp(87): note: see reference to class template instantiation 'Derived<T>' being compiled
我们可以看到,在上述代码中,Derived类中的成员函数调用了Base类的成员函数,显然这是很合理的,因为PrintBase并没有被声明为virtual,因此,派生类直接继承其声明与实现,可是编译器却不这么认为,编译器给出的错误是:
error: there are no arguments to 'printBase' that depend on a template parameter, so a declaration of 'printBase' must be available。
这里的编译器并不承认PrintBase的存在,并且认为该函数它不认识,这与面向对象的C++出现了分歧。
C++的模板中的名称会进行两次查找,称为两阶段查找(two-phase lookup)。
对于一个非依赖型名称(不以任何方式依赖于模板参数的名称),在模板声明进行解析的时候就会进行查找。
但C++标准中规定(14.6.2 3),一个非受限的名称查找的时候将不会考虑依赖型的基类。
所以
template<typename>
struct B
{
int x;
};
template<typename T>
struct X : B<T>
{
void foo() { x = 0; }
};
查找不到就会错误。
解决办法是把它变成一个依赖型名称:
template<typename>
struct B
{
int x;
};
template<typename T>
struct X : B<T>
{
void foo() { this->x = 0; } // this是依赖型名称
};
或者
template<typename>struct B
{
int x;
};
template<typename T>
struct X : B<T>
{
void foo() { B<T>::x = 0; } // B<T>是依赖型名称
};
关于此分歧的原因,可以由下面的例子来解释,归根结底,都是模板基类的引入。
class CompanyA
{
public:
void sendClearMsg() { cout << "call company A sendClear " << endl;}
void sendEncryptedMsg() { cout << "call company A sendEncryptedMsg " << endl;}
};
class CompanyB
{
public:
void sendClearMsg() { cout << "call company B sendClear " << endl;}
void sendEncryptedMsg() { cout << "call company B sendEncryptedMsg " << endl;}
};
//首先定义了两个类,用于表示各个公司自己传送信息的方式。
//CompanyC与上述两个公司不一样,只允许将数据加密之后再传送
//companyC only send encrypted Msg
class CompanyC
{
public:
void sendEncryptedMsg() { cout << "call company C sendEncryptedMsg " << endl;}
};
//定义了一个传送信息的类,对于不同的公司,封装了公司的传送信息的方式,取决于模板实参。
template
class MsgSender
{
public:
void sendClear()
{
cout << "call MsgSender sendClear ... " << endl;
Company c;
c.sendClearMsg();
}
void sendEncrypted()
{
cout << "call MsgSender sendEncrypted ... " << endl;
Company c;
c.sendEncryptedMsg();
}
};
//针对CompanyC的特化版本的传送信息方式,即如果模板实参为CompanyC,那么直接调用此模板类
template<>
class MsgSender
{
public:
void sendClear()
{
cout << "call MsgSender sendClear ... " << endl;
}
void sendEncrypted()
{
cout << "call MsgSender sendEncrypted ... " << endl;
}
};
//在每次将信息发出去时,记录下发出的信息。
//log the Message
template
class LogSendMsg : public MsgSender
{
public:
void Log()
{
cout << "call LogSendMsg log ..." << endl;
// sendClear(); //错误!!因为LogSendMsg类不知道这个sendClear函数是什么?甚至不知道是哪儿来的。
//error: there are no arguments to 'sendClear' that depend on a template parameter, so a declaration of 'sendClear' must be available|
cout << "call LogSendMsg log end... " << endl;
}
};
显然,在上述的LogSendMsg中,该类只有在模板形参Company被 实例化 之后才知道实际调用的是哪个Company。显然在上述的代码中,CampanyC是没有定义SendClear函数的,编译器不可能一一的去预先知道这样的情况,因为用户指不定就为某家公司定义了一个特化版本,会直接导致该函数失效。
因此,编译器本着 “尽早报告错误”的可能性,在看到SendClear函数的时候,编译器不会去寻找该函数的定义,只当该函数没声明。结果就如上面的错误了。
注:这里就看出了模板基类与一般的基类的区别,一般的基类会沿着类的作用域一层一层的往上找,知道找到该函数的声明。但是模板类不会干这些无用功,是因为它知道在后续自己被 “实例化”之后,该函数可能根本就不存在(例如上述CompanyC的特化版本)。
因此,对于编译器的这种 “不进入模板基类去寻找定义” 的行为,必须由用户告诉编译器该怎么干:
1 可以使用this指针,即 this->sendClear 函数。
2 使用using声明,显式地告诉编译器,我要用的是基类的sendClear函数。 using Base::sendClear或者using Base<T>::sendClear
3 使用作用域符号,告诉编译器这个函数是哪个类的。Base<T>::sendClear
值得注意的是,上述三种途径,是针对该函数一定在基类中有定义才这么干,例如上述的CompanyC并没有定义sendClear函数,那么上述做法是失效的。
总之,在使用模板基类时,对于基类中定义的名字,在派生类中必须显式地指明,因为在基类实例化之前,编译器对于基类的定义一无所知。。
定义一个类模板,注意两点:
1,类的定义前面使用关键词:template <class T>
2,函数的实现部分,在每个函数名的上一行也要加关键词template <class T>, 并且在函数名后面添加<T>,例如
-
template <class T>
-
ImageProcessor<T>::ImageProcessor(void)
-
{
-
}
3,在main.cpp文件中要同时包含模板类的.h文件和.cpp文件。
如果要从一个类模板中继承产生新的类模板注意:
1,子类的头文件中要包含父类的.h文件和.cpp文件。
2,子类的定义中,父类的名称后面要加<T>
以下是一个例子,父类是一个处理图像的通用类,子类在此继承上继承,实现对图像的下采样功能。
模板类的基类为:imageprocessor.h
-
#pragma once
-
template <class T>
-
class ImageProcessor
-
{
-
protected:
-
int input_size[4];
-
T * input_data;
-
int output_size[4];
-
T * output_data;
-
public:
-
void setInputImageSize(int, int, int, int);
-
void setInputData(T *);
-
void setOutputImageSize(int, int, int, int);
-
void getOutputImageSize(int []);
-
void setOutputData(T *);
-
bool validInputImageSize(void);
-
bool validOutputImageSize(void);
-
int getInputPixelPointerShift(int, int,int,int);
-
int getOutputPixelPointerShift(int, int,int,int);
-
T* getInputPixelPointer(int, int,int,int);
-
T* getOutputPixelPointer(int, int,int,int);
-
T getInputPixelValue(int, int,int,int);
-
void setOutputPixelValue(int, int,int,int,T);
-
ImageProcessor(void);
-
public:
-
~ImageProcessor(void);
-
};
.cpp文件为:imageprocessor.cpp
-
#pragma once
-
#include "StdAfx.h"
-
#include "ImageProcessor.h"
-
#include "windows.h"
-
#include <iostream>
-
using namespace std;
-
template <class T>
-
ImageProcessor<T>::ImageProcessor(void)
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::setInputImageSize(int h,int w,int d,int s)
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::setInputData(T* p)
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::setOutputImageSize(int h,int w,int d,int s)
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::getOutputImageSize(int size[4])
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::setOutputData(T* p)
-
{
-
}
-
template <class T>
-
bool ImageProcessor<T>::validInputImageSize(void)
-
{
-
}
-
template <class T>
-
bool ImageProcessor<T>::validOutputImageSize(void)
-
{
-
}
-
template <class T>
-
int ImageProcessor<T>::getInputPixelPointerShift(int x, int y,int z,int s)
-
{
-
}
-
template <class T>
-
int ImageProcessor<T>::getOutputPixelPointerShift(int x, int y,int z,int s)
-
{
-
}
-
template <class T>
-
T* ImageProcessor<T>::getInputPixelPointer(int x, int y,int z,int s)
-
{
-
}
-
template <class T>
-
T* ImageProcessor<T>::getOutputPixelPointer(int x, int y,int z,int s)
-
{
-
}
-
template <class T>
-
T ImageProcessor<T>::getInputPixelValue(int x, int y,int z,int s)
-
{
-
}
-
template <class T>
-
void ImageProcessor<T>::setOutputPixelValue(int x, int y,int z,int s, T value)
-
{
-
}
-
template <class T>
-
ImageProcessor<T>::~ImageProcessor(void)
-
{
-
}
子类的.h文件为:ipDownSampler.h
-
#pragma once
-
#include "imageprocessor.h"
-
#include "imageprocessor.cpp"
-
template <class T>
-
class ipDownSampler :
-
public ImageProcessor<T>
-
{
-
protected:
-
double sample_rate[4];
-
int interpolation_method;
-
public:
-
bool setDownSampleRate(double []);
-
bool getDownSampledSize(void);
-
bool validDownSampleRate(void);
-
bool setInterpolationMethod(int);
-
bool downSample();
-
T getPixelValueByNearestInterpolation(double,double,double,int);
-
T getPixelValueByBilinearInterpolation(double,double,double,int);
-
ipDownSampler(void);
-
public:
-
~ipDownSampler(void);
-
};
其.cpp文件为:ipDownSampler.cpp
-
#pragma once
-
#include "StdAfx.h"
-
#include "ipDownSampler.h"
-
template <class T>
-
bool ipDownSampler<T>::setDownSampleRate(double rate[4])
-
{
-
}
-
template <class T>
-
bool ipDownSampler<T>::getDownSampledSize(void)
-
{
-
}
-
template <class T>
-
bool ipDownSampler<T>::validDownSampleRate(void)
-
{
-
}
-
template <class T>
-
bool ipDownSampler<T>::setInterpolationMethod(int method)
-
{
-
}
-
template <class T>
-
bool ipDownSampler<T>::downSample()
-
{
-
}
-
template <class T>
-
T ipDownSampler<T>::getPixelValueByNearestInterpolation(double x,double y ,double z,int s)
-
{
-
}
-
template <class T>
-
T ipDownSampler<T>::getPixelValueByBilinearInterpolation(double x,double y ,double z,int s)
-
{
-
}
-
template <class T>
-
ipDownSampler<T>::ipDownSampler(void)
-
{
-
}
-
template <class T>
-
ipDownSampler<T>::~ipDownSampler(void)
-
{
-
}
派生类继承模板化基类的成员函数, 默认是无法访问, 模板化基类的命名.
原因是模板的定制化有可能取消某些函数, 为了能在编译期检测出错误, 所以默认无法访问.
派生类访问模板化基类, 包含三种方法:
1. 调用基类函数时, 使用"this->", 指明调用的类, 是本类, 在编译时, 可以进行检查; this->size;
2. 使用using声明式, 可以把基类的函数引入派生类, 在编译时, 可以进行检查; using Base::size;
3. 使用显示修饰(explicit qualification), 不推荐, 显示修饰会屏蔽virtual的动态绑定; Base::size;
// 模板类-晋升.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <process.h>
#include <iostream>
using namespace std;
#define TEMPLATE_TEST 0
#define EMPTY_BASE_CLASS_TEST 1
#if TEMPLATE_TEST
//********************************************模板类继承:子类访问基类成员************************************************************//
template< typename T, int nMax = 5 >
class CTestVector
{
public:
CTestVector(void)
{
cout << "CTestVector(void)" << "nMax=" << nMax << std::endl;
for (int i = 0; i < nMax; i++)
{
m_vec[i] = 0;
}
}
CTestVector(T vec)
{
cout << "CTestVector(T vec)" << "vec="<< vec <<" nMax=" << nMax << std::endl;
for (int i = 0; i < nMax; i++)
{
m_vec[i] = vec;
}
}
void print(void)
{
for (int i = 0; i < nMax; i++)
{
std::cout << m_vec[i] << std::endl;
}
}
protected:
private:
T m_vec[nMax];
};
//使用模板类:作为基类。
template< int nMax >
class CTestVectorInt : public CTestVector< int, nMax >
{
public:
CTestVectorInt(void) //:CTestVector< int, nMax >(void)
{
cout << "CTestVectorInt(void )" << "nMax=" << nMax << std::endl;
}
CTestVectorInt(int vec) :CTestVector< int, nMax >(vec)
{
cout << "CTestVectorInt(int vec) " << "nMax=" << nMax << " vec=" << vec << std::endl;
}
protected:
private:
};
/*在引入模板之后,我们面临一个新的问题,那就是如果继承自一个模板基类,是否跟继承一般的类有什么区别呢?
就是说,如果一个类继承自某个模板类,那么该类应该注意一些什么呢?其实,它与一般类的继承是很不一样的。
*/
template< typename T >
class Base
{
public:
void printBase() { cout << "call Base printBase() ..." << endl; }
};
template< typename T >
class Derived : public Base<T>
{
public:
void printDerived()
{
cout << "call Derived printDerived() ... " << endl;
//printBase(); //会报错:error C3861: 'printBase': identifier not found
/*方法一:this->, 除非Derived中也有一个printBase(),那么调用的就是Derived::printBase(),否则调用的是Base::printBase()*/
this->printBase();
//方法二:声明为基类的方法Base<T>::printBase(),调用的是Base::printBase()
Base<T>::printBase();
Base::printBase();
//方法三:using 声明基类的printBase,调用的是Base::printBase()
using Base::printBase;
printBase();
}
};
//
//程序入口点。
//
int main()
{
//使用继承模板。
//CTestVectorInt< 2 > testVectorInt(); //-------记住不要这样写当构造函数无参的时候,不会调用任何构造函数直接报错
//CTestVectorInt< 2 > testVectorInt = CTestVectorInt< 2 >() ;--------和下面的相同
CTestVectorInt< 2 > testVectorInt;
testVectorInt.print();
//CTestVectorInt< 2 > testVectorIntVal= CTestVectorInt< 2 >(8) ;--------和下面的相同
CTestVectorInt< 2 > testVectorIntVal(8);
testVectorIntVal.print();
//float实例。
CTestVector< float, 3 > testFloat(3.14159f);
testFloat.print();
//double实例。
double dA = 1.55233f;
CTestVector< double, 6 > testDouble(dA);
testDouble.print();
//
//暂停.
//
system("pause");
return 0;
}
#endif
#if EMPTY_BASE_CLASS_TEST
//**********************************************************************空基类优化*********************************************************************//
/*
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 Empty
{
};
class EmptyThree : public EmptyToo
{
};
/*
如果编译器支持空基类优化,上述程序所有的输出结果相同,但均不为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
//{
//};
/*
也许你会大吃一惊,类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 T>
class ObjectCounter
{
private:
static size_t count; // 存在对象的个数
protected:
//缺省构造函数
ObjectCounter() {
++ObjectCounter<T>::count;
}
// 拷贝构造函数
ObjectCounter(ObjectCounter<T> const&) {
++ObjectCounter<T>::count;
}
// 析构函数
~ObjectCounter() {
--ObjectCounter<T>::count;
}
public:
// 返回存在对象的个数:
static size_t live()
{
return ObjectCounter<T>::count;
}
};
// 用0来初始化count
template <typename T>
size_t ObjectCounter<T>::count = 0;
//如果想要数某个类的对象存在的个数,只需让该类从模板ObjectCounter派生即可。以一个字符串类为例:
//inherit/testcounter.cpp
//#include "objectcounter.hpp"
#include <iostream>
template <typename CharT>
class MyString : public ObjectCounter<MyString<CharT> >
{
//....
};
/*一般地,CRTP适用于仅能用作成员函数的接口(如构造函数、析构函数和小标运算operator[]等)的实现提取出来。*/
//**************************************************************16.4 参数化虚拟性*****************************************************//
/*C++允许通过模板直接参数化3种实体:类型、常数(nontype)和模板。同时,模板还能间接参数化其他属性,比如成员函数的虚拟性。*/
// inherit/virtual.cpp
#include <iostream>
void foo()
{
cout << "global foo()" << endl;
}
void goo2()
{
cout << "global goo()" << endl;
}
class NotVirtual
{
};
class Virtual
{
public:
virtual void foo()
{
cout << "Virtual::f()" << endl;
}
virtual void goo()
{
std::cout << "Virtual::goo() " << '\n';
}
};
template <typename VBase>
class Base : public VBase
{
public:
// foo()的虚拟性依赖于它在基类VBase(如果存在基类的话)中声明
void foo()
{
std::cout << "Base::foo() " << '\n';
}
void goo()
{
std::cout << "Base::goo() " << '\n';
}
};
template <typename V>
class Derived : public Base<V>
{
public:
void foo()
{
std::cout << "Derived::foo() " << '\n';
}
void goo()
{
goo2(); //gloabl goo() 如果使用的是和模板类同名的函数(gloabl goo())则模板中还是执行的自身的goo函数,不会去执行gloabl的全局函数接口
//this->goo(); //Derived::goo()
Base<V>::goo(); //Base::goo()
}
};
//**************************************************************16.5 C++ 模板 静态成员 定义(实例化)*****************************************************//
//有这样一个模板:
template <typename T>
class Test
{
public:
static std::string info;
};
template <> string Test<int>::info("123"); //1 ok 特化定义
//template <typename T> string Test<T>::info("123"); //2 ok 类似于普通类的静态成员的定义方式 2和3 不能同时定义:error C2086: 'std::string Test<T>::info': redefinition
//template <typename T> string Test<T>::info; //3 ok 采用默认构造函数初始化
//template <> string Test<int>::info; //4 error 编译认为它是一个声明而非定义
//template <> string Test<int>::info(); //5 error 声明一个函数
//template <typename T> string Test<T>::info(); //6 error 声明一个函数
/*
首先,说明一下三个正确的答案。
第一种形式称之为: 特化定义,其作用是为模板某一特化提供其静态成员的定义,在我们例子中,它仅仅为Test<int>类的静态成员info提供定义。并且调用单参数构造函数初始化。
第 二种形式类似于普通类的静态成员的定义方式,其作用是隐式地在该编译单元为模板的所有特化提供其静态成员的定义,在我们例子中,在首次使用 Test<int>,Test<float>,Test<char>...会隐式提供静态成员的定义,并且调用单参 数构造函数初始化。
第三种形式和第二种形式一致,唯一不同就是采用默认构造函数初始化。
其次,说明一下三个错误的答案。
第一种形式,很多人都会认为是对的,认为它采用默认构造函数初始化。但编译器会对特化定义进行特殊处理,编译认为它是一个声明而非定义。至于为什么如此,应该询问一下制定标准的人。我认为可能实现这样的语法可能比较困难并且这个语法也比较鸡肋。
第二种形式,这不成了声明一个函数啦。
第三种形式,同第二种。
更多内容:
两种正确的定义方式还有哪些其他的区别呢?
//a.cpp
template <typename T> string Test<T>::info("4321");
可以使用Test<int>::info
//b.cpp
template <typename T> string Test<T>::info("1234");
也可以使用Test<int>::info
这两个定义可以在不同的编译单元共存,Test<int>::info的初始值是多少,这取决与静态成员的初始化顺序,所以这不是件好事。
//a.cpp
template <> string Test<int>::info("123");
//b.cpp
template <> string Test<int>::info("123");
//a.cpp
template <> string Test<int>::info("123");
//b.cpp
template <typename T> string Test<T>::info("123");
一旦使用Test<int>::info无法通编译
上述方式无法通过编译。
一般为了避免无法编译,应当尽量减少使用,如下方式的定义
template <typename T> string Test<T>::info;
只有在你首次需要使用时在实现文件中给出如下特化定义即可,其他文件只要包含头文件就能使用。
template <> string Test<int>::info("123");
*/
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#ifndef ANDROID_UTILS_SINGLETON_H
//#define ANDROID_UTILS_SINGLETON_H
//
//#include <stdint.h>
//#include <sys/types.h>
//#include <utils/threads.h>
//#include <cutils/compiler.h>
//
//namespace android {
//#include <mutex>
//using namespace std;
//class Autolock {
//public:
// //构造的时候调用lock。
// inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
// inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
// //析构的时候调用unlock。
// inline ~Autolock() { mLock.unlock(); }
//private:
// Mutex & mLock;
//};
// // ---------------------------------------------------------------------------
//
// template <typename TYPE>
// class ANDROID_API Singleton
// {
// public:
// static TYPE& getInstance()
// {
// Mutex::Autolock _l(sLock);
// TYPE* instance = sInstance;
// if (instance == 0) {
// instance = new TYPE();
// sInstance = instance;
// }
// return *instance;
// }
//
// static bool hasInstance()
// {
// Mutex::Autolock _l(sLock);
// return sInstance != 0;
// }
//
// protected:
// ~Singleton() { };
// Singleton() { };
//
// private:
// //禁止复制构造函数和赋值运算符函数,禁止类外部和内部以及友元调用 declare private,not define
// Singleton(const Singleton&);
// Singleton& operator = (const Singleton&);
// static Mutex sLock;
// static TYPE* sInstance;
// };
//
// /*
// * use ANDROID_SINGLETON_STATIC_INSTANCE(TYPE) in your implementation file
// * (eg: <TYPE>.cpp) to create the static instance of Singleton<>'s attributes,
// * and avoid to have a copy of them in each compilation units Singleton<TYPE>
// * is used.
// *
// * NOTE: we use a version of Mutex ctor that takes a parameter, because
// * for some unknown reason using the default ctor doesn't emit the variable! 特化定义必须使用有参数的构造函数,否则认为是声明!
// */
// //想要使用Singleton,需要在自定义类型的实现文件中包含此宏,用以初始化类模版static变量,并显示实例化类模版
//#define ANDROID_SINGLETON_STATIC_INSTANCE(TYPE) \
// template<> Mutex Singleton< TYPE >::sLock(Mutex::PRIVATE); \ 特化定义
// template<> TYPE* Singleton< TYPE >::sInstance(0); \ 特化定义
// template class Singleton< TYPE >; \显示实例化
//
//}; // namespace android
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;
Base<NotVirtual>* p1 = new Derived<NotVirtual>;
p1->foo(); // 调用Base::foo()
p1->goo();
//Base<Virtual>* p2 = new Derived<NotVirtual>; //参数不对应,报错
//p1->foo(); // 调用Base::foo()
Base<Virtual>* p3 = new Derived<Virtual>;
p3->foo(); // 调用Derived::foo()
p3->goo();
std::cout << "sizeof(Empty) : " << sizeof(Empty) << '\n'; // 1
std::cout << "sizeof(EmptyToo) : " << sizeof(EmptyToo) << '\n'; // 1
std::cout << "sizeof(EmptyThree) : " << sizeof(EmptyThree) << '\n'; // 1
//std::cout << "sizeof(Empty) : " << sizeof(Empty) << '\n'; // 1
//std::cout << "sizeof(EmptyToo) : " << sizeof(EmptyToo) << '\n'; // 1
//std::cout << "sizeof(NonEmpty) : " << sizeof(NonEmpty) << '\n'; // 1
//
//暂停.
//
system("pause");
}
#endif