- property trait:用于确定模板参数的一些属性。比如这些参数表示什么类型,在混合类型的操作中,应该提升哪一个类型等等。
- policy trait:定义如何对待这些类型。
只读的参数类型
在C/C++中,函数调用实参在缺省情况下都是以传值方式进行传递的。也就是说,由调用者计算出来的实参值需要被拷贝到被调用者所控制的位置中;但是如果参数是很大的数据结构,这种拷贝非常耗资源;因此,对于这种数据,应该传递const引用;如果参数是很小的结构,从性能的观点看,究竟采用何种机制依赖于代码所在的实际体系结果。实际上,小的数据结果究竟采用何种机制,对性能的影响不大;但是,我们也必须小心处理,选择一个适当的传递机制
当引入模板后,事情就更加复杂了:因为我们事先并不知道用来替换模板参数的类型究竟有多大,而且,最后的决定也不仅仅依赖于类型的大小:一个小的结构也可能会具有昂贵的拷贝构造函数,这是我们也应该以const 引用
的方式来传递只读参数
可以使用policy trait模板来处理上面的问题,而且该policy trait
模板实际上是一个类型函数:该函数可以根据不同的情况(即类型大小),将把实参类型T映射为T
或者T const&
,即在这两种类型中挑选出一种最佳参数类型。我们来看个例子:
template<bool C, typename Ta, typename Tb>
class IfThenElse;
// 局部特化: true 的话选择第二个实参
template<typename Ta, typename Tb>
class IfThenElse<true, Ta, Tb>{
public:
typedef Ta ResultT;
};
// 局部特化: false 的话选择第三个实参
template<typename Ta, typename Tb>
class IfThenElse<false, Ta, Tb>{
public:
typedef Tb ResultT;
};
template<typename T>
class RParam{
public:
typedef typename IfThenElse<sizeof(T) <= 2*sizeof(void *),
T,
T const &>::ResultT Type;
};
上面模板实现了:对于不大于”2个指针“大小的类型,基本模板将采用传值的方式传递参数,而对于其他的类型,将采用传递const引用的方式传递参数。
另外,对于容器里下,即使sizeof函数返回的是一个很小的值,但也可能涉及到昂贵的拷贝构造函数。因此,编写如下的特化:
template<typename T>
class RParam<Array<T>>{
public:
typedef Array<T> const & Type;
};
由于我们处理的都是C++的常见类型,所以我们期望在基本模板中能够对非class类型以传值的方式进行调用。另外对于某些对性能要求比较苛刻的class类型,我们有选择的添加这些类为传值方式:
template<bool C, typename Ta, typename Tb>
class IfThenElse;
// 局部特化: true 的话选择第二个实参
template<typename Ta, typename Tb>
class IfThenElse<true, Ta, Tb>{
public:
typedef Ta ResultT;
};
// 局部特化: false 的话选择第三个实参
template<typename Ta, typename Tb>
class IfThenElse<false, Ta, Tb>{
public:
typedef Tb ResultT;
};
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
template<typename T>
class RParam {
public:
typedef typename IfThenElse<IsClassT<T>::No,
T,
T const&>::ResultT Type;
};
使用:假设我们有两个类,其中一个指定:对于只读参数而言,传值调用具有更好的性能:
class MyClass1 {
public:
MyClass1 () {
}
MyClass1 (MyClass1 const&) {
std::cout << "MyClass1 copy constructor called\n";
}
};
class MyClass2 {
public:
MyClass2 () {
}
MyClass2 (MyClass2 const&) {
std::cout << "MyClass2 copy constructor called\n";
}
};
//对于RParam<>的MyClass2 参数,传值
template<>
class RParam<MyClass2> {
public:
typedef MyClass2 Type;
};
使用:
template<typename T1, typename T2>
void foo(typename RParam<T1>::Type p1,
typename RParam<T2>::Type p2){
}
int main()
{
MyClass1 myClass1;
MyClass2 myClass2;
foo<MyClass1, MyClass2>(myClass1, myClass2);
}
上面的实用有几个严重的缺点:函数声明变得复杂,而且现在不能使用实参演绎来获取比如foo()的函数了,因为模板参数只是出现在函数参数的限定符里面。
解决上面的问题的方法:使用一个内联的包装(wrapper)函数模板:但是该方案假设内联函数将会被编译器移除,即编译器将直接调用位于内联函数里面的函数:
template <typename T1, typename T2>
void foo_core (typename RParam<T1>::Type p1,
typename RParam<T2>::Type p2)
{
//...
}
// 为了避免显式指定模板参数而实现的wrapper
template <typename T1, typename T2>
inline
void foo (T1 const & p1, T2 const & p2)
{
foo_core<T1,T2>(p1,p2);
}
int main()
{
MyClass1 myClass1;
MyClass2 myClass2;
foo(myClass1, myClass2);// ==> foo<MyClass1, MyClass2>(myClass1, myClass2);
}
拷贝、交换和移动
本节将引入一个policy trait模板:它将选择出最佳的操作,来拷贝、交换或者移动某一特定类型的元素。
假设拷贝操作是通过拷贝构造函数和拷贝赋值运算符来实现的。对于单一元素而言,这是完全正确的。但是对于具有相同类型的多个元素而言,与重复地调用该类型的构造函数或者赋值运算符相比,可能还存在效率更高的操作。
容器类型就是一个典型的例子。实际上,对容器类型而言,拷贝操作通常是不允许的,而主要是进行交换或者移动操作。
因此,我们期望能够用一个合适的trait模板,来确定上面的问题(最佳操作)。对于泛型定义,我们将区分class和nonclass类型,因为定于nonclass类型而言,我们并不需要在意用户自定义的拷贝构造函数和拷贝赋值运算符。这一次,我们将
- 定义一个基本模板(CMS表示”copy、move、swap“)。
// primary template
template<typename T, bool Bitwise>
class BitOrClassCSM;
- 使用继承,从而能够在两种trait实现中进行选择。
template <typename T>
class CSMtraits : public BitOrClassCSM<T, IsClassT<T>::No > {
};
CSMtraits 的实现完全委托给类BitOrClassCSM的特化
- 实现BitOrClassCSM
// 用于对象安全拷贝的局部特化
template<typename T>
class BitOrClassCSM<T, false> {
public:
static void copy (typename RParam<T>::Type src, T* dst) {
// 把其中一项拷贝给对应对的另一项
*dst = src;
}
static void copy_n (T const* src, T* dst, size_t n) {
//把其中n项拷贝给其他n项
for (size_t k = 0; k<n; ++k) {
dst[k] = src[k];
}
}
static void copy_init (typename RParam<T>::Type src,
void* dst) {
//拷贝一项到未进行初始化的存储空间
::new(dst) T(src);
}
static void copy_init_n (T const* src, void* dst, size_t n) {
//拷贝n项到未进行初始化的存储空间
for (size_t k = 0; k<n; ++k) {
::new((void*)((T*)dst+k)) T(src[k]);
}
}
static void swap (T* a, T* b) {
//交换1项
T tmp(*a);
*a = *b;
*b = tmp;
}
static void swap_n (T* a, T* b, size_t n) {
//交换n项
for (size_t k = 0; k<n; ++k) {
T tmp(a[k]);
a[k] = b[k];
b[k] = tmp;
}
}
static void move (T* src, T* dst) {
// 移动1项到另一项
assert(src != dst);
*dst = *src;
src->~T();
}
static void move_n (T* src, T* dst, size_t n) {
// 移动n项到另一项
assert(src != dst);
for (size_t k = 0; k<n; ++k) {
dst[k] = src[k];
src[k].~T();
}
}
static void move_init (T* src, void* dst) {
// 移动一项到未初始化的存储空间
assert(src != dst);
::new(dst) T(*src);
src->~T();
}
static void move_init_n (T const* src, void* dst, size_t n) {
// 移动n项到未初始化的存储空间
assert(src != dst);
for (size_t k = 0; k<n; ++k) {
::new((void*)((T*)dst+k)) T(src[k]);
src[k].~T();
}
}
};
上面policy trait模板的成员函数都是静态的。实际上,大多数情况也是如此,因为我们只是对参数类型的对象应用这些成员函数,而并非对trait class类类型的对象应用这些成员函数
最后,另一个针对位元拷贝的trait的局部特化如下:
// 针对更快的对象位元拷贝而实现的局部特化
template <typename T>
class BitOrClassCSM<T,true> : public BitOrClassCSM<T,false> {
public:
static void copy_n (T const* src, T* dst, size_t n) {
// copy n items onto n other ones
std::memcpy((void*)dst, (void*)src, n*sizeof(T));
}
static void copy_init_n (T const* src, void* dst, size_t n) {
// copy n items onto uninitialized storage
std::memcpy(dst, (void*)src, n*sizeof(T));
}
static void move_n (T* src, T* dst, size_t n) {
// move n items onto n other ones
assert(src != dst);
std::memcpy((void*)dst, (void*)src, n*sizeof(T));
}
static void move_init_n (T const* src, void* dst, size_t n) {
// move n items onto uninitialized storage
assert(src != dst);
std::memcpy(dst, (void*)src, n*sizeof(T));
}
};