您的位置:首页 > 其它

《STL源码剖析》读书笔记--第二章 空间配置器(allocator)

2007-09-30 14:12 239 查看
第二章 空间配置器(allocator)

2.1 空间配置器的标准接口

根据STL的规范,以下是allocator的必要接口

allocator的标准接口说明
allocator::value_type

allocator::pointer

allocator::const_pointer

allocator::reference

allocator::const_reference

allocator::size_type

allocator::difference_type

各种type
rebind(class)一个nested的class template.class rebind<U>拥有唯一的成员other,那是一个typedef,代表allcator<U>
allocator(),allocator(const allocator&),~allocator  
template<class U>allocator(const allocator<U>&)泛化的copy constructor
pointer allocator::address(reference x) const
const_pointer allocator::address(const_reference x) const

传回某个物件的地址。a.address(x)=&x
pointer allocator::allocate(size_type n,const void*=0)配置n个T物件,第二个参数一般忽略
void allocator::deallocate(pointer p,size_type n)归还先前的空间
void allocator::construct(pointer p,const T& x)等同于new ((const void*)p)T(x)
void allocator::destroy(pointer p)等同于p->~T()
2.1.1 设计一个简单的空间配置器

//file: 2jjalloc.h

#ifndef _JJALLOC_

#define _JJALLOC_

#include <cstddef> //for ptrdiff_t,size_t

namespace JJ

{

template <class T>

inline T* _allocate(ptrdiff_t size,T*)

{

set_new_handle(0);//当new失败时,呼叫该函数所设的参数

T* tmp=(T*)(::operator new((size_t)(size*sizeof(T))));

if (tmp==0)

{

exit(1);

}

return tmp;

}

template <class T>

inline void _deallocate(T* buffer)

{

::operator delete(buffer);

}

 

template <class T1,class T2>

inline void _construct(T1* p, const T2& value)

{

new(p) T1(value);

}

template <class T>

inline void _destroy(T* ptr)

{

ptr->!T();

}

 

template <class T>

class allocator

{

public:

typedef T value_type;

typedef T* pointer;

typedef const T* const_pointer;

typedef T& reference;

typedef const T& const_reference;

typedef size_t size_type;

typedef ptrdiff_t difference_type;

 

template <class U> struct rebind

{typedef allocator<U> other;}

pointer allocate(size_type n, const void* hint=0)

{

return _allocate((difference_type)n,(pointer)0);

}

void deallocate(pointer p,size_type n) {_deallocate(p);}

void construct(pointer p,const t& value)

{

_construct(p,value);

}

void destroy(pointer p) {_destroy(p);}

pointer address(reference x) {return (pointer)&x;}

const_pointer const_address(const_reference x) {return (const_pointer)&x;}

size_type max_size() const

{return size_type(UINT_MAX/sizeof(T));}

}

}

2.2 具备次配置能力(sub allocation)的SGI空间配置器

SGI STL的配置器与众不同,与标准规范不同,其名称为alloc,而非allocator,并且不接收任何引数(所谓引数,即是template<T>中的T),换句话说,既不能使用标准写法:

vector<int,std::allocator<int>>iv; //in VC

必须这么写:

vector<int,std::alloc> iv;

2.2.1 SGI标准的空间配置器,std::allocator

虽然SGI也定义了一个符合部分标准的allocator,但SGI从未用过它,也不主张我们使用。

其实现和我们上面所列的code类似

 

2.2.2 SGI特殊的空间配置器,std::alloc



2.2.3 建模和解构的基本工具:construct()和destroy()

typename语法

#ifndef __SGI_STL_INTERNAL_CONSTRUCT_H
#define __SGI_STL_INTERNAL_CONSTRUCT_H

#include <new.h>

__STL_BEGIN_NAMESPACE

template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value);
}

template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for ( ; first < last; ++first)
destroy(&*first);
}

template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first, last, trivial_destructor());
}

template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}

inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

__STL_END_NAMESPACE

#endif



之所以需要增加trivial_destructor的特化是因为如果该类别的析构无关痛痒,反复的循环调用是很耗费效率的.

特化后,如为true_type,则什么都不做

2.2.4 空间的配置与释放,std::alloc





