一:trait
1.Fixed Traits
- 主要是构造适应各种类型的函数
- Fixed主要指,一旦定义了这个分离的 trait,则无法在算法中进行改写。
- value trait 是用于针对型别进行 value 方面的操作,如 zero(),它是 fixed trait 和下面的另外一种参数化萃取的一部分。
template <typename T>
class accumulation_traits;
template <>
class accumulation_traits<char> {
public:
typedef int AccT;
static AccT zero() { return 0; }
};
template <>
class accumulation_traits<short> {
public:
typedef int AccT;
static AccT zero() { return 0; }
};
template <>
class accumulation_traits<int> {
public:
typedef long AccT;
static AccT zero() { return 0; }
};
template <>
class accumulation_traits<double> {
public:
typedef double AccT;
//static const double zero = 0.0; //complier error
static AccT zero() { return 0; }
};
template <typename T>
inline
typename accumulation_traits<T>::AccT accum(T const* beg, T const* end)
{
typedef typename accumulation_traits<T>::AccT AccT;
AccT total = accumulation_traits<T>::zero();
while(beg != end){
total += *beg;
++beg;
}
return total;
}
测试代码:
int main()
{
int num[] = {1, 2, 3, 4, 5};
std::cout<<"average="<<accum<int>(&num[0], &num[5])<<std::endl;
char name[] = "templates";
int length = sizeof(name) - 1;
std::cout<<"average="<<accum(&name[0], &name[length])/length<<std::endl;
return 0;
}
上述代码实际上就是针对一个累加函数 accum 使用 trait,使得该函数可以根据相应型别采用相应操作。为什么要针对不同型别采取不同操作呢?因为比如说测试用例中的 char 类型,在累加的过程中很大可能会溢出,那么它就需要以 int 类型来计算,返回时再以 char 类型返回即可。
为了实现这点,我们定义了一个 trait 类——accumulation_traits,针对该类进行不同类型的模板特化。由于我们可以在类外部的通过作用域访问符号检测类内部成员,同理使用 typedef + typename 我们就可以提取出此时 trait 类特化版本的模板类型(这是技巧),然后就可以在 accum 函数中使用该类型了。
我们需要在累加前对初值进行初始化,那么我们可以采用在 trait 特化类中使用进行相应的清零(不能直接使用 T(),因为不确定是否有构造函数),但 class 中不允许对非 int 型 static 变量直接初始化,所以使用一个静态函数,返回强转为该类型 0 值的函数就是最佳的选择。
2.Parameterized Traits(参数化萃取)
上面的萃取称为fixed萃取,因为一旦定义了这个分离的 trait,就
不能在算法中进行改写。然而,有些情况下我们需要对 trait 进行改写。例如:我们偶然发现可以对一组 char 求和,然后需要把他们安全地放在 char 类型的变量里面,而不是经由写好的萃取变为 int 做处理,这没必要且效率不高。
从原则上讲,参数化 trait 主要目的在于:添加一个有缺省值的模板参数,而且该缺省值是由我们前面介绍的 trait 模板决定的。在这种具有缺省值的情况下,用户可以不需要提供这个额外的模板是惨。但是对于特殊用户,你自己就可以定义自己的 trait 方案。唯一不足之处在于:
不能对函数模板预设缺省模板实参!
所以我们通过把算法
实现为一个类,就可以绕开这个不足。这同时说明:除了函数模板,类模板也可以很容易使用 trait。不过,类模板不能对参数进行演绎必须由客端显示提供模板实参(比如stack<Int>),所以可以把它封装为函数来简化接口。
template <typename T,
typename AT = accumulation_traits<T> >
class accum_class {
public:
static typename AT::AccT accum(const T* beg, const T* end) {
typename AT::AccT total = AT::zero();
while(beg != end) {
total += *beg;
++beg;
}
return total;
}
};
template <typename T>
inline
typename accumulation_traits<T>::AccT accum(const T* beg, const T* end)
{
return accum_class<T>::accum(beg, end);
}
template <typename Traits, typename T> //其中第一个参数就是 trait 方案,有默认值,也可由用户提供
inline
typename Traits::AccT accum(const T* beg, const T* end)
{
return accum_class<T, Traits>::accum(beg, end);
}
int main()
{
int num[] = {1, 2, 3, 4, 5};
std::cout<<"average="<<accum(&num[0], &num[5])<<std::endl;
char name[] = "templates";
int length = sizeof(name) - 1;
cout<<"avg"<<accum<accumulation_traits<char> >(&name[0],&name[length])/length<<endl;//accum可以加显示参数,加了就默认对应参数第一个模板参数,也可不加
return 0;
}
二:其他技巧
(以下转自:
C++ Template之技巧性基础知识
1.对于T是自定义类型的,如果存在子类型则需要在模版内部加上typename
示例代码:
1
2
3
4
5
|
template
<
typename
T>
class
Myclass
{
typename
T::SubType *ptr;
//需要加上typename不然编译器可能误解为是静态对象SubType和ptr相乘
};
|
2.类模版里对基类成员函数的调用使用BASE::exit();和this->,避免调用的是外部全局函数,但是在vc6.0上面这条规则是先调用的BASE里面的函数。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <iostream>
#include <string>
#include <vector>
using
namespace
std;
void
exit
()
{
cout <<
"hello world"
<<endl;
}
template
<
typename
T>
class
BaseMyclass
{
public
:
void
exit
()
{
cout <<
"Base"
<<endl;
}
};
template
<
typename
T>
class
Myclass:
public
BaseMyclass<T>
{
public
:
void
foo()
{
exit
();
}
};
int
main()
{
Myclass<
int
> m1;
m1.foo();
return
0;
}
|
3.成员模板,由于两种不同的类型之间的赋值不能使用类本身的接口,所以需要重新设计接口。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <iostream>
#include <string>
#include <vector>
using
namespace
std;
template
<
typename
T>
class
Stack
{
public
:
template
<
typename
T2>
Stack<T>& operator=(Stack<T2>
const
&);
};
template
<
typename
T>
template
<
typename
T2>
Stack<T>& Stack<T>::operator=(Stack<T2>
const
&op2)
{
}
int
main()
{
return
0;
}
|
调用时首先通过显式指定的模板类型实例化一个类,然后通过实例化一个成员函数,只是现在的成员函数是模板函数,然后根据调用的参数实例化成一个成员函数。
4.模板的模板参数,实际上是一个模板类,上面的成员模板实际上是一个模板函数。
代码示例:
1
2
3
4
5
6
|
template
<
typename
T,
template
<
typename
T>
class
CONT = vector>
class
Stack
{
public
:
};
|
5.零初始化,因为模板不知道类型所以我们无法用任何一个常量来初始化模板参数定义的变量。但是可以如下例子进行初始化
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <iostream>
#include <string>
#include <vector>
using
namespace
std;
template
<
typename
T>
void
foo()
{
T x = T();
}
template
<
typename
T>
class
Myclass
{
private
:
T x;
public
:
Myclass():x(T()){}
};
int
main()
{
foo<
int
>();
Myclass<
int
> m1;
return
0;
}
|
6.使用字符串作为函数模板的实参若是引用则需要字符串长度完全匹配
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template
<
typename
T>
inline
T&
const
max(T
const
& a,T
const
& b)
{
return
a > b ? a:b;
}
int
main()
{
cout << max(
"apple"
,
"peach"
)<<endl;
//OK
cout << max(
"apple"
,
"tomato"
)<<endl;
//ERROR
return
0;
}
|
若不为引用则不需要字符串长度完全一样,原因是参数会转换成字符串指针类型,属于相同的类型
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <iostream>
#include <string>
#include <vector>
using
namespace
std;
template
<
typename
T>
inline
T max(T a,T b)
{
return
a > b ? a:b;
}
int
main()
{
cout << max(
"apple"
,
"peach"
)<<endl;
//OK
cout << max(
"apple"
,
"tomato"
)<<endl;
//OK
return
0;
}
|
字符串作为实参的测试示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
template
<
typename
T>
void
ref(T
const
& x,T
const
& y)
{
cout <<
"x int the ref is "
<<
typeid
(x).name() <<endl;
}
template
<
typename
T>
void
nonref(T x)
{
cout <<
"x int the noref is "
<<
typeid
(x).name() <<endl;
}
int
main()
{
ref(
"hello"
);
nonref(
"hello"
);
return
0;
}
|