【C++标准库】:第三章:通用工具
标签(空格分隔):【C++标准库】
文章目录
第三章:通用工具
本章节主要内容为C++标准库的通用工具,包括:1. pair<> 和 tuple<>; 2. smart pointer class( chared_ptr<> 和 unique_ptr); 3. 数值极值(Numeric limit); 4. type tarit 和 type utility; 5. 辅助函数(例如:min(), max());6. class ratio<>; 7. clock 和 time;8. 若干重要的C函数。
3.1 Pair 和 Tuple
C++98 中提供了一个简单的class,用来处理类型不同的两个值,而不需要为它们再定义特别的class. 当标准函数需要返回一个value pair时码或者当容器元素是一个key/value pair时,就会用到该class.
TR1 引入了一个tuple calss, 它延伸了上述概念,接受任意(但有限)个元素。标准库实现保证可移植的范围是:允许tuple带有最多10个类型不同的元素。
C++11, tuple class 被重新实现,采用variadic template概念,此时可以用于任意大小的异质集合(heterogeneous collection). 但此时,calss pair依旧为两个元素服务。
3.1.1 Pair
Class pair 可以将两个 value 视为一个单元。C++标准库内多处用到了这个class, 尤其是容器map, multimap, unordered_map, unordered_multimap. 任何函数如果需要返回两个value,也需要使用pair.
Struct pair 定义于 < utility>, 提供下列各项操作(见下)。原则上可以对pair<> 执行creat, copy/assign/swap 及 compare 操作,此外还提供first_type 和 second_type类型定义式,用来表示第一value和第二value的类型。
- 元素访问:为了让程序能够处理pair的两个值,它提供了“直接访问对应数据成员”的能力。
事实上,由于它是个struct而不是calss,以至于所有成员都是public:
namespace std{
template<typename T1, typename T2>
struct pair{
//member
T1 first;
T2 second;
};
}
操作函数 | 影响 |
---|---|
pair< T1, T2> p | Default 构造函数,建立一个pair,元素类型分别为T1,T2,各自以其defalut构造函数初始化 |
pair< T1, T2> p(val,val) | 建立一个pair,元素类型分别为T1,T2,以val和val为初值 |
pair< T1, T2> p(rv1, rv2) | 建立一个pair,元素类型分别为T1,T2,以rv1和rv2进行搬移式初始化(move initialized) |
pair< T1, T2> p(piecewise_construct,t1,t2) | 建立一个pair,元素类型分别为 tuple T1,T2,以tuple T1和 T2 的元素为初值 |
pair< T1, T2> p(p2) | Copy构造函数,建立p成为p2的拷贝 |
pair< T1, T2> p(rv) | Move构造函数,将rv的内容移至p中(允许隐式类型转换) |
p = p2 | 将p2赋值给p |
p = pv | 将rv的值move assign给p |
p.first | 直接成员访问,获得pair内第一个值 |
p.second | 直接成员访问,获得pair内第二个值 |
get< 0>§ | C++11后,等价于p.first |
get< 1>§ | C++11后,等价于p.second |
p1 == p2 | 等价于 p1.first == p2.first && p1.second == p2.second |
p1 != p2 | 等价于 !(p1==p2) |
p1 < p2 | |
p1 > p2 | |
p1 <= p2 | |
p1 >= p2 | |
p1.swap(p2) | C++11后,互相交换p1和p2的数据 |
swap(p1,p2) | C++11后,互相交换p1和p2的数据 |
make_pair(val1,val2) | 返回一个pair,带有val1, val2的类型和数值 |
例如:
pair<int, double> p(4, 3.14);
cout<<get<0>(p)<<endl; //输出 4
cout<<get<1>(p)<<endl; //输出 3.14
cout<<tuple_size< pair<int, double>>::value<<endl;//输出 2
- 构造函数和赋值:
default构造函数生成一个pair时,以两个“被default构造函数个别初始化”的元素作为初值。
例如:
pair<int, float> p; //iniuutialize p.first and p.second with zero.
//就是以int()和float()来初始化p
copy 构造函数同时存在两个版本:版本1接受相同类型的pair;版本2是一个member template, 在“构造过程中需要隐式类型转换”是被调用。如果pair对象被复制,调用的是被隐式合成的那个copy构造函数。
- 逐块式构造(Piecewise Construction): pair<>提供三个构造函数,用来初始化first和second成员.
namespace std{
template <typename T1, typename T2>
struct pair{
pair(const T1& x,const T2& y); //方法一
template <typename U, typename V> pair(U&&& x, V&& y); //方法二
//方法三
template< typename ... Args1, typename ... Args2> pair(piecewise_construct_t, tupe<Args1...> first_args, tuple<Args2...> second_args);
};
}
- 便携函数make_pair(): template函数make_pair() 使得程序员无须写出类型就能生成对象。
例如,以下两种形式是等价的。
pair<int, double>(4, 3.14);
make_pair(4, 3.14);
//自C++11后,也可以使用初值列(initializer list);
{4, 3.14}
- pair之间的比较:两个pair对象内的所有元素相等,这两个pair对象才被视为相等(equal).
namespace std{
template<typename T1, typename T2>
bool operator== (const pair<T1, T2>& x, const pair<T1, T2>& y){
return x.first == y.first && x.second == y.second;
}
}
//两个pair互相比较时,first具有较高的优先级,如果第一个元素不相等,则其比较结果直接返回。如果first相等,才继续比较second.
其他的比较操作符(comparison operator)如法炮制。
- pair运用实例:C++标准库中大量使用pair. 例如 (unordered) map 和 multimap容器的元素类型便是pair, 也就是一对key/value; 另外,C++中凡事“必须返回两个value”的函数一般也使用pair对象。
3.1.2 tuple(不定数的值组)
tuple 是 TR1 引入的东西,它扩展了 pair 的概念,拥有任意数量的元素。也就是说,tuple呈现出一个异质元素列(heterogeneous list of elements), 其中每个类型都可以被指定,或者由编译期推导。
然而,由于 TR1使用C++98的语言特性,因此不能定义出一个“参数个数不定”的template,基于这个原因,实现必须具体指明“一个tuple可以拥有的”所有可能的元素个数。TR1对此的建议是至少10个实参,这意味这tuple往往被如下定义:
//T0~T9至少10个类型各异的template参数,每个都带有实现赋予的默认类型。
template<typename T0 =..., ......, typename T9 = ...>
class tuple
自C++11以来,variadic template 被引入,使template可以接受任何数量的template实参,于是< tuple> 中的calss tuple 声明式就被简化为如下:
namespace std{
template Mtypename ...Types>
class tuple;
}
- tuple的操作:原则上,tuple的接受十分直观:1. 通过明白的声明,使用便捷函数make_tuple(), 可以创建一个tuple. 2. 通过get<>() function template, 可以访问tuple的元素。
例如:
//创造一个异质的四元素tuple, 每个元素的内容由default构造函数初始化。
tuple<string, int , int, double> t1;
//显式创造并初始化一个异质的三元素tuple
tuple<int, float, string> t2( 41, 6.3, "test");
cout<<get<0>(t2)<<" ";//输出为41;
cout<<get<1>(t2)<<" ";//输出为6.3;
cout<<get<2>(t2)<<" ";//输出为test;
//使用make_tuple创建一个tuple
auto t3 = make_tuple(22,44,"test2");
//比较和赋值
if( t2 < t3)
t2 = t3; //It's Ok,
//tuple的元素类型可以是reference, 例如;
string s = "test3";
tuple<string&> t4(s);
cout<<get<0>(t4)<<" ";//输出为test3
get<0>(t4) = "modify";
cout<<get<0>(t4)<<" ";//输出为modify
//tuple不是寻常容器,不允许迭代元素,因此必须在编译期知道待处理元素的索引值
int i; get<i>(t2);// compile-time ERROR: i is no compile-time value;
操作函数 | 影响 |
---|---|
tuple< T1, T2,…,Tn> t | 以n个给定类型的元素建立一个tuple, 每个元素的内容由default构造函数初始化 |
tuple< T1, T2,…,Tn> t(v1,v2,…,vn) | 以n个给定类型的元素建立一个tuple, 以给定值进行初始化 |
tuple< T1, T2> t§ | 建立一个tuple, 带有两个元素,分别使用给定的类型,并以给定的pair p为初值(类型必须吻合) |
t = t2 | 将 t2 赋值给 t |
t = p | 并以给定的pair p为初值(类型必须吻合) |
t1 == t2 | 如果所有元素比较相则返回true, 否则返回false |
t1 != t2 | |
t1 > t2 | |
t1 < t2 | |
t1 >= t2 | |
t1 <= t2 | |
t1.swap( t2) | 自C++11后,互换t1, t2数据 |
swap(t1,t2) | 同上 |
make_tuple(v1,v2,…) | 以传入的所有数值和类型建立一个tuple,并允许由此tuple, 并允许由此tuple提供数值 |
tie( ref1, ref2,…) | 建立一个由reference构成的tuple, 并允许由此tuple提取个别数值 |
-
便捷函数make_tuple() 和 tie()
便捷函数make_tuple() 会依据value建立tuple,不需要明确指明元素的类型。可见上例。
借由特比的函数对象reference_wrapper<> 及便捷函数ref() 和 cref() (自C++11起并定义于),可以影响make_tuple()产生的类型,也可以改变tuple内的值。如果想最方便地在tuple中使用reference, 可以选择tie(), 它可以建立一个内含有reference的tuple:
tuple< int, float, string> t(77, 1.1, "test");
int i; float f; string s;
tie(i,f,s) = t;//以i,f,s的reference建立一个tuple,因此该赋值操作就是将t内的元素分别给i,f,s
cout<<i<<" "<<f<<" "<<s; //输出 77 1.1 test
- tuple 和 初值列(initializer list)
各个构造函数中,“接受不定个数的实参”的版本被声明为explicit:
namespace std{
template <typename ...Types>
class tuple{
public:
explicit tuple( const Types& ...);
template<typename ...UTypes> explicit tuple(UTypes&&...)
}
}
//例:
tuple<int, double> t1(42, 3.14); //Ok, old syntax
tuple<int, double> t2{42, 3.14}; //Ok, new syntax
tuple<int, double> t3 = {42, 3.14}; //ERROR
vector< tuple<int,float>> v{{1,1.0},{2,2.0}};//ERROR
tuple<int, int> foo(){
return [1,2}; //ERROR
}
vector< tuple<int,float>> v{make_tuple(1,1.0)};//OK
tuple<int, int> foo(){
return {1,2}; //OK
}
- 其他的tuple特性:
- tuple_size< tupletype>::value 获得元素个数.
- tuple_element< idx, tupletype>::type 获得第idx个元素的类型.
- tuple_cat() 将多个tuple串接成一个tuple
例如:
tuple<int, float, string > test;
tuple_size<test>::vaule; //输出3
tuple_element<1,test>::type; //float
-
tuple的输入与输出
tuple class最初公开与Boost程序库,在其中,tuple可以将其元素写到output stream, 但是在C++标准库并不支持这项操作,但如果拥有util/printtuple.hpp
,就可以使用操作符<<来打印tuple. -
tuple与pair的转化:可以用pair作为初值来初始化一个双元素tuple; 也可以将一个pair赋值给一个双元素tuple.
3.2 Smart Pointer( 智能指针)
pointer 十分重要,但也会带来麻烦。pointer可以在作用域边界之外拥有reference的语义。但是,维持pointer寿命及所指向对象的寿命却十分棘手。
解决方法:使用smart pointer. 称为smart的原因是该指针可以知道自己是否是指向某个对象的嘴个一个指针,并运用该点来决定在适当的时刻是否销毁自身。
C++11起,C++标准库提供了两大类型的smart pointer: 1. class shared_ptr 实现的共享式拥有(shared ownership) 的概念。多个smart pointer可以指向相同的对象,该对象和其相关资源会在“最后一个reference被销毁”时释放。为了在结构比较复杂的情景中执行上述工作,标准库提供了 weak_ptr, bad_weak_ptr 和 enable_shared_from_this 等辅助类。2. class unique_ptr 实现独占式拥有(exclusive ownership) 或称为 严格拥有(strict ownership) 概念,保证同一时间只有一个smart pointer 可以指向对象,可以交接拥有权,可以避免资源泄漏。
3.2.1 class shared_ptr
几乎每个程序都需要具有“在相同时间的多处地点处理或使用对象”的能力。为此,必须在程序的多个地点指向同一个对象,虽然C++提供了reference, 但为了确保“指向对象”的最后一个reference被删除时对象本身也被删除,所以需要“当对象不再被使用时就被清理”的语义。
class shared_ptr 提供了这样的共享式拥有语义,即多个 shared_ptr 可以拥有同一个对象,对象的最末一个有责任销毁对象,并清理与该对象相关的所有资源。
如果对象以new产生,则默认情况下清理工作由delete完成,但也可以定义其他的清理方法。
- 使用 shared_ptr:可以像任何其他pointer一样使用 shared_ptr, 可以赋值、拷贝、比较它们,也可以使用操作符 * 或 -> 来访问其所指向的对象。
例如:
//声明两个shared_ptr并各自指向string
shared_ptr<string> pNico( new string("nico"));
shared_ptr<string> pJutta( new string("jutta"));
//注意,由于“接受单一pointer作为唯一实参“的构造函数是explicit,所以形如以下
//shared_ptr<string> pNico = new string("nico"); //ERROR
//就像使用寻常pointer的用法
(*pNico)[0] = 'N'; //运用operator *, 取得pNico指向的对象
pJutta->replace(0, 1, "J"); //运用 operator ->, 取得pJutta指向的对象内第一个成员
//多次安插以上两个pointer.
//容器为c传入的元素创造属于容器自己的拷贝
vector< shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
for( auto ptr:whoMadeCoffee)
cout<< *ptr<<endl;//按插入顺序输出
*pNico = "Nicolai"; //该对象所有身影都转换为新的值"Nicolai".
//针对vector内第一个shared pointer 调用use_count(), 输出该shared pointer所指向对象当前拥有着的数量
//vector中第一个元素所指向的对象有四个主人:vector中有三个,pJutta本身一个。
cout<<endl<<"use_count = "<<whoMadeCoffee[0].use_count()<<endl;//输出use_count = 4
- 定义一个delete:可以声明属于自己的delete.
例如:
//例如:让它在“删除被指向对象”之前先打印一条信息
shared_ptr<string> pNico( new string("nico"), [](string* p){
cout<<" delete "<<*p<<endl;
delete p;
}
);
shared_ptr<string> pNico( new string("nico"));
pNico = nullptr;//此会输出一个 delete nico.
- 对付array: shared_ptr 提供的 default deleter 调用的是 delete, 而不是 delete[], 这意味着只有当shared pointer拥有"有new建立起来的但一对象", default deleter 才能适才适所。
很不幸,为array建立一个 shared_ptr 是可能的,但是确是错误的:
shared_ptr<int> p( new int[10]); //ERROR, but compiles
因此,如果使用new[]来建立一个array of object, 必须定义自己的deleter.
例如:
//传递一个函数、函数对象或lambda, 让它们针对传入的寻常指针调用delete[].
shared_ptr<int> p( new int[10], [](int* p){
delete[] p;
}
);
3.2.2 class week_ptr
class weak_ptr 允许“共享但不拥有”某对象。该class会建立起一个shared pointer, 一旦最后一个拥有该对象的shared pointer 失去了拥有权,任何 weak pointer 都会自动成空。
因此,除了defalut和copy构造函数之外,class weak_ptr 只能提供“接受一个shared_ptr”的构造函数。
weak_ptr指向的对象,不能使用操作符* 和 -> 访问,而是必须建立一个shared_ptr,这是因为:1. 在weak pointer 之外建立一个shared pointer 可因此检查是否存在一个相应对象。2. 当指向的对象正被处理时,shared pointer 无法被释放
因此,class weak_ptr 只能提供小量操作,只能够用来创建、赋值、赋值 weak pointer, 以及转换为一个shared pointer, 或检查自己是否指向某个对象。
- 使用 week_ptr:
//当使用shared_ptr时
class Person{
//拥有一个name和若干reference指向其他Person
public:
string name;
shared_ptr<Person> mother;
shared_ptr<Person> father;
vector<shared_ptr<Person>> kids;
Person( const string& n, shared_ptr<Person> m = nullptr, shared_ptr<Person> f = nullptr):name(n), mother(m), father(f){
}
~Person(){
cout<<" delete "<<name<<endl;
}
};
shared_ptr<Person> initFamily( const string& name){
//建立三个Person: mom dad kid, 根据传入实参将所有姓名初始化
shared_ptr<Person> mom( new Person( name + "'s mom"));
shared_ptr<Person> dad( new Person( name + "'s dad"));
shared_ptr<Person> kid( new Person( name, mom, dad));
//设定kid的父母,并将kid插入到对应父母的kids容器内部
mom->kids.push_back(kid);
dad->kids.push_back(kid);
//返回kid
return kid;
}
/*-----------------------------*/
int main() {
shared_ptr<Person> p = initFamily("nico");
//p是指向上述家庭的最后一个handle. 在内部,每个Person对象都有reference从kid指向其父母以及反向指向。
cout<<" nico's family exists"<<endl;
cout<<" - nico is shared "<<p.use_count()<<" times "<<endl;
cout<<" - name of 1st kid of nicos mom: "<<p->mother->kids[0]->name<<endl;
p = initFamily("jim");
cout<<" jim's family exists "<<endl;
//输出
//nico's family exists
//- nico is shared 3 times
//- name of 1st kid of nicos mom: nico
//jim's family exists
return 0;}
//使用weak_ptr时, 注意 vector中的指针变化
class Person{
public:
string name;
shared_ptr<Person> mother;
shared_ptr<Person> father;
vector<weak_ptr<Person>> kids; // weak_ptr
Person( const string& n, shared_ptr<Person> m = nullptr, shared_ptr<Person> f = nullptr):name(n), mother(m), father(f){
}
~Person(){
cout<<" delete "<<name<<endl;
}
};
//main函数中第四行改为 “cout<<" - name of 1st kid of nicos mom: "<<p->mother->kids[0].lock()->name<<endl;”
//执行main,得到:
// nico's family exists
// - nico is shared 1 times
// - name of 1st kid of nicos mom: nico
// delete nico
// delete nico's dad
// delete nico's mom
// jim's family exists
// delete jim
// delete jim's dad
// delete jim's mom
- 误用shared pointer
虽然shared_ptr 强化了程序安全,但是由于对象的相应资源往往被自动释放,当对象不再被使用时可能出现问题。例如:由于循环依赖(cyclic dependency) 造成"dangling pointer"(空荡指针)。
另外,必须保证某个对象只能被一组shared pinter拥有。例如一下代码是错误的:
int* p = new int;
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);
//ERROR, 当sp1 和 sp2 都会在丢失p的拥有权时候调用delete. 这意味着相应的资源会被释放两次
3.2.4 细究 shared pointer 和 weak pointer
- 细究 shared pointer : class shared_ptr 提供的是带有“共享式拥有”语义的smart pointer概念,无论何时当shared pointer 的最后一个拥有着被销毁,相应对象就会被delete.
class shared_ptr<> 被模版化,模版参数是“原始pointer所指对象”的类型:
namespace std {
template <typename T>
class shared_ptr{
public:
typedef T element_type;
//...
};
}
//元素类型可以是void, 意味着 shared pointer 共享的对象又一个未具体说明的类型,例如void* .
一个empty shared_ptr 并不能分享对象拥有权,所以 use_count() 返回0. 然而,由于设计了一个特殊构造函数,使得empty shared pointer 还可以指向对象。
下表给出shared pointer 提供的所有操作。
操作 | 效果 |
---|---|
shared_ptr< T> sp | default构造函数,建立一个empty shared pointer, 使用 default deleter(即delete) |
shared_ptr< T> sp(ptr) | 建立一个shared pointer, 令其拥有 *ptr, 使用 default deleter(即delete) |
shared_ptr< T> sp( ptr, del) | 建立一个shared pointer, 令其拥有 *ptr, 使用del作为 deleter |
shared_ptr< T> sp( ptr, del, ac) | 建立一个shared pointer, 令其拥有 *ptr, 使用del作为 deleter并以ac为allocator |
shared_ptr< T> sp(nullptr) | 建立一个empty shared pointer, 使用 default deleter(即delete) |
shared_ptr< T> sp(nullptr, del) | 建立一个empty shared pointer, 使用del作为 deleter |
shared_ptr< T> sp(nullptr, del, ac) | 建立一个empty shared pointer, 使用del作为 deleter并以ac为allocator |
shared_ptr< T> sp(sp2) | 建立一个shared pointer, 与sp2共享拥有权 |
shared_ptr< T> sp(move(sp2)) | 建立一个shared pointer, 拥有sp2先前拥有的pointer, 之后将sp2改为empty |
shared_ptr< T> sp(sp2, ptr) | Alias构造函数,建立一个shared pointer, 与sp2共享拥有权,但指向*ptr |
shared_ptr< T> sp(wp) | 基于一个weak pointer 创建出一个shared pointer |
shared_ptr< T> sp(move(up)) | 基于一个unique_ptr up 创建出一个shared pointer |
shared_ptr< T> sp(move(ap)) | 基于一个auto_ptr up 创建出一个shared pointer |
sp.~shared_ptr() | 析构函数,调用delete——如果sp拥有一个对象 |
sp = sp2 | 赋值,sp共享sp2的拥有权,放弃之前对象的拥有权 |
sp = move(sp2) | move assignment(sp2将拥有权移交给sp) |
sp = move(up) | 赋予一个unique_ptr up(up将拥有权移交给sp) |
sp = move(ap) | 赋予一个auto_ptr ap(ap将拥有权移交给sp) |
sp.swap(sp2) | 置换sp和sp2的pointer和deleter |
swap(sp, sp2) | 置换sp和sp2的pointer和deleter |
sp.reset() | 放弃拥有权并初始化shared pointer, 使它像是empty |
sp.reset(ptr) | 放弃拥有权并初始化shared pointer(拥有*ptr), 使用default deleter(即调用delete) |
sp.reset(ptr, del) | 放弃拥有权并初始化shared pointer(拥有*ptr), 使用del 作为 deleter |
sp.reset(ptr, del, ac) | 放弃拥有权并初始化shared pointer(拥有*ptr), 使用del 作为 deleter并以ac作为allocator |
make_shared(…) | 为一个新对象(以传入实参作为初值)建立一个shared pointer |
allocate_shared(ac,…) | 为一个新对象(以传入实参作为初值)建立一个shared pointer, 使用allocator ac |
sp.get() | 返回存储的pointer(通常是被拥有物的地址;若不容有对象则返回nullptr |
*sp | 返回拥有的对象(如果none则形成不确定行为) |
sp->… | 为拥有物提供成员访问,如果none则形成不确定行为 |
sp.use_count() | 返回共享对象之拥有者数量(包括sp本身), 若为empty则返回0 |
sp.unique() | 等价于 sp.use_count() == 1 |
if(sp) | 操作符bool(), 判断sp是否为empty |
sp1 == sp2 | 针对存储的pointer调用== |
sp1 != sp2 | |
sp1 < sp2 | |
sp1 <= sp2 | |
sp1 > sp2 | |
sp1 >= sp2 | |
static_pointer_cast(sp) | 对sp执行static_cast<> 语义 |
dynamic_pointer_cast(sp) | 对sp执行dynamic_cast<> 语义 |
const_pointer_cast(sp) | 对sp执行const_cast<> 语义 |
get_deleter(sp) | 若有定义deleter, 则返回delete指针,否则返回nullptr |
strm<< sp | 针对sp的raw pointer调用output操作符(相当于 strm<< sp.get() |
sp.owner_before(sp2) | 提供自己与另一个shared pointer 之间的strict weak ordering |
sp.owner_before(wp) | 提供自己与另一个weak pointer 之间的strict weak ordering |
- 细究 class weak pointer: weak_ptr 是 shared_ptr 的帮手,用来共享对象但不拥有对象。
weak_ptr 可以为 empty——如果它不是以一个shared_ptr 为初值或者其对应对象的最末一个拥有者被删除就会发生这种情况。
class weak_Ptr<> 也被模块化了,以“初始pointer指向之对象”的类型为模版参数:
namespace std{
template <typename T>
class weak__ptr{
public:
typedef T element_type;
//...
}
}
操作 | 效果 |
---|---|
weak_ptr< T> wp | default 构造函数,建立一个empty weak pointer, 这意味expired()结果是true. |
weak_ptr< T> wp(sp) | 建立一个weak pointer, 共享被sp拥有的pointer的拥有权 |
wp.~weak_ptr() | 析构函数,销毁weak pointer但不影响它所用的对象 |
wp = wp2 | 赋值,此后wp将共享wp2的拥有权,放弃之前拥有对象的拥有权 |
wp = sp | 赋予shared pointer sp(此后wp共享sp的拥有权,放弃之前拥有对象的拥有权) |
wp.swap( wp2) | 置换wp和wp2的pointer |
swap( wp, wp2) | 置换wp和wp2的pointer |
wp.reset() | 放弃被拥有物的拥有权(如果有的话),重新初始化为一个empty weak pointer |
wp.use_count() | 返回的是对象被 shared_ptr 拥有的次数,至于 weak_ptr 对它的共享次数是不计 |
wp.expired() | 等价于 “wp.use_count() == 0” |
wp.lock() | 返回一个shared pointer, 共享“被weak_pointer拥有的pointer”的拥有权 |
wp.owner_before( wp2) | 提供自己与另一个weak pointer之间的strict weak ordering |
wp.owner_before( sp) | 提供自己与另一个shared pointer之间的strict weak ordering |
3.2.5 class unique_ptr
unique——ptr 是自C++11起开始是提供的类型,它是一种在异常发生时可以帮助避免资源泄漏的smart pointer, 一般而言,这个smart pointer 实现了独占式拥有概念,意味着它可以确保一个对象何其拥有的资源同一时间只被一个pointer拥有,一旦拥有着被销毁或者变成empty或者开始拥有另外一个对象,则先前拥有的那个对象就会被销毁。其任何相应资源也会被释放。
class unique_ptr 继承 class auto_ptr, 后者由C++98引入但已经不在被使用。
- class unique_ptr 的目的
函数往往以一下方式运行:1. 获得某些资源;2. 执行某些操作;3. 将取得的资源释放掉。
如果限定local对象,则函数进入点获得的资源会在函数结束前被自动释放,因为那些local对象的析构函数就会被调用,如果资源是显式获得,不与任何对象捆绑,他们必须被明确释放掉。
程序员使用pointer时,资源往往被显式管理(managed explicitly).
以此方式使用pointer的典型例子是,以new和delete创建和销毁对象:
void f(){
ClassA* ptr = new ClassA; //creat an object explicitly
//... //perform some operations.
delete ptr; //clean up
}
//一个明显的问题:有可能忘记delete对象,尤其是在函数中有个return.
//另一个不太危险的问题是:它可能抛出异常,这将导致立即exit函数,末尾的delete也没有被调用,导致内存/资源泄漏
//为了避免发生内存泄漏,通常函数会步骤所有异常,例如:
void f(){
ClassA* ptr = new ClassA; //creat an object explicitly
try{
//...
}
catch( ...){ //for any exception
delete ptr; //clean up
throw; //rethrow the exception
}
delete ptr; //clean up on normal end
}
//为了在异常发生时能适当处理好对象的删除,代码会变得比较复杂。这是一种不好的编程风格,应当避免。
//对于以上行为,smart pointer 可以带来帮助,smart pointer可以在自身被销毁时释放所指向的数据。
void f(){
unique(ClassA) ptr( new ClassA);
//..
}
//以上就足够了,因为unique_ptr是“其所指向之对象”的唯一拥有者。当unique_ptr 被销毁,其所指向的对象也就自动被销毁。
- 使用unqiue_ptr: unqiue_ptr 与寻常pointer非常相似的接口,操作符 * 用来提领(dereference)指向对象,操作符 -> 用来访问成员——如果被指向的对象来自于class或struct.
unique_ptr<string> up( new string( "nico"));
(*up)[0] = 'N'; //replace first char;
cout<<*up<<endl; //输出 Nico
up->append("lai"); //appen some characters;
cout<<*up<<endl; //输出 Nicolai
//但它不提供算数, 如"++", "--" 之类的运算。
//unique_ptr<> 不逊于以肤质语法将寻常的pointer作为初值,故必须直接初始化unique_ptr.
unique_ptr<int> up2 = new int;//ERROR
unique_ptr<int> up2( new int);//OK
//unique_ptr 不一定必须拥有对象,也可以为empty, 例如:
unique_ptr<string> up2;
//可以对它赋予nullptr或调用reset()
up2 = nullptr;
up2.reset();
if( up){//可以用操作符bool()来检查unique pointer是否拥有对象
//if(up) 等价于if( up != nullptr) 或 if( up.get() !=nullptr)
cout<<*up<<endl; //输出Nicolai
}
- 转移 unique_ptr 的拥有权:unique_ptr 提供的语义是“独占式拥有”,但需要由程序来确保“没有两个unique pointer 以同一个pointer 作为初值”。
同时注意,unqiue pointer不能执行copy 和 assign语句。
例如:
string* sp = new string( "hello");
unique_ptr<string> up1(sp);
unique_ptr<string> up2(sp);//ERROR, 因为up1 和 up2拥有了相同的数据
//如何使用copy函数?
unique_ptr<ClassA> up1( new ClassA);
unique_ptr<ClassA> up2( move(up1));//OK, up1不再拥有
unique_ptr<ClassA> up3(up1); //ERROR, 编译期间发生错误,因为一次只允许一个拥有者,up3不可以成为原对象的另一个拥有者
//如何assignment?
unique_ptr<ClassA> up1( new ClassA);
unique_ptr<ClassA> up2;
up2 = up1; //ERROR
up3 = move(up1); //move assignment 将拥有权从up1转移给up3, 若up3之前原本有对象,则delete删除该对象
- 源头和去处
拥有权的转移指出了 unique_ptr 的一种用途:函数可以利用他们将拥有权转移给其他函数。
这里会发生两种情况,如下:
// 函数是接收端。
// 如果将一个由move() 建立起来的 unique_ptr 以 rvalue reference 身份作为函数实参,则被调用函数的参数将会取得unique_ptr 的拥有权,因此若该函数不再转移拥有权,对象会在函数结束前被deleted.
void sink( unqiue_ptr<ClassA> up){ //sink 得到拥有权
//...
}
unique_ptr<ClassA> up( new ClassA);
sink( move(up)); //up失去拥有权
//函数是供应端
//当函数返回一个 unique_ptr, 其拥有权会转移到调用端场景内,例如:
unique_ptr<ClassA> source(){
unique_ptr<ClassA> ptr( new ClassA); //ptr拥有新对象
retrun ptr; //拥有权转移到端场景内
}
void g(){
unique_ptr<ClassA> p;
for( int i = 0; i < 10; i++){
p = source();
// 每次source()被调用,就会以new创建对象并返回给调用者p,夹带拥有权。返回值先被赋值给p, 于是拥有权也转移给p.
//在循坏的第二次迭代中,对p赋值导致p先前拥有的对象被删除,一旦g()结束,p被销毁,导致最后一个由p拥有的对象被析构。
}
}
- unique_ptr 被当作成员:在class内使用 unique_ptr 可以比米娜资源泄漏,如果使用 unique_ptr 代替寻常的pointer 就不再需要析构函数,因为对象被删除的同时会连带所有成员被删除。此外 unique_ptr 也可以协助“对象初始化期间因为抛出异常而造成资源泄漏”。
注意,只有当一切构造动作完成后,析构函数才有可能调用,因此,一旦构造期间发生异常,只有那些已经完全构造好的对象,其析构函数才会被调用,所以,对于“拥有多个raw pointer” 的calss,如果构造期间第一个new成功而第二个失败,就有可能造成资源泄漏,例如:
class ClassB{
private:
ClassA* ptr1;
ClassA* ptr2;
public:
//当第二个new异常时,以下将导致内存泄漏
ClassB( int val1, int val2) : ptr1(new ClassA(val1)), ptr2( new ClassA(val2)){
}
//当第二个new异常时,以下可能导致内存泄漏
ClassB( const ClassB& x) : ptr1(new ClassA(*x.ptr1)), ptr2( new ClassA(*x.ptr2)){
}
//assignment operator
const ClassB& operator = ( const ClassB& x){
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB(){
delete ptr1;
delete ptr2;
}
};
// 只要使用unique_ptr, 就可以避免上述可能的资源泄漏
class ClassB{
private:
unique_ptr<ClassA> ptr1;
unique_ptr<ClassA> ptr2;
public:
//不会导致内存泄漏
ClassB( int val1, int val2) : ptr1(new ClassA(val1)), ptr2( new ClassA(val2)){
}
//不会导致内存泄漏
ClassB( const ClassB& x) : ptr1(new ClassA(*x.ptr1)), ptr2( new ClassA(*x.ptr2)){
}
//assignment operator
const ClassB& operator = ( const ClassB& x){
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB(){
delete ptr1;
delete ptr2;
}
};
- 对付array:
默认情况下unique_ptr 会失去拥有权(可能因为被销毁,也可能因为被赋予新的值,也可能因为变成empty),会为其所拥有的对象调用delete.
基于C语言的规则,C++无法区分pointer是“指向单对象”还是“指向array”, 但是C++语言又规定,对于array应该使用delete[], 所以,以下语句是错误的:
unique_ptr<string> up( new string[10]);//可编译,但无法运行
//如何避免?C++标准库为class unique_ptr提供了一个偏特化版本用来处理array,该版本会在遗失其所指对象拥有权时,对该对象调用delete[], 声明如下:
unqiue_ptr<string[]> up( new string[10]);//OK
//该版本接口中不再提供 * 和 ->, 而是提供操作符 [].
cout<<*up<<endl; //ERROR: * not defined for arrays
cout<<up[0]<<endl; //OK
- class default_delete<>
class default_delete<>声明如下:
namespace std {
template <typedef T, typedef D = default_delete<T>>
class unique_ptr{
public:
T& operator*() const;
T* operator->() const noexcept;
};
template< typename T, typename D>
class unique_ptr<T[], D>{
public:
T& operator[](size_t i) const; //用来处理array
};
}
- 其他相应资源的deleter:当所指向的对象要求的不只是调用delete或delete[]时,就必须具体指定自己的deleter.
此处的deleter的定义方式与shared_ptr略不相同,必须具体指明deleter 的类型作为第二个 template 实参,该类型可以是 reference to funciton, 或者是function pointer, 或者是 function object.
3.2.6 细究class unique_ptr
class unique_ptr 提供了一个“带有独占拥有权的 smart pointer语义”的概念。
一旦某一个unique pointer有了独占控制权,便无法创造出“多个pointer拥有该相应对象”的形式。主要目的是确保在那个pointer寿命结束时,相应对象被删除(或资源被清理干净),这有助于提供异常安全性。
相比于shared pointer, 这个class关注的是最小量的空间开销和时间开销。
class unqiue_ptr<> 被模版化,其参数是原始pointer所指对象的类型,以及deleter的类型:
namespace std{
template <typename T, typename D = defalut_delete<T>>
class unique_ptr{
public:
typedef ... pointer; //may be D::pointer
typedef T element_type;
typedef D deleter_type;
};
}
另外,它有一个针对array的偏特化版本:
namespace std{
template < typename T, typename D>
class unique_ptr<T[], D>{
public:
typedef ... pointer; //may be D::pointer
typedef T element_type;
typedef D deleter_type;
};
}
//元素类型T可以是void, 意味着unqiue pointer 拥有的对象其类型并不明确,就像void* 那样。
unique pointer 提供的所有操作:
操作 | 效果 |
---|---|
unique_ptr<…> up | default构造函数,建立一个empty unique pointer, 使用default/passed deleter 类型的一个实例作为deleter |
unique_ptr< T> up(nullptr) | 建立一个empty unique pointer, 使用default/passed deleter 类型的一个实例作为deleter |
unique_ptr<…> up(ptr) | 建立一个 unique pointer, 拥有*ptr 使用default/passed deleter 类型的一个实例作为deleter |
unique_ptr<…> up(ptr, del) | 建立一个 unique pointer, 拥有*ptr 使用del 作为deleter |
unique_ptr< T> up(move(up2)) | 建立一个 unique pointer, 拥有先前被 up2 拥有的pointer( 此后up2为空) |
unique_ptr< T> up(move(ap)) | 建立一个 unique pointer, 拥有先前被 auto_ptr ap 拥有的pointer( 此后ap为空) |
up.~unique_ptr() | 析构函数,为一个被拥有的对象调用deleter |
up = move(up2) | move赋值( up2移交拥有权给up) |
up = nullptr | 对一个被拥有物调用deleter, 并令其为空 |
up1.swap(up2) | 置换up1和up2的pointer和deleter |
swap(up1, up2) | 置换up1和up2的pointer和deleter |
up.reset() | 等待于up = nullptr |
up.reset(ptr) | 对一个被拥有物调用deleter, 并重新初始化shared pointer 使它拥有*ptr |
up.release() | 放弃拥有权,将拥有权交给调用者 |
up.get() | 返回被存储的pointer(被存储物的地址) |
*up | 只作用于单对象,返回被拥有物 |
up->… | 只作用于单对象,为被拥有物提供成员访问 |
up[idx] | 只作用于array对象,返回被存储的array内索引为idx的元素 |
if(up) | 判断up是否为空 |
up1 == up2 | |
up1 != up2 | |
up1 == up2 | |
up1 == up2 | |
up1 < up2 | |
up1 <= up2 | |
up.get_deleter() | 返回一个reference代表deleter |
3.2.7 class auto_ptr
在C++98中,标准库只提供一个 smart pointer calss auto_ptr, 但是在C++11中已经明确声明不再支持。
3.2.8 smart pointer 结语
C++11提供了两种 smart pointer:1. shared_ptr 用来共享拥有权;2. unique_ptr 用来独占拥有权。
3.3 数值的极值(numeric limit)
一般而言,数值类型(numeric type)有着与平台相依的极值,C++标准库借由template numeric_limits 提供这些极值,用来取代传统C语言所采用的预处理器常量(preprocessor constant),注,依旧可以使用后者。
其中整数常量定义于 < climits> 和 < limits.h>, 浮点常量定义于< cfloat> 和 < float.h>.
新的极值概念有两个优点,第一个是提供更好的类型安全性;第二个是程序员可借此写出一些template以核定这些极值。
内建类型(built-in type)的最小长度:
类型 | 最小长度 |
---|---|
char | 1 byte (8 bits) |
short int | 2 byte |
int | 2 byte |
long int | 4 byte |
long long int | 8 byte |
float | 4 bytes |
double | 8 bytes |
long double | 8 bytes |
- class numeric_limits<>
使用template, 通常是为了对所有类型一次性地撰写出一个通用方案,除此之外,还可以在必要时以template为每个类型提供相同的接口。
方法:不但提供通用性的template, 还提供其特化的版本。
例如:numeric_limits:
//通用性的template, 为所有类型提供默认极值:
namespace std{
template< typename T>
class numeric_limits{
public:
static constexpr bool is_specialized = false;//对类型T而言,不存在所谓的极值。
};
}
//各具体类型的极值,由特化版本提供:
namespace std{
template<> class numeric_limits<int>{
public:
static constexpr bool is_specialized = true;//所有其他成员都依据特定类型的具体极值设定。
static constexpr int min() noexcept{
return -2147483648;
}
static constexpr int max() noexcept{
return 2147483647;
}
//...
};
}
以下给出 class numeric_limits<> 的所有成员及其意义:
成员 | 意义 | 对应的C常量 |
---|---|---|
is_specialized | 类型是否有极值 | |
is_signed | 类型带有正负号 | |
is_integer | 整数类型 | |
is_exact | 计算结果不产生舍/入误差(此成员对所有整数类型而言均为true) | |
is_bounded | 数值集的个数有限(对所有内建类型而言,此成员均为true) | |
is_modulo | 两正值想加,其结果可能因为溢出而回绕到较小的值 | |
is_iec559 | 遵从IEC 559 及 IEEE 754 标准 | |
min() | 最小有限值(只有当is_bounded || !is_signed 成立时才有意义) | INT_MIN, FLT_MIN, CHAR_MIN |
max() | 最大有限值(只有当is_bounded成立时才有意义) | INT_MAX, FLT_MAX |
lowest() | 最大负有限值(只有当is_bounded成立时才有意义;始自C++11) | |
digits | 字符和整数:不包含正负号的位数;浮点数:raidx的位数 | CHAR_BIT, FLT_MANT_DIG |
digits10 | 十进制数字的个数(只有is_bounded成立时才有意义) | FLT_DIG |
max_digits10 | 必要的十进制数位个数,确保不同的数值总是不同(对所有浮点数类型都有意义;始自C++11) | |
radix | 整数:表达式的底,几乎总是2; 浮点数:指数表达式的底 | FLT_RADIX |
min_exponent | 底radix的最小负整数指数 | FLT_MIN_EXP,… |
max_exponent | 底radix的最大负整数指数 | FLT_MAX_EXP,… |
min_exponent10 | 底10的最小负整数指数 | FLT_MIN_10_EXP,… |
max_exponent10 | 底10的最大负整数指数 | FLT_MAX_10_EXP,… |
exsilon() | 1 和 最近姐1的值之间的差距 | FLT_EPSILON,… |
round_style | 舍/入风格 | |
round_error() | 最大舍/入差量度 | |
has_infinity | 类型有“正无穷大”表述 | |
infinity() | 表现“正无穷大”(如果有的话) | |
has_quiet_NaN | 类型拥有nonsignaling "Not a Number"表述 | |
quiet_NaN() | 安静表达"Not a Number", 如果能够的话 | |
has_signaling_NaN | 类型拥有signaling "Not a Number"表述形式 | |
signaling_NaN() | signaling “Not a Number”, 如果能够的话 | |
has_denorm | 类型是否允许 denormalized value | |
has_denorm_loss | 准确度的遗失被侦测为一个denormalization loss, 而不是一个inexact result | |
denorm_min() | 最小正值的denormalized value | |
traps | 已实现trapping | |
tinyness_before | 在舍/入之前可以侦测出tinyness |
numeric_limits<> 的数值舍/入风格:
舍/入风格 | 意义 |
---|---|
round_toward_zero | 无条件舍去 |
round_to_nearest | 化为最接近值 |
round_toward_infinity | 化为不小于被操作数的最小整数 |
round_toward_neg_infinity | 化为不大于被操作数的最小整数 |
round_indeterminate | 无法确定 |
numeric_limits<> 的数值化简风格:
化简风格 | 意义 |
---|---|
denorm_absent | 此类型不允许化简 |
denorm_present | 此类型允许向最接近的可表述值做化简 |
denorm_indeterminate | 无法确定 |
下面给出对float极值的一份全特化(full specialization)完整实现,与平台相依,并且给出各个成员的确切签名式(signature):
namespace std {
template<> class numeric_limits<float>{
public:
static constexpr bool is_specialized = true;
inline constexpr float min() noexcept{
return 1.17549435E-38F;
}
inline constexpr float max() noexcept{
return 3.40282347E+38F;
}
inline constexpr float lowest() noexcept{
return -3.40282347E+38F;
}
static constexpr int digits = 24;
static constexpr int digits10 = 6;
static constexpr int max_digits10 = 9;
static constexpr bool is_signed = true;
static constexpr bool is_integer = false;
static constexpr bool is_exact = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = false;
static constexpr bool is_iec559 = true;
static constexpr int radix = 2;
inline constexpr float epsilon() noexcept{
return 1.19209290E-07F;
}
static constexpr float_round_style round_style = round_to_nearest;
inline constexpr float round_error() noexcept{
return 0.5F;
}
static constexpr int min_exponent = -125;
static constexpr int max_exponent = +128;
static constexpr int min_exponent10 = -37;
static constexpr int max_exponent10 = +38;
static constexpr bool has_infinity = true;
inline constexpr float infinity() noexcept{
return ...;
}
static constexpr bool has_quiet_NaN = true;
inline constexpr float quiet_NaN() noexcept{
return ...;
}
static constexpr bool has_signaling_NaN = true;
inline constexpr float signaling_NaN() noexcept{
return ...;
}
static constexpr float_denorm_style has_denorm = denorm_absent;
static constexpr bool has_denorm_loss = false;
inline constexpr float denorm_min() noexcept{
return min();
}
static constexpr bool traps = true;
static constexpr bool tinyness_before = true;
};
}
- numeric_limits<> 实例:下面例子展示了某些类型极值的可能运用,例如用来了解某个类型的最大值,或者确定cahr是否带有正负号:
cout<<boolalpha;//将bool类型显示为true or false
//程序的输出结果与执行平台有关
//整数类型最大值
cout<<numeric_limits<short>::max()<<endl;//max(shrot) = 32767;
cout<<numeric_limits<int>::max()<<endl;//max(int) = 2147483647;
cout<<numeric_limits<long>::max()<<endl;//max(long) = 9223372036854775807;
//浮点数类型最大值
cout<<numeric_limits<float>::max()<<endl;//max(float) = 3.40282e+38;
cout<<numeric_limits<double>::max()<<endl;//max(double) = 1.79769e+308;
cout<<numeric_limits<long double>::max()<<endl;//max(long double) = 1.18973e+4932;
//char
cout<<numeric_limits<char>::is_signed<<endl;//is_signed(char) = true;
//string
cout<<numeric_limits<string>::is_specialized<<endl;//is_specialized(string) = false;
3.4 Type Trait 和 Type Utility
C++标准库中几乎都以template为基础,为了更多地支持template变成(又称为超编程),标准库提供了template通用工具,用来协助应用程序开发人员和程序库作者。
type trait, 由 TR1 引入而在C++11中大幅度扩展的一个极值,定义出因type而异的行为,它们可以被用来针对type优化代码,以提供特别能力。
其他的工具,例如reference和function wrapper也可以为编程带来若干帮助。
3.4.1 type trait 的目的
type trait 提供一种用来处理type属性(property of a type)的办法,它是一个template, 可以在编译期根据一个或多个template实参(通常也是type)产生出一个type或value.
例如:
template <typename T>
void foo( const T& val){
if( is_pointer<T>::value)//is_pointer 用来检查是否为指针
cout<<" foo() called for a pointer"<<endl;
else
cout<<" foo() called for a value"<<endl;
}
type trait 另一个运用是处理两个或多个类型的“共通类型”。
例如:
//如果判断不同类型中的两个值之中的较小值,函数类型应该是什么?
template <typename T1, typename T2>
//??? min( const T1& x, const T2& y);
//type tarit 可以解决如上问题:
typename common_type<T1, T2>::type min( const T1& x, const T2& y);
//如果两个实参分别是int和long,则common_type<T1, T2>::type会产生int.
//如果实参之一是string而另一个字符串字面长脸(const char*), 则返回string.
//以下是common_type 的实现:
template <typename T1, typename T2>
struct common_type<T1, T2>{
typedef decltype( true ? declval<T1>() : declval<T2>()) type;
};
3.4.2 细究 type trait
type trait 通常被定义于 < type_traits>.
- (单参)类型判断式( (Unary) Type Predicate)
如果某个特定性质( specific property) 适用,则 type predicate 产生 true_type, 否则产生 false_type. 这些类型都是辅助用的intergral_constant的特化,所以他们对应的value 成员或产生true 或 false.
namespace std {
template <typename T, T val>
struct integral_constant{
static constexpr T value = val;
typedef T value_type;
typedef integral_constant<T, v> type;
constexpr operator value_type(){
return value;
}
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
}
以下给出了针对所有类型而设计的类型判断工具(type predicate):
trait | 效果 |
---|---|
is_void< T> | 类型void |
is_integral< T> | 整数类型( bool, char, char16_t, char32_t, wchar_t) |
is_floating_point< T> | 浮点数类型(float, double, long double) |
is_arithmetic< T> | 整数(包括bool, character)或浮点数 |
is_signed< T> | 带正负号的算数类型 |
is_unsigned< T> | 不带正负号的算数类型 |
is_const< T> | 带const限定符 |
is_volatile< T> | 带volatile限定符 |
is_array< T> | 寻常的array类型( not std:array) |
is_enum< T> | 枚举(enumeration)类型 |
is_union< T> | 联合(union)类型 |
is_classs< T> | class/struct 类型,但不是union类型 |
is_function< T> | 函数类型 |
is_reference< T> | lvalue 或者 rvalue reference |
is_rvalue_reference< T> | rvalue reference |
is_lvalue_reference< T> | lvalue reference |
is_pointer< T> | pointer类型(包括function pointer但不包括 pointer to nonstatic member) |
is_member_pointer< T> | Pointer, 指向一个nonstatic成员 |
is_member_object_pointer< T> | Pointer, 指向一个nonstatic数据成员 |
is_member_function_pointer< T> | Pointer, 指向一个nonstatic成员函数 |
is_fundamental< T> | void, 整数型(包括bool和char), 浮点数,或 nullptr |
is_scalar< T> | 整数型(包括bool和char),浮点数,枚举, pointer, member pointer, 或 nullptr |
is_object< T> | 除void, function 或 reference 外的任何类型 |
is_compound< T> | array, enumeration, union, class, function, reference, pointer |
is_trivial< T> | scalar, trivial class 或 那些类型构成的array |
is_trivial_copyable< T> | scalar, trivial copyable class 或 那些类型构成的array |
is_standard_layout< T> | scalar, standard layout class 或 那些类型构成的array |
is_pod< T> | plain old data type, "memcpy() 可用来复制对象"的类型 |
is_literal_type< T> | scalar, reference, class 或那些类型构成的array |
以下列出了针对class类型而设计,能够更加阐明class细节的trait.
trait | 效果 |
---|---|
is_empty< T> | class 不带任何成员, virtual成员函数, virtual base class |
is_polymorphic< T> | class 带有一个(derived) virtual 成员函数 |
is_abnstract< T> | abstract class(即至少一哦个pure virtual function) |
has_virtual_destructor< T> | class 带有virtual析构函数 |
is_default_constructible< T> | class 能够完成default construction |
is_copy_constructible< T> | class 能够完成copy construction |
is_move_constructible< T> | class 能够完成move construction |
is_copy_assignable< T> | class 能够完成copy assignable |
is_move_assignable< T> | class 能够完成move assignable |
is_destructible< T> | class 带有可被调用的析构函数 |
is_trivially_default_constructible< T> | class 能够完成trivially default construction |
is_trivially_copy_constructible< T> | class 能够完成trivially copy construction |
is_trivially_move_constructible< T> | class 带有trivially move construction |
is_trivially_destructible< T> | class 带有trivially callable destructor |
is_nothrow_default_constructible< T> | class 能够完成 default construction 且不抛出异常 |
is_nothrow_copy_constructible< T> | class 能够完成 copy construction 且不抛出异常 |
is_nothrow_move_constructible< T> | class 能够完成 move construction 且不抛出异常 |
is_nothrow_copy_assignable< T> | class 能够完成 copy assignable 且不抛出异常 |
is_nothrow_move_assignable< T> | class 能够完成 move assignable 且不抛出异常 |
is_nothrow_destructible< T> | class 带有 callabe destructor 且不抛出异常 |
注意,以上triat中的大部分都是单参式(unary), 即它们只使用一个template实参。
例如:is_count<>可以用来检查传入的类型是否是const,
cout<<is_const< int>::value<<endl; //false;
cout<<is_const< const volatile int>::value<<endl; //true;
cout<<is_const< int* const>::value<<endl; //true;
cout<<is_const< const int*>::value<<endl; //false;
cout<<is_const< const int&>::value<<endl; //false;
cout<<is_const< int[3]>::value<<endl; //false;
cout<<is_const< const int[3]>::value<<endl; //true;
cout<<is_const< int[]>::value<<endl; //false;
cout<<is_const< const int[]>::value<<endl; //true;
- 用以检验类型关系(Type Relation)的Trait
下表给出type trait 可以检查类型之间的关系,包括检查 class type 提供了哪一种构造函数和哪一种赋值操作:
|trait|效果|
|-|-|
|is_same< T1,T2>|T1 和 T2相同类型|
|is_base_of< T,D>|类型T是类型D的base calss|
|is_convertible< T, T2>|类型T可转换至类型T2|
|is_constructible< T, Args…>|可以用类型Args初始化T|
|is_trivially_constructible< T, Args…>|可以用类型Args平凡初始化T|
|is_nothrow_constructible< T, Args…>|可以用类型Args初始化T且不抛异常|
|is_assignable< T, T2>|可以将类型T2赋值给类型T|
|is_trivially_assignable< T, T2>|可以将类型T2平凡赋值给类型T|
|is_nothrow_assignable< T, T2>|可以将类型T2赋值给类型T且不抛异常|
|uses_allocator< T, Alloc>|Alloc 可以被转换为T::allocator_type|
例如:
//注意,一个诸如int的类型究竟表现出lvalue或者rvalue
//因此is_assignable<>的第一个类型如果是nonclass, 则永远会收得到false_type. 对于其他class类型,以其寻常类型作为第一类型是可以的
77 = 32;////无法通过编译
cout<<is_assignable<int, int>::value<<endl; //false
cout<<is_assignable<int&, int>::value<<endl; //true
cout<<is_assignable<int&&, int>::value<<endl; //false
cout<<is_assignable<long&, int>::value<<endl; //true
cout<<is_assignable<int&, void*>::value<<endl; //false
cout<<is_assignable<void*, int>::value<<endl; //false
cout<<is_assignable<const char*, string>::value<<endl; //false
cout<<is_assignable<string, const char*>::value<<endl; //true
//对于is_constructible:
cout<<is_constructible<int>::value<<endl; //true;
cout<<is_constructible<int,int>::value<<endl; //true;
cout<<is_constructible<long,int>::value<<endl; //true;
cout<<is_constructible<int,void*>::value<<endl; //false;
cout<<is_constructible<void*,int>::value<<endl; //false;
cout<<is_constructible<const char*,string>::value<<endl; //false;
cout<<is_constructible<string, const char*>::value<<endl; //true;
cout<<is_constructible<string, const char*, int, int>::value<<endl; //true;
- 类型修饰符( type modifer)
下表给出所列的trait允许改动的类型:
trait | 效果 |
---|---|
remove_const< T> | 对应不带const的类型 |
remove_volatile< T> | 对应不带volatile的类型 |
remove_cv< T> | 对应不带const和volatile的类型 |
add_const | 对应const的类型 |
add_volatile< T> | 对应volatile的类型 |
add_cv< T> | 对应const和volatile的类型 |
make_signed< T> | 对应带正负号 nonreference类型 |
make_unsigned< T> | 对应不带正负号 nonreference类型 |
remove_reference< T> | 对应nonreference类型 |
add_lvalue_reference< T> | 对应lvalue reference类型( rvalue -> lvalue) |
add_rvalue_reference< T> | 对应rvalue reference类型( lvalue -> rvalue) |
remove_pointer< T> | pointer指向的类型,若非pointer则返回原类型 |
add_pointer< T> | pointer类型,指向对应的nonreference type类型 |
例如:
//对于类型int, 有:
typedef int T;
add_const<T>::type; //const int
add_lvalue_reference<T>::type; //int&
add_rvalue_reference<T>::type; //int&&
add_pointer<T>::type; //int*
make_signed<T>::type; //int
make_unsigned<T>::type; //unsigned int
remove_const<T>::type; //int
remove_reference<T>::type; //int
remove_pointer<T>::type; //int
//对于类型 const int& 有:
typedef const int& T;
add_const<T>::type; //const int&
add_lvalue_reference<T>::type; //const int&
add_rvalue_reference<T>::type; //const int&
add_pointer<T>::type; //const int*
make_signed<T>::type; //undeined behavior
make_unsigned<T>::type; //undeined behavior
remove_const<T>::type; //const int&
remove_reference<T>::type; //const int
remove_pointer<T>::type; //const int&
- 其他type trait: 这里给出其余的所有type trait, 他们用来查询特殊属性、检查类型关系或者提供更复杂的类型变换。
trait | 效果 |
---|---|
rank< T> | array 类型的维度 |
extent< T, I = 0> | 维度I的尺度 |
remove_extent< T> | 获取array的元素类型 |
remove_all_extent< T> | 获取多维array的元素类型 |
underlying_type< T> | 枚举类型的低层类型 |
decay< T> | 将T转换成对应的“实值类型” |
enable_if< B,T=void> | 只有bool B为true时产生type T |
conditional< B, T, F> | 只有bool B为true时产生type T, 否则产生type F |
common_type< T1,…> | 所有被传入的类型的共通类型 |
result_of< F, ArgTypes> | 调用F并基于实参类型ArgTypes后,其所得结果的类型 |
alignment_of< T> | 等价于 alignof(T) |
aligned_storage< Len> | Len byte 加上默认对齐规则所可容纳的类型 |
aligned_storage< Len, Align> | Len byte 且以size_t Align的约数进行对齐,所可容纳的类型 |
aligned_storage< Len, Types…> | Len byte 且以 a union of Types…进行对齐,所可容纳的类型 |
例如:
cout<<rank<int>::value<<endl; //0
cout<<rank<int[]>::value<<endl; //1
cout<<rank<int[3]>::value<<endl; //1
cout<<rank<int[][1]>::value<<endl; //2
cout<<rank<int[][1][2]>::value<<endl; //3
cout<<extent<int>::value<<endl; //0
cout<<extent<int[]>::value<<endl; //0
cout<<extent<int[3]>::value<<endl; //3
cout<<extent<int[][1], 1>::value<<endl; //1
cout<<extent<int[][1][2], 2>::value<<endl; //2
remove_extent<int>::type; //int
remove_extent<int[]>::type; //int
remove_extent<int[3]>::type; //int
remove_extent<int[][2]>::type; //int[2]
remove_extent<int[1][2]>::type; //int[2]
remove_all_extents<int>::type; //int
remove_all_extents<int[]>::type; //int
remove_all_extents<int[5]>::type; //int
remove_all_extents<int[][7]>::type; //int
remove_all_extents<int[5][7]>::type; //int
3.5 辅助函数
C++标准库提供了若干小型辅助函数,用来挑选最小值/最大值,交换两个值,提供增补的比较操作符。
下表给出< algorith>提供的通用工具函数,用来在两值或多值之间选择最大和最小值:
操作 | 效果 |
---|---|
min(a,b) | 返回a, b之间的最小值 |
min(a,b,cmp) | 返回a, b之间的最小值,以cmp进行比较 |
min(initlist) | 返回initlist中的最小值 |
min(initlist,cmp) | 返回initlist中的最小值,以cmp进行比较 |
max(a, b) | 返回a, b之间的最大值 |
max(a,b,cmp) | 返回a, b之间的最大值,以cmp进行比较 |
max(initlist) | 返回initlist中的最大值 |
max(initlist,cmp) | 返回initlist中的最大值,以cmp进行比较 |
minmax(a,b) | 返回一个pair, 其中first是二者中最小值,second是二者中最大值 |
minmax(a,b,cmp) | 返回一个pair, 其中first是二者中最小值,second是二者中最大值,以cmp进行比较 |
minmax(initlist) | 返回一个pair, 其中first是initlist中最小值,second是initlist中最大值 |
minmax(initlist, cmp) | 返回一个pair, 其中first是initlist中最小值,second是initlist中最大值,以cmp进行比较 |
C++中两个值互换:函数swap() 用来交换两个对象的值。
swap的泛化实现定义于< utility>:
namespace std {
template <typename T>
inline void swap( T& a, T& b){
T temp( move(a));
a = move( b);
b = move( temp);
}
}
运用该函数,可以交换任意变量的值:swap( x,y);
另外,swap() 提供了一份异常明细:
noexcept( is_nothrow_move_constructible<T>::value && is_nothrow_move_assignable<T>::value)
另外,自C++11起,标准库提供了一个针对array的重载版本:
namespace std{
template <typename T, size_t N>
void swap( T (&a)[N], T (&b)[N])
noexcept( noexcept( swap( *a, *b)));
}
swap() 最大的优势在于,通过 模块特化(template specialization) 或 函数重载(function overloading), 我们可以为更加复杂的类型提供特殊实现版本。这些版本有可能交换内部成员,而非对对象赋值,这可以节约大量的时间。
标准库的所有容器和string 都运用了这个技术。
增补的“比较操作符”:有四个function分别定义了 !=, >, <=, >=这四个比较操作符。它们都是利用操作符 == 和 < 完成的,这四个函数定义于 < utility>, 通常定义如下:
namespace std {
namespace rel_ops{ //次级命名空间
template <typename T>
inline bool operator!= (const T& x, const T& y){
return !( x == y);
}
template <typename T>
inline bool operator> (const T& x, const T& y){
return y < x;
}
template <typename T>
inline bool operator<= (const T& x, const T& y){
return !(y < x);
}
template <typename T>
inline bool operator>= (const T& x, const T& y){
return !(x < y);
}
}
}
3.6 class ratio<> 的编译期分数运算
C++11起,标准库提供了一个进口允许指定编译期分数(compile-time fractiopon), 并允许对它们执行编译期运算。
整个ratio utility 由< ratio> 提供,其中的class ratio<>定义如下:
namespace std {
template <intmax_t N, intmax_t D = 1>
class ratio{
public://分母/分子都是public且自动降到最简式
typedef ratio<num, den> type;
//intmax_t是带正负号的整数类型,有能力表现任何带符号的整数
//intmax_t被设计于<cstdint>或<stdint.h>内,至少64位。
static constexpr intmax_t num;
static constexpr intmax_t den;
};
}
例如:
typedef ratio<5,3> FiveThirds;
cout<<FiveThirds::num<<"/"<<FiveThirds::den<<endl; //输出 5/3
typedef ratio<5,3> AlsoFiveThirds;
cout<<AlsoFiveThirds::num<<"/"<<AlsoFiveThirds::den<<endl;//输出 5/3
typedef ratio<22,22> test1;
cout<<test1::num<<"/"<<test1::den<<endl; //1/1
typedef ratio<0> test2;
cout<<test2::num<<"/"<<test2::den<<endl; //0/1
ratio<7,-3> test3;
cout<<test3.num<<"/"<<test3.den<<endl; //-7/3
下表给出了ratio<>的各种操作:
运算 | 意义 | 结果 |
---|---|---|
ratio_add | 和 | ratio<> |
ratio_subtract | 差 | ratio<> |
ratio_multiply | 积 | ratio<> |
ratio_divide | 商 | ratio<> |
ratio_equal | == | true_type 或 false_type |
ratio_not_equal | != | true_type 或 false_type |
ratio_less | < | true_type 或 false_type |
ratio_less_equal | <= | true_type 或 false_type |
ratio_greater | > | true_type 或 false_type |
ratio_greater_equal | <= | true_type 或 false_type |
下面给出一些例子:
ratio_add< ratio<2,7>, ratio<2,6>>::type; //最终结果为ratio<13,21>
ratio_equal< ratio<1,3>, ratio<2,6>>::value; //最终结果为true
//当发生溢出、分母为0时会返回错误
ratio_multiply< ratio<1,3>, ratio<2,0>>::value; //由于分母为0而报错
ratio_multiply< ratio<1,numeric_limits<long long>::max()>, ratio<1,2>>::type; //由于溢出报错