2.2.5 第一级配置器 __malloc_alloc_template剖析

template <int inst>
class __malloc_alloc_template {

private:
//以下为几个函数指针
static void *oom_malloc(size_t); //OOM=out of memory

static void *oom_realloc(void *, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif

public:
//SGI STL并没有使用Set_new_hanlde的机制,因为它并没有使用new的方法,而是把两步分解开来执行。
static void * allocate(size_t n)
{
void *result = malloc(n);
if (0 == result) result = oom_malloc(n);
return result;
}

static void deallocate(void *p, size_t /* n */)
{
free(p);
}

static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz);
if (0 == result) result = oom_realloc(p, new_sz);
return result;
}

static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}

};

// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif

template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)();
void *result;

//不断的循环尝试释放,分配,期望在一次中得到正确处理
for (;;)

{
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = malloc(n);
if (result) return(result);
}
}

template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{
void (* my_malloc_handler)();
void *result;

for (;;) {
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = realloc(p, n);
if (result) return(result);
}
}

typedef __malloc_alloc_template<0> malloc_alloc;

Reference:

1.Effective C++ 2nd 条款7:为内存不足的状况预做准备 025
Be prepared for out-of-memory conditions.

2.Memory Pool(内存池)

 

2.2.6 第二级配置器 __default_alloc_template剖析

我们知道,如果反复的分配一小块内存,然后释放,会造成很多的碎片。

因此,第二级配置器对此做出了调整,如果分配的区块够大(大于128byte),这直接移交第一级配置器处理。

如果小于,则使用内存池(memory pool,又称记忆池)管理,此法也称为次层配置(sub-allocation)。

其方法为:

1.每次配置一大块记忆体,并维护与之对应的free-lists(自由串列)。

2.若下次,再有相同大小的内存分配需求,则直接再此free-lists上分配。

3.如果客户端释放,则由配置器回收到free-lists中.---别忘了,配置器不光负责分配,而且负责回收

4.为了方便管理,SGI第二级分配器要求每次分配的为8的倍数

template <bool threads, int inst>
class __default_alloc_template
{
private:
enum {__ALIGN = 8}; //小型区块的最小边界
enum {__MAX_BYTES = 128}; //小型区块的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};//free-lists个数

static size_t ROUND_UP(size_t bytes)

{return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)); //好的方法,比(bytes+_ALIGN-1)*_ALIGN/_ALIGN 效率高
}

PRIVATE:
union obj

{
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
}; //妙哉!为了维护串列,需要额外开销指针的空间。

//但使用Union就可以做到节省空间的一物两用
private:
static obj * __VOLATILE free_list[];

static size_t FREELIST_INDEX(size_t bytes) //根据区块的大小,决定使用第几个free-lists
{
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
}

// Returns an object of size n, and optionally adds to size n free list.
static void *refill(size_t n);

// Allocates a chunk for nobjs of size "size". nobjs may be reduced
// if it is inconvenient to allocate the requested number.
static char *chunk_alloc(size_t size, int &nobjs);

// Chunk allocation state.
static char *start_free;//记忆池起始位置,只在chunk_alloc中变化
static char *end_free;//记忆池结束位置,只在chunk_alloc中变化
static size_t heap_size;

public:

static void * allocate(size_t n);

static void deallocate(void *p, size_t n);

static void * reallocate(void *p, size_t old_sz, size_t new_sz);

}

 

//以下几个static变量的定义

template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;

template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;

