C++编程语言:标准库:工具类(Bjarne Stroustrup)
第 35章 工具类(Utinities)
目录
35.1 引言
35.2 时间类(Time)
35.2.1 duration
35.2.2 time_point
35.2.3 时钟(Clocks)
35.2.4 时间特征类(Time Trais)
35.3 编译时比率运算(Compile-Time Rational Arithmetic)
35.4 类型函数(Type Functions)
35.4.1 类型特征(Type Traits)
35.4.2 类型生成器(Type Generators)
35.5 其它小工具(Minor Utilities)
35.5.1 move() 和 forward()
35.5.2 swap()
35.5.3 关系运算符(Relational Operators)
35.5.4 比较和哈希type_info(Comparing and Hashing type_info)
35.6 建议(Advice)
35.1 引言
标准库提供了许多“工具组件”,它们的用途非常广泛,因此很难被归类为某个主要标准库组件的一部分。
35.2 时间类(Time)
在 <chrono> 中,标准库提供了处理时间段和时间点的功能。所有 chrono 功能都位于 std::chrono(子)命名空间中,因此我们必须使用 chrono:: 显式限定,或者添加 using 指令(译注:chrono只是一个构词前缀,词义为“时间的,时间段的,与时间相关的,” 可以看作是名词chronology的缩写):
using namespace std::chrono;
我们经常需要对某些事情进行计时,或者做一些依赖于时间的事情。例如,标准库中的互斥锁和锁提供了让线程(thread)等待一段时间(duration)或等到给定时间点(time_point)的选项。
如果你想知道当前的 time_point,可以调用 now() 来获取以下三个时钟中的一个:system_clock、steady_clock 和 high_resolution_clock。例如:
steady_clock::time_point t = steady_clock::now();
// ... do something ...
steady_clock::duration d = steady_clock::now()−t; // 取 d 个时间单位一样的东西
时钟返回一个 time_point,而持续时间是同一时钟的两个 time_point 之间的差值。通常,如果您对细节不感兴趣,auto 是你的好帮手:
auto t = steady_clock::now();
// ... do something ...
auto d = steady_clock::now()−t; // 取 d 个时间单位一样的东西
cout << "something took " << duration_cast<milliseconds>(d).count() << "ms"; // 按毫秒打印
此处的时间设施旨在高效地支持系统深层的使用;它们并非提供便利设施来帮助你维护社交日程。事实上,时间设施源于高能物理的严格需求。
事实证明,“时间”的处理远比我们通常想象的要复杂得多。例如,我们有闰秒,时钟不准确且必须调整(这可能导致时钟报告的时间倒退),精度不同的时钟等等。此外,用于处理短时间跨度(例如纳秒)的语言工具本身不应耗费大量时间。因此,chrono工具并不简单,但这些工具的许多用途却可以非常简单。
C 风格时间工具见 §4.3.6 。
35.2.1 duration
在 <chrono> 中,标准库提供了类型duration 来表示两个时间点之间的时间段(time_points §35.2.2):
template<typename Rep, typename Period = ratio<1>>
class duration {
public:
using rep = Rep;
using period = Period;
// ...
};
duration<Rep,Period> (§iso.20.11.5) | |
duration d {}; | 默认构造函数;d成了{Rep{},Period{}};constexpr |
duration d {r}; | 从r构造; r必须能够无窄化地置换为Rep;explicit |
duration d {d2}; | 复制构造函数:d获得与 d2 相同的值; d2必须能够无窄化地置换为Rep;explicit |
d=d2 | d获得与 d2 相同的值; d2必须能够无窄化地置换为Rep;explicit |
r=d.count() | r是d中时钟的滴答数;constexpr |
我们可以用具体的period来定义duration。例如:
duration<long long,milli> d1 {7}; // 7 毫秒
duration<double ,pico> d2 {3.33}; // 3.33 皮秒
duration<int,ratio<1,1>> d3 {}; // 0 称
duration的period保存了该period的时钟滴答数。
cout << d1.count() << '\n'; // 7
cout << d2.count() << '\n'; // 3.33
cout << d3.count() << '\n'; // 0
自然地,count()的值取决于 period:
d2=d1;
cout << d1.count() << '\n'; // 7
cout << d2.count() << '\n'; // 7e+009
if (d1!=d2) cerr<<"insane!";
这里,d1 和 d2 相等但报告的 count() 值却非常不同。
初始化过程中需注意避免截断或精度损失(即使未使用 {} 符号)。例如:
duration<int, milli> d {3}; // OK
duration<int, milli> d {3.5}; // error : 3.5转为 int是窄化
duration<int, milli> ms {3};
duration<int, micro> us {ms}; // OK
duration<int, milli> ms2 {us}; // error :我们会丢失很多毫秒
标准库提供了对duration的有意义的算术运算:
duration<Rep,Period> (§iso.20.11.5) r是一个Rep ,算术按表示的common_type执行 | |
++d | ++d.r |
d++ | duration{d.r++} |
−−d | −−d.r |
d−− | duration{d.r−−} |
+d | d |
-d | duration{−d.r} |
d+=d2 | d.r+=d2.r |
d-=d2 | d.r−=d2.r |
d%=d2 | d.r%=d2.r.count() |
d%=r | d.r+=r |
d∗=r | d.r+=r |
d/=r | d.r+=r |
period是一个单位制,因此不存在取普通值的 = 或 += 。允许这样做就好比允许在长度(以米为单位)上加5个未知的SI单位。考虑一下:
duration<long long,milli> d1 {7}; // 7 milliseconds
d1 += 5; // error
duration<int,ratio<1,1>> d2 {7}; // 7 seconds
d2 = 5; // error
d2 += 5; // error
这里的 5 是什么意思?5 秒?5 毫秒?还是别的什么?如果你知道自己的意思,就明确说明。例如:
d1 += duration<long long,milli>{5}; // OK: 毫秒
d3 += decltype(d2){5}; // OK: 秒
只要组合便理,涉及具有不同表示形式的duration的算术运算是允许的(§35.2.4):
duration<Rep,Period> (§iso.20.11.5) r是一个Rep ,算术按表示的common_type执行 | |
d3=d+d2 | constexpr |
d3=d−d2 | constexpr |
d3=d%d2 | constexpr |
d2=d%r | d2=d%r.count(); constexpr |
d2=d∗x | x是一个duration或一个Rep; constexpr |
d2=r∗d | constexpr |
d2=d/x | x是一个duration或一个Rep; constexpr |
支持兼容表示形式的duration之间的比较和显式转换:
duration<Rep,Period> (§iso.20.11.5) | |
d=zero() | Rep:d=duration{duration_values<rep>::z ero()} 的0;constexpr |
d=min() | 最小的Rep值(小于或等于zero()):d=duration{duration_values<rep>::min()};constexpr |
d=max() | 最大的Rep值(大于或等于zero()):d=duration{duration_values<rep>::max()};constexpr |
d==d2 | 比较是按 d 和 d2 的 common_type 进行的;constexpr |
d!=d2 | !(d==d2) |
d<d2 | 比较是按 d 和 d2 的 common_type 进行的;constexpr |
d<=d2 | !(d>d2) |
d>d2 | 比较是按 d 和 d2 的 common_type 进行的;constexpr |
d>=d2 | !(d<d2) |
d2=duration_cast<D>(d) | 将duration d 转换为duration类型 D; 表示形式或周期不使用隐式转换;constexpr |
标准库提供了一些使用 <ratio> (§35.3) 中的 SI 单位的便捷别名:
using nanoseconds = duration<si64,nano>;
using microseconds = duration<si55,micro>;
using milliseconds = duration<si45,milli>;
using seconds = duration<si35>;
using minutes = duration<si29,ratio<60>>;
using hours = duration<si23,ratio<3600>>;
这里,siN 表示“至少 N 位的实现定义的有符号整数类型”。
Duration_cast 用于获取已知单位的持续时间。例如:
auto t1 = system_clock::now();
f(x); // do something
auto t2 = system_clock::now();
auto dms = duration_cast<milliseconds>(t2−t1);
cout << "f(x) took " << dms.count() << " milliseconds\n";
auto ds = duration_cast<seconds>(t2−t1);
cout << "f(x) took " << ds.count() << " seconds\n";
在这个例子中,我们需要进行强制转换的原因是,我们会丢弃一些信息:在我使用的系统上,system_clock 以纳秒(nanoseconds)为单位。
或者,我们可以简单地(尝试)构建一个合适的duration:
auto t1 = system_clock::now();
f(x); // do something
auto t2 = system_clock::now();
cout << "f(x) took " << milliseconds(t2−t1).count() << " milliseconds\n"; // error : tr uncation
cout << "f(x) took " << microseconds(t2−t1).count() << " microseconds\n";
时钟的精度取决于实现。
35.2.2 time_point
在 <chrono> 中,标准库提供了 time_point 类型来表示给定clock测量的给定纪元的时间点:
template<typename Clock, typename Duration = typename Clock::duration>
class time_point {
public:
using clock = Clock;
using duration = Duration;
using rep = typename duration::rep;
using period = typename duration::period;
// ...
};
纪元(epoch)是由clock确定的时间范围,以持续时间来衡量,从持续时间 ::zero() 开始:
time_point<Clock,Duration> (§iso.20.11.6) | |
time_point tp {}; | 默认构造函数:纪元的开始:duration::zero() |
time_point tp {d}; | 构造函数:纪元时间点d:time_point{}+d; explicit |
time_point tp {tp2}; | 构造函数:tp 指向与 tp2 相同的时间点; tp2 的持续时间类型必须能够隐式转换为 tp 的持续时间类型 |
d=tp.time_since_epoch() | d 是 tp 的存储时长 |
tp=tp2 | t 倾向于与 tp2 相同的时间点; tp2 的持续时间类型必须能够隐式转换为 tp 的持续时间类型 |
例如:
void test()
{
time_point<steady_clock,milliseconds> tp1(milliseconds(100));
time_point<steady_clock,microseconds> tp2(microseconds(100∗1000));
tp1=tp2; // error : 会截断
tp2=tp1; // OK
if (tp1!=tp2) cerr << "Insane!";
}
和duration一样,time_point 支持有意义的算术和比较:
time_point<Clock,Duration> (continued) (§iso.20.11.6) | |
tp+=d | tp前移:tp.d+=d |
tp-=d | tp后移:tp.d-=d |
tp2=tp+d | tp2=time_point<Clock>{tp.time_since_epoc()+d} |
tp2=d+tp | tp2=time_point<Clock>{d+tp.time_since_epoc()} |
tp2=tp−d | tp2=time_point<Clock>{tp.time_since_epoc()−d} |
d=tp−tp2 | d=duration{tp.time_since_epoc()−tp2.time_since_epoc()} |
tp2=tp.min() | tp2=time_point(duration::min()); static; constexpr |
tp2=tp.max() | tp2=time_point(duration::max()); static; constexpr |
tp==tp2 | tp.time_since_epoch()==tp2.time_since_epoch() |
tp!=tp2 | !(tp==tp2) |
tp<tp2 | tp.time_since_epoch()<tp2.time_since_epoch() |
tp<=tp2 | !(tp2<tp) |
tp>tp2 | tp2<tp |
tp>=tp2 | !(tp<tp2) |
tp2=time_point_cast<D>(tp) | 将 time_point tp 转换为 time_point<C,D>: time_point<C,D>(duration_cast<D>(t.time_since_epoch())) |
例如:
void test2()
{
auto tp = steady_clock::now();
auto d1 = time_point_cast<hours>(tp).time_since_epoch().count()/24; // days since start of epoch
using days = duration<long,ratio<24∗60∗60,1>>; //a day’s duration
auto d2 = time_point_cast<days>(tp).time_since_epoch().count(); // days since start of epoch
if (d1!=d2) cout << "Impossible!\n";
}
不访问时钟的 time_point 操作可以是 constexpr,但目前不能保证如此。
35.2.3 时钟(Clocks)
time_point 和 duration 值最终从硬件时钟获取。<chrono> 中,标准库提供了时钟的基本接口。system_clock 类表示从系统实时时钟获取的“挂钟时间”。
class system_clock {
public:
using rep = /* 实现定义的有符号类型 */;
using period = /* 实现定义的 ratio<> */;
using duration = chrono::duration<rep,period>;
using time_point = chrono::time_point<system_clock>;
// ...
};
所有数据和函数成员都是static。我们不显式地处理时钟对象。相反,我们使用时钟类型:
时钟成员(§iso.20.11.7) | |
is_steady | 这种时钟类型稳定吗?也就是说,对于所有连续的 now() 调用,是否有c.now()<=c.now(),并且时钟滴答之间的时间间隔是否恒定?static |
tp=now() | tp 是system_clock的调用时间的time_point;noexcept |
t=to_time_t(tp) | t 是 time_point tp 的 time_t (§43.6);noexcept |
tp=from_time_t(t) | tp 是 time_t t 的 time_point;noexcept |
例如:
void test3()
{
auto t1 = system_clock::now();
f(x); // do something
auto t2 = system_clock::now();
cout << "f(x) took " << duration_cast<milliseconds>(t2−t1).count() << " ms";
}
一个系统提供了三种命名时钟:
时钟类型(§iso.20.11.7) | |
system_clock | 系统的实时时钟;系统时钟可能会被重置(向前或向后跳转)以匹配外部时钟 |
steady_clock | 时间不断向前移动的时钟;也就是说,时间不会倒流,时钟滴答之间的时间是恒定的 |
high_resolution_clock | 系统中时间增量最短的时钟。 |
这三种时钟不需要不同;标准库时钟名称可以是别名。
我们可以像这样确定时钟的基本属性:
cout << "min " << system_clock::duration::min().count()
<< ", max " << system_clock::duration::max().count()
<< ", " << (treat_as_floating_point<system_clock::duration>::value ? "FP" : "integral") << '\n';
cout << (system_clock::is_steady?"steady\n": "not steady\n");
当我在我的一个系统上运行它时,产生了以下结果:
min −9223372036854775808, max 9223372036854775807, integral
not steady
不同的系统和不同的时钟会产生不同的结果。
35.2.4 时间特征类(Time Trais)
chrono设施的实现依赖于一些标准设施,统称为时间特征(time traits)。
duration 和 time_point 的转换规则取决于它们的表示形式是浮点数(以便可以接受舍入)还是整数:
template<typename Rep>
struct treat_as_floating_point : is_floating<Rep> { };
标准库提供了一些标准值:
duration_values<Rep> (§iso.20.11.4.2) | |
r=zero() | r=Rep(0); static; constexpr |
r=min() | r=numeric_limits<Rep>::lowest(); static; constexpr |
r=max() | r=numeric_limits<Rep>::max(); static; constexpr |
两个久期的共同类型是通过计算它们的最大公约数 (GCD) 来确定的:
template<typename Rep1, typename P1, typename Rep2, typename P2>
struct common_type<duration<Rep1,P1>, duration<Rep2, P2>> {
using type = duration<typename common_type<Rep1,Rep2>::type, GCD<P1,P2>> ;
};
这使得 type 成为具有最大可能滴答周期的duration的别名,以便两个duration参数都可以转换为该值,而无需进行除法运算。这意味着 common_type<R1,P1,R2,P2>::type 可以保存duration<R1,R2> 和duration<R2,P2> 中的任意值,而不会出现截断错误。但是,浮点duration可能存在舍入误差。
template<typename Clock, typename Duration1, typename Duration2>
struct common_type<time_point<Clock, Duration1>, time_point<Clock, Duration2>> {
using type = time_point<Clock, typename common_type<Duration1, Duration2>::type>;
};
换句话说,要拥有一个common_type,两个time_point 必须拥有一个共同的时钟类型。它们的common_type 是一个time_point,其common_type 与它们的duration相同。
35.3 编译时比率运算(Compile-Time Rational Arithmetic)
在 <ratio> 中,我们找到了类 ratio,它提供了编译时比率运算。标准库使用 ratio 来提供时间长度和时间点的编译时表示(§35.2):
Ratio算术运算 (§iso.20.10.4) | |
z=ratio_add<x,y> | z.num=x::num∗y::den+y::num∗x::den; z.den=x::den∗y::den |
z=ratio_subtract<x,y> | z.num=x::num∗y::den−y::num∗x::den; z.den=x::den∗y::den |
z=ratio_multiply<x,y> | z.num=x::num∗y::num; z.den=x::den∗y::den |
z=ratio_divide<x,y> | z.num=x::num∗y::den; z.den=x::den∗y::num |
ratio_equal<x,y> | x::num==y::num && x::den==y::den |
ratio_not_equal<x,y> | !ratio_equal<x,y>::value |
ratio_less<x,y> | x::num∗y::den < y::num∗x::den |
ratio_less_equal<x,y> | !ratio_less_equal<y,x>::value |
ratio_not_equal<x,y> | !ratio_less<y,x>::value |
ratio_greater<x,y> | ratio_less<y,x>::value |
ratio_greater_equal<x,y> | !ratio_less<x,y>::value |
例如:
static_assert(ratio_add<ratio<1,3>, ratio<1,6>>::num == 1, "problem: 1/3+1/6 != 1/2");
static_assert(ratio_add<ratio<1,3>, ratio<1,6>>::den == 2, "problem: 1/3+1/6 != 1/2");
static_assert(ratio_multiply<ratio<1,3>, ratio<3,2>>::num == 1, "problem: 1/3∗3/2 != 1/2");
static_assert(ratio_multiply<ratio<1,3>, ratio<3,2>>::den == 2, "problem: 1/3∗3/2 != 1/2");
显然,这不是表达数字和算术的便捷方式。在 <chrono> 中,我们找到了时间比率算术的常规符号(例如 + 和 ∗)(§35.2)。同样,为了帮助表达单位值,标准库提供了常用的SI单位制量级名称:
using yocto = ratio<1,1000000000000000000000000>; // 条件支持
using zepto = ratio<1,1000000000000000000000>; // 条件支持
using atto = ratio<1,1000000000000000000>;
using femto = ratio<1,1000000000000000>;
using pico = ratio<1,1000000000000>;
using nano = ratio<1,1000000000>;
using micro = ratio<1,1000000>;
using milli = ratio<1,1000>;
using centi = ratio<1,100>;
using deci = ratio<1,10>;
using deca = ratio<10,1>;
using hecto = ratio<100,1>;
using kilo = ratio<1000,1>;
using mega = ratio<1000000,1>;
using giga = ratio<1000000000,1>;
using tera = ratio<1000000000000,1>;
using peta = ratio<1000000000000000,1>;
using exa = ratio<1000000000000000000,1>;
using zetta = ratio<1000000000000000000000,1>; // 条件支持
using yotta = ratio<1000000000000000000000000,1>; // 条件支持
有关使用示例,请参阅§35.2.1。
35.4 类型函数(Type Functions)
在 <type_traits> 中,标准库提供了类型函数(§28.2)来确定类型的属性(类型特征;§35.4.1)以及从现有类型生成新类型(类型生成器;§35.4.2)。这些类型函数主要用于编译时,以支持简单和复杂的逾编程(metaprogramming)。
35.4.1 类型特征(Type Traits)
在 <type_traits> 中,标准库提供了大量的类型函数,允许程序员确定一个类型或一对类型的属性。它们的名称大多一目了然。主要类型谓词测试类型的基本属性:
主要类型谓语 (§iso.20.9.4.1) | |
is_void<X> | 是否为X void? |
is_integral<X> | X是否为整数? |
is_floating_point<X> | X是否为浮点类型? |
is_array<X> | X是否为内置类型? |
is_pointer<X> | X是否为指针(不包括指向成员的指针)? |
is_lvalue_reference<X> | X是否为一个左值指针? |
is_rvalue_reference<X> | X是否为一个右值指针? |
is_member_object_pointer<X> | X是否为一个指向非static数据成员的指针? |
is_member_function_pointer<X> | X是否为一个指向非static成员函数的指针? |
is_enum<X> | X是否为一个enum(无论是普通的还是class enum)? |
is_union<X> | X是否为一个union |
is_class<X> | X是否为一个class(包括struct 但不包括enum)? |
is_function<X> | X是否为一个函数? |
类型特征返回一个bool值。要访问该值,请使用后缀 ::value。例如:
template<typename T>
void f(T& a)
{
static_assert(std::is_floating_point<T>::value ,"FP type expected");
// ...
}
如果你不喜欢 ::value 符号,则可以定义一个 constexpr 函数(§28.2.2):
template<typename T>
constexpr bool Is_floating_point<T>()
{
return std::is_floating_point<T>::value;
}
template<typename T>
void f(T& a)
{
static_assert(Is_floating_point<T>(),"FP type expected");
// ...
}
理想情况下,使用为所有标准库类型特征提供此类功能的库。
某些类型函数可查询基本属性的组合:
组合类型谓语 (§iso.20.9.4.2) | |
is_reference<X> | x 是引用(左值或右值引用)吗? |
is_arithmetic<X> | x 是算术类型(整数或浮点数;§6.2.1)吗? |
is_fundamental<X> | x是基本类型(§6.2.1)吗? |
is_object<X> | x 是对象类型(不是函数)吗? |
is_scalar<X> | x 是标量类型吗(不是类或函数)? |
is_compound<X> | x是复合类型 (!is fundamental<X>) 吗? |
is_member_pointer<X> | x 是指向非静态数据或函数成员的指针吗? |
这些组合类型谓词仅仅提供了符号上的便利。例如,如果 X 是左值引用或右值引用,则 is_reference<X> 为真。
与主要类型谓词一样,类型属性谓词为类型的基本方面提供测试:
类型属性谓语 (§iso.20.9.4.3) | |
is_const<X> | X 是 const 吗? |
is_volatile<X> | X 是否为易失性(§41.4)? |
is_trivial<X> | X 是简单类型吗(§8.2.6)? |
is_trivially_copyable<X> | X 是否可以作为简单的位集合被复制、移动和销毁(§8.2.6)? |
is_standard_layout<X> | X 是标准布局类型(§8.2.6)吗? |
is_pod<X> | X 是 POD 吗(§8.2.6)? |
is_literal_type<X> | X 是否有一个 constexpr 构造函数(§10.4.3)? |
is_empty<X> | X 是否有需要对象空间的成员? |
is_polymorphic<X> | X 有虚函数吗? |
is_abstract<X> | X 有纯虚函数吗? |
is_signed<X> | X 是算术类型并且是有符号吗? |
is_unsigned<X> | X 是算术类型并且是无符号吗? |
is_constructible<X,args> | 可以从参数构造 X 吗? |
is_default_constructible<X> | 可以从 {} 构造 X 吗? |
is_copy_constructible<X> | 可以从 X& 构造 X 吗? |
is_move_constructible<X> | 可以从 X&& 构造 X 吗? |
is_assignable<X,Y> | 可以将 Y 分配给 X 吗? |
is_copy_assignable<X> | 可以将 X& 分配给 X 吗? |
is_move_assignable<X> | 可以将 X&& 分配给 X 吗? |
is_destructible<X> | X 可以被销毁吗(即 ˜X() 尚未被删除)? |
例如:
template<typename T>
class Cont {
T∗ elem; // store elements in an array pointed to by elem
int sz; // sz elements
// ...
Cont(const Cont& a) // copy constructor
:sz(a.sz), elem(new T[a.elem])
{
static_assert(Is_copy_constructable<T>(),"Cont::Cont(): no copy");
if (Is_trivially_copyable<T>())
memcpy(elem,a.elem,sz∗sizeof(T)); // memcopy optimization
else
uninitialized_copy(a.begin(),a.end(),elem); // use copy constructors
}
// ...
}
但这种优化可能是不必要的,因为 uninitialized_copy() 很可能已经以这种方式进行了优化。
对于空类,它不能有虚函数、没有虚基,也不能有 !is_empty<Base>::value 的基类。
类型属性谓词不会根据使用位置进行访问检查。相反,它们会始终如一地给出你对成员和友元之外的使用所期望的结果。例如:
class X {
public:
void inside();
private:
X& operator=(const X&);
˜X();
};
void X::inside()
{
cout << "inside =: " << is_copy_assignable<X>::value << '\n';
cout << "inside ˜: " << is_destructible<X>::value << '\n';
}
void outside()
{
cout << "outside =: " << is_copy_assignable<X>::value << '\n';
cout << "outside ˜: " << is_destructible<X>::value << '\n';
}
inside() 和 outside() 都会写入 00,以报告 X 既不可销毁,也不可复制赋值。此外,如果要删除某个操作,请使用 =delete (§17.6.4),而不是将其作为private操作。
类型属性谓语 (§iso.20.9.4.3) | |
is_trivially_constructible<X,args> | 是否可以仅使用简单的操作从参数构造 X ? |
is_trivially_default_constructible<X> | |
is_trivially_copy_constructible<X> | §8.2.6 |
is_trivially_move_constructible<X> | |
is_trivially_assignable<X,Y> | |
is_trivially_copy_assignable<X> | |
is_trivially_move_assignable<X> | |
is_trivially_destructible<X> |
例如,考虑可以如何优化容器类型的析构函数:
template<class T>
Cont::˜Cont() // destructor for a container Cont
{
if (!Is_trivially_destructible<T>())
for (T∗ p = elem; p!=p+sz; ++p)
p−>˜T();
}
类型属性谓语 (§iso.20.9.4.3) | |
is_nothrow_constructible<X,args> | 是否可以仅使用 noexcept 操作从参数构造 X? |
is_nothrow_default_constructible<X> | |
is_nothrow_copy_constructible<X> | §8.2.6 |
is_nothrow_move_constructible<X> | |
is_nothrow_assignable<X,Y> | |
is_nothrow_copy_assignable<X> | |
is_nothrow_move_assignable<X> | |
is_nothrow_destructible<X> | |
has_virtual_destructor<X> | X 有虚拟析构函数吗? |
与 sizeof(T) 类似,属性查询返回与类型参数相关的数值:
类型属性查询 (§iso.20.9.5) | |
n=alignment_of<X> | n=alignof(X) |
n=rank<X> | 如果 X 是数组,则 n 是维数;否则 n==0 |
n=extent<X,N> | 如果 X 是数组,则 n 是第 N 维元素的数量;否则 n==0 |
n=extent<X> | n=extent<X,0> |
例如:
template<typename T>
void f(T a)
{
static_assert(Is_array<T>(), "f(): not an array");
constexpr int dn {Extent<a,2>()}; // 第2维中的元素数量(基于0)
// ..
}
在这里,我再次使用了返回数值的类型函数的 constexpr 版本(§28.2.2)。
类型关系基于两种类型:
类型关系 (§iso.20.9.6) | |
is_same<X,Y> | X与Y类型相同吗? |
is_base_of<X,Y> | X是Y的基类型吗? |
is_convertible<X,Y> | 一个X可以隐式地转换为Y吗? |
例如:
template<typename T>
void draw(T t)
{
static_assert(Is_same<Shape∗,T>() || Is_base_of<Shape,Remove_pointer<T>>(), "");
t−>draw();
}
35.4.2 类型生成器(Type Generators)
在 <type_traits> 中,标准库提供了用于通过给定其他类型作为参数来生成一个类型的类型函数。
const和volatile修改工具 (§iso.20.9.7.1) | |
remove_const<X> | 类似于 X,但删除了所有顶层的 const |
remove_volatile<X> | 类似于 X,但删除了所有顶层的 volatile |
remove_cv<X> | 类似于 X,但删除了所有顶层的const或volatile |
add_const<X> | 若X是一个引用,函数,或const,则为X ;否则为 const X |
add_volatile<X> | 若X是一个引用,函数,或volatile,则为X ;否则为 volatile X |
struct add_cv<X> | 添加 const 和volatile :add_const<typename add_volatile<T>::type>::type |
类型转换器返回一个类型。要访问该类型,请使用后缀 ::type。例如:
template<typename K, typename V>
class My_map {
{
pair<typename add_const<K>::type ,V> default_node;
// ...
};
如果你不喜欢 ::type ,可以定义类型别名(§28.2.1):
template<typename T>
using Add_const = typename add_const<T>::type;
template<typename K, typename V>
class My_map {
{
pair<Add_const<K>,V> default_node;
// ...
};
在理想情况下,使用为标准库类型转换器系统地提供此类别名的支持库。
引用修改 (§iso.20.9.7.2,§iso.20.9.7.6) | |
remove_reference<X> | 若X是一个引用类型,则为引用类型,否则为X |
add_lvalue_reference<X> | 若X是一个右值引用类型Y&& ,则为 Y&;否则为X& |
add_r value_reference<X> | 若X是一个引用类型,则为X; 否则为 X&& |
decay<X> | 通过值传递的类型X的函数参数的类型 |
decay函数处理数组类型“衰减”(即类型转换)以及引用解引用。
在编写模板时,添加和删除引用的类型函数非常重要,因为模板应该处理可能为引用也可能不是引用的参数。例如:
template<typename T>
void f(T v)
{
Remove_reference<T> x = v; // v的副本
T y = v; // 可能为v的一个副本;可能为x的一个引用
++x; /递增局部变量
++y;
// ...
}
这里,x 确实是 v 的副本,但如果 T 是引用类型,则 y 是对 v 的引用:
void user()
{
int val = 7;
f(val); // 调用f<int&>(): f() 中的 ++y 将递增 val
f(7); // 调用f<int>(): f 中的 ++y 将递增一个局部副本
}
在这两个调用中,++x 都会增加一个本地副本。
符号修改 (§iso.20.9.7.3) | |
make_signed<X> | 删除所有(显式或隐式) unsigned修饰符并添加signed修饰符;X 必须是整数类型(布尔值或枚举除外) |
make_unsigned<X> | 删除所有(显式或隐式)signed 修饰符并添加 unsigned;X 必须是整数类型(bool或枚举除外) |
对于内置数组,我们有时想要获取元素类型或删除一个维度:
数组修改 (§iso.20.9.7.4) | |
remove_extent<X> | 若X 是一个数组类型,则结果为元素类型;否则结果为X |
remove_all_extents<X> | 若X 是一个数组类型,则结果为基类型;否则结果为X |
例如:
int a[10][20];
Remove_extent<decltype(a)> a10; // an array[10]
Remove_all_extents<decltype(a)> i; // an int
我们可以创建一个指向任意类型的指针类型,或者求得指向的类型:
指针修改 (§iso.20.9.7.5) | |
remove_pointer<X> | 若X 是一个指针类型,则结果为指向的类型;否则结果为X |
add_pointer<X> | remove_reference<X>::type∗ |
例如:
template<typename T>
void f(T x)
{
Add_pointer<T> p = new Remove_reference<T>{};
T∗ p = new T{}; // would not wor k if T is a reference
// ...
}
在处理系统最底层的内存时,我们有时必须考虑内存对齐问题(§6.2.9):
内存对齐 (§iso.20.9.7.6) | |
aligned_storage<n,a> | 一个 POD 类型,其大小至少为 n,且其对齐方式是a的一个除数 |
aligned_storage<n> | aligned_storage<n,def>,其中,def是任意对象 T (sizeof(T)<=n) 所要求的最大对齐字节。 |
aligned_union<n,X...> | 可以容纳具有 X 类型成员的union的大小至少为 n 的 POD 类型 |
标准提到的这个可能是 aligned_storage 的可能实现:
template<std::size_t N, std::size_t A>
struct aligned_storage {
using type = struct { alignas(A) unsigned char data[N]; }; //与A对齐的N 个chars (§6.2.9)
};
最后的类型函数用于类型选择、计算常见类型等,可以说是最有用的:
其它转换 (§iso.20.9.7.6) | |
enable_if<b,X> | 若 b==true 则结果为 X;否则没有成员::type,导致大多数用途的替换失败(§23.5.3.2) |
enable_if<b> | enable_if<b,void> |
conditional<b,T,F> | 若 b==true 则结果为 T;否则结果为F |
common_type<X> | 参数包 X 的所有类型的通用类型;如果两种类型可以用作 ?:表达式的真和假类型,则它们是通用的 |
underlying_type<X>result_of<FX> | X 的底层类型(§8.4);X 必须是枚举 F(X) 结果的类型;FX 必须是 F(X) 类型,其中 F 使用参数列表 X 调用 |
有关 enable_if 和 conditional 的示例,请参阅 §28.3.1.1 和 §28.4。
求得一个可以对多种类型进行运算的类型通常很有用,例如,两个相关但不同类型的值相加的结果。类型函数 common_type 可以求得这样的通用类型。一个类型是其自身的通用类型(显然):
template<typename ...T>
struct common_type;
template<typename T>
struct common_type<T> {
using type = T;
};
两种类型的共同类型是正是 ?: 的规则(§11.1.3)给到我们的规则:
template<typename T, typename U>
struct common_type<T, U> {
using type = decltype(true ? declval<T>() : declval<U>());
};
declval<T>() 类型函数返回未估算类型 T 变量的类型。
通过递归应用 N==1 和 N==2 的规则可以求得 N 类型的公共类型:
template<typename T, typename U, typename ... V>
struct common_type<T, U, V...> {
using type = typename common_type<typename common_type<T, U>::type , V...>::type;
};
例如:
template<typename T, typename U>
using Common_type = typename common_type<T,U>::type;
Common_type<int,double> x1; // x1 is a double
Common_type<int,string> x2; // error : no common type
Common_type<int,short,long,long long> x3; // x3 is a long long
Common_type<Shape∗,Circle∗>x4; //x4 is a Shape*
Common_type<void∗,double∗,Shape∗> x5; //x5 is a void*
Result_of 用于提取可调用类型的结果的类型:
int ff(int) { return 2; } // function
typedef bool (∗PF)(int); //pointer to function
struct Fct { // function object
double operator()(string);
string operator()(int,int);
};
auto fx = [](char ch) { return tolower(ch); }; // lambda
Result_of<decltype(&ff)()> r1 = 7; // r1 is a int
Result_of<PF(int)> r2 = true; // r2 is a bool
Result_of<Fct(string)> r3 = 9.9; // r3 is a double
Result_of<Fct(int,int)> r4 = "Hero"; // r4 is a string
Result_of<decltype(fx)(char)> r5 = 'a'; // r5 is a char
请注意,Result_of 可以区分 Fct::operator()() 的两个版本。
奇怪的是,同样的情况并不适用于非成员函数。例如:
int f(); // function
string f(int);
Result_of<decltype(&f)()> r1 = 7; // error : no overload resolution for pointer to function
遗憾的是,我们没有对指向函数的指针进行重载解析,但是为什么我要以如此迂回的方式使用 Result_of,而不是:
Result_of<ff> r1 = 7; // error : 无参数规范,
// 而ff 是一个函数而不是一种类型
Result_of<ff()> r1 = 7; // error : Result_of 的参数必须是一种类型
Result_of<decltype(f)()> r2 = 7; // error : decltype(f) 是一个函数类型
// 而不是一个函数指针
Result_of<decltype(f)∗()> r3 = 7; // OK: r3是一个int
当然,Result_of 通常出现在模板中,因为我们无法轻易在程序文本中查找答案。例如:
template<typename F, typename A>
auto temp(F f, A a) −> Result_of<F(A)>
{
// ...
}
void f4()
{
temp(ff,1);
temp(fx,'a');
temp(Fct(),"Ulysses");
}
请注意,函数 ff 在调用中被转换为指针函数,因此 Result_of 中对指向函数的指针的依赖并不像乍看起来那么奇怪。
declval() (§iso.20.2.4) | |
declval<T>() | 对于 T: typename add_r value_reference<T>::type 返回一个右值;从不使用 declval() 的返回值 |
declval() 类型的函数在标准库中并不常见,因为它本身就是一个函数(无需用户包装)。它返回一个永远不能使用的值。其目的是在需要 X 类型变量时使用 declval<X> 作为类型。例如:
template<typename T, siz e_t N>
void array<T,N> swap(array& y) noexcept(noexcept(swap(declval<T&>(), declval<T&>())))
{
for (int i=0; i<a.size(); ++i)
swap((∗this)[i],a[i]);
}
另见 common_type 的定义。
35.5 其它小工具(Minor Utilities)
这些实用程序规模较小,不是那么重要。它们不适合归入更大的组别。
35.5.1 move() 和 forward()
在 <utility> 中,我们发现了一些最有用的小功能:
其它转换(§iso.20.9.7.6) | |
x2=forward(x) | x2是一个右值;x可能是一个左值;noexcept |
x2=move(x) | x2是一个右值; noexcept |
x2=move_if_noexcept(x) | 若x可移动,则 x2=move(x) ;否则 x2=x;noexcept |
move() 只不过是一个向右值的转换:
template<typename T>
Remove_reference<T>&& move(T&& t) noexcept
{
return static_cast<Remove_reference<T>&&>(t);
}
我认为 move() 应该称为 rvalue(),因为它实际上并没有移动任何东西。相反,它为其参数生成一个右值,以便可以移动所引用的对象。
move() 用于告诉编译器某个对象将不再在上下文中使用,以便可以移动其值并留下一个空对象。最简单的示例是 swap() 的实现(§35.5.2)。
forward() 仅从右值生成右值:
template<typename T>
T&& forward(Remove_reference<T>& t) noexcept
{
return static_cast<T&&>(t);
}
template<typename T>
T&& forward(Remove_reference<T>&& t) noexcept;
{
static_assert(!Is_lvalue_reference<T>,"forward of lvalue");
return static_cast<T&&>(t);
}
这对 forward() 函数应该始终同时可用,并且它们之间的选择应该通过重载解析来完成。在这种情况下,任何左值都会转到第一个版本,而所有右值都会转到第二个版本。例如:
int i = 7;
forward(i); // call first version
forward(7); // call second version
断言是为那些自作聪明的程序员准备的,它会使用显式模板参数和左值来调用第二个版本。
forward() 的典型用途是将参数从一个函数“完美转发”到另一个函数(§23.5.2.1,§28.6.3)。标准库中的 make_shared<T>(x)(§34.3.2)就是一个很好的例子。
如果目的是通过移动操作“窃取”对象的表示,则使用 move() ,而使用 forward() 进行转发。因此,forward(x) 是安全的,而 move(x) 会将 x 标记为析构,因此应谨慎使用 move(x)。在 move(x) 之后,唯一安全的 x 用法是析构或作为赋值的目标。显然,特定类型可以提供进一步的保证,并且理想情况下,类的不变量保持不变。但是,除非你确实了解情况,否则不要依赖这一点。
35.5.2 swap()
在 <utility> 中,标准库提供了通用的 swap() 和针对内置数组的特化:
其它转换(§iso.20.2.2) | |
swap(x,y) | 交换x和y的值; x和y按非const的引用传递; 若x和y的复制操作是noexcept的,则声明为 noexcept |
swap(a1n,a2n) | a1n和a2n作为数组的引用传递:T(&)[N] ; 若 *a1n和*a2n的复制操作是noexcept的,则声明为 noexcept |
相对明显实现的 swap() 是:
template<typename T>
void swap(T& a, T& b) noexcept(Is_nothrow_move_constructible<T>()
&& Is_nothrow_move_assignable<T>())
{
T tmp {move(a)};
a = move(b);
b = move(tmp);
}
这意味着 swap() 不能用于交换右值:
vector<int> v1 {1,2,3,4};
swap(v,vecor<int>{}); // error : 第二个参数是右值
v.clear(); //clearer (less obscure)
35.5.3 关系运算符(Relational Operators)
在 <utility> 中,我们在子命名空间 rel_ops 中可找到任意类型的关系运算符:
std::rel_ops中的关系运算符(§iso.20.2.1) | |
x!=y | !(x==y) |
x>y | y<x |
x<=y | !(y<x) |
x>=y | !(x<y) |
这要求程序员确保 x==y 和 x<y 有效。
例如:
struct Val {
double d;
bool operator==(Val v) const { return v.d==d; }
};
void my_algo(vector<Val>& vv)
{
using namespace std::rel_ops;
for (int i=0; i<ww.siz e(); ++i)
if (0>ww[i]) ww[i]=abs(ww[i]); // OK: > from rel_ops
}
使用 rel_ops 而不污染命名空间可能会很困难。特别是:
namespace Mine {
struct Val {
double d;
bool operator==(Val v) const { return v.d==d; }
};
using namespace std::rel_ops;
}
这可能会暴露 rel_ops 中完全通用的模板,使其能够通过参数依赖查找(§14.2.4)找到,并应用于可能不适用的类型。更安全的方法是将 using 指令放置在局部作用域中。
35.5.4 比较和哈希type_info(Comparing and Hashing type_info)
在 <typeindex> 中,标准库提供了对 type_index 的比较和哈希处理的支持。type_index 是根据 type_info(§22.5)创建的,专门用于进行此类比较和哈希处理。
type_index运算(§iso.20.13) | |
tip 是指向 type_info 的指针,由 type_index 表示 | |
type_index ti {tinf}; | tip表示指向type_info tinf的指针;noexcept |
ti==ti2 | ti和ti2表示相同的type_info: ∗ti.tip==∗ti2.tip); noexcept |
ti!=ti2 | !(ti==ti2);noexcept |
ti<ti2 | ti.tip−>before(ti2.tip); noexcept |
ti<=ti2 | !ti2.tip−>before(ti.tip); noexcept |
ti>ti2 | ti2.tip−>before(ti.tip); noexcept |
ti>=ti2 | !ti.tip−>before(ti2.tip); noexcept |
n=ti.hash_code() | n=ti.tip−>hash_code() |
p=name() | p= ti.tip−>name() |
hash<type_index> | hash的一种规范(§31.4.3.4) |
例如:
unordered_map<type_index,type_info∗> types;
// ...
types[type_index{something}] = &typeid(something);
35.6 建议(Advice)
[1] 使用 <chrono> 函数,例如 steady_clock、duration 和 time_point 进行计时;§35.2。
[2] 优先使用 <clock> 函数而不是 <ctime> 函数;§35.2。
[3] 使用 duration_cast 获取已知时间单位的持续时间;§35.2.1。
[4] 使用 system_clock::now() 获取当前时间;§35.2.3。
[5] 可以在编译时查询类型的属性;§35.4.1。
[6] 仅当 obj 的值无法再次使用时才使用 move(obj);§35.5.1。
[7] 使用 forward() 进行转发;§35.5.1。
内容来源:
<<The C++ Programming Language >> 第4版,作者 Bjarne Stroustrup