【C++标准库】:第三章:通用工具

本文详细介绍了C++标准库中的通用工具,包括Pair和Tuple、Smart Pointer、数值极值、Type Trait和Type Utility等内容。重点讲解了Pair的构造、访问和比较,以及Smart Pointer的共享和独占拥有权概念,特别是shared_ptr和unique_ptr的使用和注意事项。此外,还提及了编译期分数运算和时间相关工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【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> pDefault 构造函数,建立一个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特性:
  1. tuple_size< tupletype>::value 获得元素个数.
  2. tuple_element< idx, tupletype>::type 获得第idx个元素的类型.
  3. 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> spdefault构造函数,建立一个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> wpdefault 构造函数,建立一个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<…> updefault构造函数,建立一个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)的最小长度:

类型最小长度
char1 byte (8 bits)
short int2 byte
int2 byte
long int4 byte
long long int8 byte
float4 bytes
double8 bytes
long double8 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_addratio<>
ratio_subtractratio<>
ratio_multiplyratio<>
ratio_divideratio<>
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; //由于溢出报错

3.7 clock 和 timer

我的微信公众号

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值