template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * __VOLATILE
__default_alloc_template<threads, inst> ::free_list[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

2.2.7 空间配置函数allocate()

如果大于128,扔给第一级分配器

/* n must be > 0 */
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;

if (n > (size_t) __MAX_BYTES)

{
return(malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n);

result = *my_free_list;
if (result == 0)

{
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result -> free_list_link;
return (result);
};



2.2.8 空间释放函数deallocate()

如果大于128,扔给第一级分配器

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;

if (n > (size_t) __MAX_BYTES)

{
malloc_alloc::deallocate(p, n);
return;
}
my_free_list = free_list + FREELIST_INDEX(n);
q -> free_list_link = *my_free_list;
*my_free_list = q;
}



2.2.9 重新填充free-lists

使用chunk_alloc分配空间,默认分配20个。如记忆体空间不足,可能个数比这个少。

/* Returns an object of size n, and optionally adds to size n free list.*/
/* We assume that n is properly aligned. */
/* We hold the allocation lock. */
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20;
char * chunk = chunk_alloc(n, nobjs);
obj * __VOLATILE * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;

if (1 == nobjs) return(chunk); //如果只分配了一个,可以直接返回
my_free_list = free_list + FREELIST_INDEX(n);

/* Build free list in chunk */
result = (obj *)chunk;
*my_free_list = next_obj = (obj *)(chunk + n);
for (i = 1; ; i++) //形成链表结构

{
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + n);
if (nobjs - 1 == i)

{
current_obj -> free_list_link = 0;
break;
}

else

{
current_obj -> free_list_link = next_obj;
}
}
return(result);
}

2.2.10 记忆池(memory pool)

虽然S

/* We allocate memory in large chunks in order to avoid fragmenting */
/* the malloc heap too much. */
/* We assume that size is properly aligned. */
/* We hold the allocation lock. */

//注意nobjs为Reference型
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
char * result;
size_t total_bytes = size * nobjs; //需要分配的大小
size_t bytes_left = end_free - start_free; //目前遗留的大小

if (bytes_left >= total_bytes) //如果能够满足,直接分配,返回

{
result = start_free;
start_free += total_bytes;
return(result);
}

else if (bytes_left >= size) //如果大于一个的大小,则能分配几个,就分配几个

{
nobjs = bytes_left/size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
}

else //如果一个都不够了

{

// Try to make use of the left-over piece.
if (bytes_left > 0) //如果原有的还有一些剩余的空间,先把它分配出去

{
obj * __VOLATILE * my_free_list =free_list + FREELIST_INDEX(bytes_left);

((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}

//准备重新分配20+n的大小,n的大小取决于不断变化的heap_size

size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);//这句话,原文放在if(bytes_left>0)之前
start_free = (char *)malloc(bytes_to_get);//调用malloc分配20+n的空间

if (0 == start_free) //如果非常的不幸,分配失败了,内存空间不够

{
int i;
obj * __VOLATILE * my_free_list, *p;
// Try to make do with what we have. That can't
// hurt. We do not try smaller requests, since that tends
// to result in disaster on multi-process machines.

//寻找原有分配好的,比当前区块大的区块,是否有剩余空间,如果有,分配一个出去
for (i = size; i <= __MAX_BYTES; i += __ALIGN)

{
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (0 != p) //抓到了一个

{
*my_free_list = p -> free_list_link;//原来拥有的,释放掉一个
start_free = (char *)p;//start_free,end_free,抓一个大小。
end_free = start_free + i;
return(chunk_alloc(size, nobjs));//重新调用分配
// Any leftover piece will eventually make it to the
// right free list.
}
}

//苍天呀,大地啊!一个都没有了,山穷水尽。呼叫第一层的alllocate,

//它和直接呼叫allocate的做法唯一的不同在于拥有exception的处理

//期望着Exception能够解决我们的问题
end_free = 0; // In case of exception.
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// This should either throw an
// exception or remedy the situation. Thus we assume it
// succeeded.
}
heap_size += bytes_to_get; //重新设定堆的大小
end_free = start_free + bytes_to_get;//设定分配空间的大小
return(chunk_alloc(size, nobjs));//根据设定过的值,重新呼叫自身
}
}

实例操作,第一次chunk_alloc(32,20),分配了40+n个,一个返回。19个给free_list[3]控制,剩下的20+n给记忆池

第二次chunk_alloc(64,20),此时不够20个,分配给其10个,然后返回一个,9个由free_list[7]控制。记忆池空了

第三次chunk_alloc(96,20),呼叫重新分配新空间



2.3 记忆体基本处理工具

作用于非初始化空间上的函数,除了以上介绍过的construct()和destroy(),下面介绍的是另外的三个。

分别对应于STL的算法copy(),fill(),fill_n()

三者都是Commit or Roll-back,意即如果成功就全部分配,否则一个都不成功

2.3.1 uninitialized_copy

template <class InputIterator, class ForwardIterator>
inline ForwardIterator __uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result)

2.3.2 uninitialized_fill

template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x)

2.3.3 uninitialize_fill_n

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,const T& x)

 (1)uninitialize_fill_n实做

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x)

{
return __uninitialized_fill_n(first, n, x, value_type(first));//利用value_type,取出first的所指型别
}

|

|

//

template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n,const T& x, T1*)

{
typedef typename __type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first, n, x, is_POD()); //判断型别是否是POD型别
}

所谓POD型别,plain old data.也就是指纯量型别(scalar types)或传统的C struct型别。

POD型别,必然拥有trival ctor/dtor/copy/assignment函数。

因此,我们可以对POD类型采取最有效的初值填写方法

而对non-POD类型需采取最安全的做法

|

|

//

// Valid if copy construction is equivalent to assignment, and if the
// destructor is trivial.
template <class ForwardIterator, class Size, class T>
inline ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n,const T& x, __true_type)

{
return fill_n(first, n, x);
}

template <class ForwardIterator, class Size, class T>
ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n,const T& x, __false_type)

{
ForwardIterator cur = first;
for ( ; n > 0; --n, ++cur)
construct(&*cur, x);//调用constuct,初始化cur中的内容为x
return cur;
}

(2)uninitialized_copy实做

template <class InputIterator, class ForwardIterator>
inline ForwardIterator uninitialized_copy(InputIterator first, InputIterator last,ForwardIterator result)

{
return __uninitialized_copy(first, last, result, value_type(result));
}

template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator __uninitialized_copy(InputIterator first, InputIterator last,ForwardIterator result, T*)

{
typedef typename __type_traits<T>::is_POD_type is_POD;
return __uninitialized_copy_aux(first, last, result, is_POD());
}

template <class InputIterator, class ForwardIterator>
inline ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last,ForwardIterator result,__true_type)

{
return copy(first, last, result);
}

template <class InputIterator, class ForwardIterator>
ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last,ForwardIterator result,__false_type)

{
ForwardIterator cur = result;
for ( ; first != last; ++first, ++cur)
construct(&*cur, *first);
return cur;
}

对于char*和wchar_t*,可以使用最有效的memmove办法

inline char* uninitialized_copy(const char* first, const char* last, char* result)

{
memmove(result, first, last - first);
return result + (last - first);
}

inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_t* last,wchar_t* result)

{
memmove(result, first, sizeof(wchar_t) * (last - first));
return result + (last - first);
}

(3)uninitialized_fill实做

template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x)

{
__uninitialized_fill(first, last, x, value_type(first));
}

template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x, T1*)

{
typedef typename __type_traits<T1>::is_POD_type is_POD;
__uninitialized_fill_aux(first, last, x, is_POD());
}

template <class ForwardIterator, class T>
inline void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __true_type)
{
fill(first, last, x);
}

template <class ForwardIterator, class T>
void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __false_type)
{
ForwardIterator cur = first;
for ( ; cur != last; ++cur)
construct(&*cur, x);
}

所有的Non-Pod函数的false_type都是非inline的





typename

一、MSDN:C++ Specific —>

typename identifier;

Use this keyword only in template definitions. This keyword tells the compiler that an unknown identifier is a type. For example:

template<class T> class X {
typename T::Y;      // treat Y as a type

Y m_y;
};


This keyword can also be used in place of class in template parameter lists. For example, the following statements are identical:

template<class T1, class T2>...
template<typename T1, typename T2>...


 

二、

2005-12-05 09:14 作者: fatalerror99 出处: BLOG 责任编辑:方舟

  问题:在下面的 template declarations(模板声明)中 class 和 typename 有什么不同?

C++箴言:理解typename的两个含义

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"
  答案:没什么不同。在声明一个 template type parameter(模板类型参数)的时候,class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 class,因为它更容易输入。其他人(包括我本人)更喜欢 typename,因为它暗示着这个参数不必要是一个 class type(类类型)。少数开发者在任何类型都被允许的时候使用 typename,而把 class 保留给仅接受 user-defined types(用户定义类型)的场合。但是从 C++ 的观点看,class 和 typename 在声明一个 template parameter(模板参数)时意味着完全相同的东西。

  然而,C++ 并不总是把 class 和 typename 视为等同的东西。有时你必须使用 typename。为了理解这一点,我们不得不讨论你会在一个 template(模板)中涉及到的两种名字。

  假设我们有一个函数的模板,它能取得一个 STL-compatible container(STL 兼容容器)中持有的能赋值给 ints 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数,而且就像我下面写的,它甚至不能编译,但是请将这些事先放在一边——有一种方法能发现我的愚蠢:

template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
 // this is not valid C++!
 if (container.size() >= 2) {
  C::const_iterator iter(container.begin()); // get iterator to 1st element
  ++iter; // move iter to 2nd element
  int value = *iter; // copy that element to an int
  std::cout << value; // print the int
 }
}
  我突出了这个函数中的两个 local variables(局部变量),iter 和 value。iter 的类型是 C::const_iterator,一个依赖于 template parameter(模板参数)C 的类型。一个 template(模板)中的依赖于一个 template parameter(模板参数)的名字被称为 dependent names(依赖名字)。当一个 dependent names(依赖名字)嵌套在一个 class(类)的内部时,我称它为 nested dependent name(嵌套依赖名字)。C::const_iterator 是一个 nested dependent name(嵌套依赖名字)。实际上,它是一个 nested dependent type name(嵌套依赖类型名),也就是说,一个涉及到一个 type(类型)的 nested dependent name(嵌套依赖名字)。

  print2nd 中的另一个 local variable(局部变量)value 具有 int 类型。int 是一个不依赖于任何 template parameter(模板参数)的名字。这样的名字以 non-dependent names(非依赖名字)闻名。(我想不通为什么他们不称它为 independent names(无依赖名字)。如果,像我一样,你发现术语 "non-dependent" 是一个令人厌恶的东西,你就和我产生了共鸣,但是 "non-dependent" 就是这类名字的术语,所以,像我一样,转转眼睛放弃你的自我主张。)

  nested dependent name(嵌套依赖名字)会导致解析困难。例如,假设我们更加愚蠢地以这种方法开始 print2nd:

template<typename C>
void print2nd(const C& container)
{
 C::const_iterator * x;
 ...
}
  这看上去好像是我们将 x 声明为一个指向 C::const_iterator 的 local variable(局部变量)。但是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type(类型)。但是如果 C::const_iterator 不是一个 type(类型)呢?如果 C 有一个 static data member(静态数据成员)碰巧就叫做 const_iterator 呢?再如果 x 碰巧是一个 global variable(全局变量)的名字呢?在这种情况下,上面的代码就不是声明一个 local variable(局部变量),而是成为 C::const_iterator 乘以 x!当然,这听起来有些愚蠢,但它是可能的,而编写 C++ 解析器的人必须考虑所有可能的输入,甚至是愚蠢的。

  直到 C 成为已知之前,没有任何办法知道 C::const_iterator 到底是不是一个 type(类型),而当 template(模板)print2nd 被解析的时候,C 还不是已知的。C++ 有一条规则解决这个歧义:如果解析器在一个 template(模板)中遇到一个 nested dependent name(嵌套依赖名字),它假定那个名字不是一个 type(类型),除非你用其它方式告诉它。缺省情况下,nested dependent name(嵌套依赖名字)不是 types(类型)。(对于这条规则有一个例外,我待会儿告诉你。)

  记住这个,再看看 print2nd 的开头:

template<typename C>
void print2nd(const C& container)
{
 if (container.size() >= 2) {
  C::const_iterator iter(container.begin()); // this name is assumed to
  ... // not be a type
  这为什么不是合法的 C++ 现在应该很清楚了。iter 的 declaration(声明)仅仅在 C::const_iterator 是一个 type(类型)时才有意义,但是我们没有告诉 C++ 它是,而 C++ 就假定它不是。要想转变这个形势,我们必须告诉 C++ C::const_iterator 是一个 type(类型)。我们将 typename 放在紧挨着它的前面来做到这一点:

template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
  通用的规则很简单:在你涉及到一个在 template(模板)中的 nested dependent type name(嵌套依赖类型名)的任何时候,你必须把单词 typename 放在紧挨着它的前面。(重申一下,我待会儿要描述一个例外。)

  typename 应该仅仅被用于标识 nested dependent type name(嵌套依赖类型名);其它名字不应该用它。例如,这是一个取得一个 container(容器)和这个 container(容器)中的一个 iterator(迭代器)的 function template(函数模板):

template<typename C> // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required
  C 不是一个 nested dependent type name(嵌套依赖类型名)(它不是嵌套在依赖于一个 template parameter(模板参数)的什么东西内部的),所以在声明 container 时它不必被 typename 前置,但是 C::iterator 是一个 nested dependent type name(嵌套依赖类型名),所以它必需被 typename 前置。

  "typename must precede nested dependent type names"(“typename 必须前置于嵌套依赖类型名”)规则的例外是 typename 不必前置于在一个 list of base classes(基类列表)中的或者在一个 member initialization list(成员初始化列表)中作为一个 base classes identifier(基类标识符)的 nested dependent type name(嵌套依赖类型名)。例如:

template<typename T>
class Derived: public Base<T>::Nested {
 // base class list: typename not
 public: // allowed
  explicit Derived(int x)
  : Base<T>::Nested(x) // base class identifier in mem
  {
   // init. list: typename not allowed
 
   typename Base<T>::Nested temp; // use of nested dependent type
   ... // name not in a base class list or
  } // as a base class identifier in a
  ... // mem. init. list: typename required
};
  这样的矛盾很令人讨厌,但是一旦你在经历中获得一点经验,你几乎不会在意它。

  让我们来看最后一个 typename 的例子,因为它在你看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator(迭代器)的 function template(函数模板),而且我们要做一个 iterator(迭代器)指向的 object(对象)的局部拷贝 temp,我们可以这样做:

template<typename IterT>
void workWithIterator(IterT iter)
{
 typename std::iterator_traits<IterT>::value_type temp(*iter);
 ...
}
  不要让 std::iterator_traits<IterT>::value_type 吓倒你。那仅仅是一个 standard traits class(标准特性类)的使用,用 C++ 的说法就是 "the type of thing pointed to by objects of type IterT"(“被类型为 IterT 的对象所指向的东西的类型”)。这个语句声明了一个与 IterT objects 所指向的东西类型相同的 local variable(局部变量)(temp),而且用 iter 所指向的 object(对象)对 temp 进行了初始化。如果 IterT 是 vector<int>::iterator,temp 就是 int 类型。如果 IterT 是 list<string>::iterator,temp 就是 string 类型。因为 std::iterator_traits<IterT>::value_type 是一个 nested dependent type name(嵌套依赖类型名)(value_type 嵌套在 iterator_traits<IterT> 内部,而且 IterT 是一个 template parameter(模板参数)),我们必须让它被 typename 前置。

  如果你觉得读 std::iterator_traits<IterT>::value_type 令人讨厌,就想象那个与它相同的东西来代表它。如果你像大多数程序员,对多次输入它感到恐惧,那么你就需要创建一个 typedef。对于像 value_type 这样的 traits member names(特性成员名),一个通用的惯例是 typedef name 与 traits member name 相同,所以这样的一个 local typedef 通常定义成这样:

template<typename IterT>
void workWithIterator(IterT iter)
{
 typedef typename std::iterator_traits<IterT>::value_type value_type;

 value_type temp(*iter);
 ...
}
  很多程序员最初发现 "typedef typename" 并列不太和谐,但它是涉及 nested dependent type names(嵌套依赖类型名)规则的一个合理的附带结果。你会相当快地习惯它。你毕竟有着强大的动机。你输入 typename std::iterator_traits<IterT>::value_type 需要多少时间?

  作为结束语,我应该提及编译器与编译器之间对围绕 typename 的规则的执行情况的不同。一些编译器接受必需 typename 时它却缺失的代码;一些编译器接受不许 typename 时它却存在的代码;还有少数的(通常是老旧的)会拒绝 typename 出现在它必需出现的地方。这就意味着 typename 和 nested dependent type names(嵌套依赖类型名)的交互作用会导致一些轻微的可移植性问题。

  Things to Remember

  ·在声明 template parameters(模板参数)时,class 和 typename 是可互换的。

  ·用 typename 去标识 nested dependent type names(嵌套依赖类型名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: