《C++11/14高级编程:Boost程序库探秘》笔记
讨论一些功能比较简单,但实现原理却涉及C++语言深层次概念细节的Boost组件。
compressed_pair
compressed_pair库提供一个和std::pair非常相似的模板类compressed_pair,但它针对空类成员进行了特别的优化,可以”压缩“pair的大小。
空类:指的是没有非静态成员变量(静态成员不会增加类实例大小),也没有虚函数(会导致虚表指针)的class/struct类型。
空类不含有任何数据成员,理论上不应该占据内存,但是实际使用时仍然需要占用一定的内存空间,因为C++不允许存在0大小的对象,所以编译器会暗地里在类中插入一个char使它至少有1字节的大小。
compressed__pair利用了编译器的空基类优化技术,通过模板偏特化知道模板参数是否为空类,如果是空类,它就从空类protected继承,而不是作为成员变量。
compressed__pair的类摘要:
template<class T1,class T2>
class compressed__pair
{
public:
typedef T1 first_type;
typedef T2 second_type;
...
compressed__pair();
compressed__pair(first_param_type x,sencond_param_type y);
explicit compressed__pair(first_param_type x);
explicit compressed__pair(second_param_type y);
first_reference first();
first_const_reference first() const;
second_reference second();
second_const_reference second() const;
void swap(compressed__pair& y);
};
compressed_pair不提供任何比较操作符重载。访问成员需要使用函数形式的first()和second(),它们返回内部成员的引用,可以当作左值被赋值,也可以当作右值。
compressed_pair库在boost::details子名字空间下定义了一个模板类compressed_pair_imp,是compressed_pair的实现类,被用于私有继承:
template<class T1,class T2,int Version>
class compressed_pair_imp;
它根据模板参数类型是否相同和成员是否为空这三个条件共定义了6个偏特化的compressed_pair_imp实现,再用type_traits库的is_same、is_empty、remove_cv以及details空间里的compressed_pair_switch进行编译期元计算,决定int模板参数Version的值,从而确定使用哪个版本的compressed_pair_imp。
checked_delete
checked_delete是对C++关键字delete的增强版,可以在编译期保证delete或delete[]删除的是一个指向”完整类型“的指针,避免在运行时发生未定义行为。
所谓不完整类型指仅有声明而没有定义的类,通常见于类的前向声明。
checked_delete库包含两个函数和两个函数对象,分别为:
两个函数
checked_delete:用于删除普通指针
checked_array_delete:用于删除数组指针,相当于delete[]两个函数对象(模板类)
checked_deleter
checked_array_deleter
模板函数的用法和delete、delete[]基本等价,只是把操作指针变量的delete表达式改成函数调用式:
auto p1 = new int(10);
checked_delete(p1);
auto p2 = new int[10];
checked_array_delete(p2);
//也可以删除对象指针,demo_class为一个自定义的类
auto p3 = new demo_class;
checked_delete(p3);
auto p4 = new demo_class[10];
checked_array_delete(p4);
模板类重载了operator(),可以像函数一样被调用,但不具备自动推导模板参数的功能,使用时必须用模板参数指明要删除的对象类型。
template<class T>
struct checked_deleter
{
typedef void result_type;
typedef T* argument_type;
void operator()(T* x) const;
};
template<class T>
struct checked_array_deleter
{
typedef void result_type;
typedef T* argument_type;
void operator()(T* x) const;
};
//使用时,圆括号调用构造函数生成一个临时函数对象,然后使用operator()调用删除功能
auto p1 = new demo_class;
checked_deleter<demo_class>()(p1);
auto p2 = new demo_class[10];
checked_array_deleter<demo_class>()(p2);
上面的函数对象的用法似乎很麻烦,因为它和checked_delete()是完全一样的,确增加了书写,但因为它的定义完全符合C++标准规范,故可以传递给那些需要函数对象的泛型代码,比如搭配标准库算法操作容器里的指针:
vector<demo_class*> v;
v.push_back(new demo_class);
v.push_back(new demo_class);
for_each(v.begin(),v.end(),checked_deleter<demo_class>());
checked_delete的实现原理相当简单,全部实现代码如下:
template<class T> inline void checked_deleter(T* x)
{
typedef char type_must_be_complete[sizeof(T)?1:-1];
(void) sizeof(type_must_be_complete);
delete x;
}
它通过typedef定义了一个数组类型,大小由要被删除的类型T确定,如果T是一个完整的类型,那么sizeof(T)是一个正整数,数组大小为1,否则,sizeof(T)是0,数组大小为-1,但C++中数组定义不允许为负数,所以会引发一个编译错误。checked_array_delete实现类似。
addressof
addressof是对C++取址操作(&)的增强,已被收入C++标准(头文件<memory>),主要是防止程序员重载操作符&后,使用&取址出错的问题。
addressof是一个模板函数,与checked_delete类似,用法简单。
它的实现原理是使用了复杂的转型操作,核心实现如下:
reinterpret_cast<T*>(&const_cast<char&>(
reinterpret_cast<const volatile char &>(v)));
T是addressof的模板类型参数,v是T的一个引用,addressof先使用reinterpret_cast转型操作把v强制解释成char类型,然后再将其重新解释成T*类型,这样就得到了变量的真正地址。
因为使用了多次运行时转型,所以addressof的运行效率没有原始的operator&高。
base_from_member
用于解决一种情况:当基类需要由派生类的成员变量来初始化时,C++的类初始化顺序要求基类必须在派生类之前完成初始化,一般的解决方法是把派生类的成员移到另一个辅助基类中,base_from_member使用多重继承和模板技术提供了这个用成员初始化基类的惯用法。
它的类摘要如下:
template<typename MemberType,int UniqueID = 0>
class base_from_member
{
protected:
MemberType member; //成员变量
base_from_member();
base_from_member(T1 x1);
base_from_member(T1 x1,T2 x2);
...
//默认情况下,有11个构造函数,最大支持10个参数,参数被用来在构造时初始化member成员变量。这个数量可以通过在包含头文件前定义宏BOOST_BASE_FROM_MEMBER_MAX_ARITY修改
};
用法:
class derived :
private base_from_member<complex<int>>, //第一个成员
private base_from_member<string,1>, //使用第二个模板参数
private base_from_member<string,2>, //用于区分不同的类型
public base,public base2
{
//complex<int> c; //不直接声明成员变量
//typedef简化base_from_member代码的编写
typedef base_from_member<complex<int>> pbase_type;
typedef base_from_member<string,1> pbase_type1;
typedef base_from_member<string,2> pbase_type2;
public:
derived(int a, int b) :
pbase_type(a, b),
pbase_type1("str1"),
pbase_type2("str2"),
base(pbase_type::member), //用基类名字限定来使用成员变量
base2(pbase_type1::member,pbase_type2::member)
{
cout << member << endl; //成员变量的名字是member
}
};
conversion
标准转型操作符
C++标准为显式类型转换定义了四个新的转型操作符:
- const_cast:用于增加或者去除const、volatile修饰。
- static_cast:可以显式执行所有编译器可执行的隐式类型转换操作,不能执行多态类的交叉转型。
- dynamic_cast:用于多态对象(即存在虚函数的对象)间的类型转换,可以向上或者向下转换对象的类型
- reinterpret_cast:对目标的内存二进制位进行低层次的重新解释
const_cast
去掉const属性:const_cast<int*> (&num),常用,因为不能把一个const变量直接赋给一个非const变量,必须要转换。
加上const属性:const int* k = const_cast<const int*>(j),一般很少用,因为可以把一个非const变量直接赋给一个const变量,比如:const int* k = j;
class A {
public:
int m_iNum{ 0 };
};
int main()
{
//常量指针被转化成非常量指针,转换后指针指向原来的变量(即转换后的指针地址不变)。
//1. 指针指向类
const A *pca1 = new A;
A *pa2 = const_cast<A*>(pca1); //常量对象转换为非常量对象
pa2->m_iNum = 200; //fine
//转换后指针指向原来的对象
assert(200 == pca1->m_iNum);
assert(200 == pa2->m_iNum);
//2. 指针指向基本类型
const int ica = 100;
int * ia = const_cast<int *>(&ica);
*ia = 200;
assert(100 == ica);
assert(200 == *ia);
//常量引用转为非常量引用
A a0;
const A &a1 = a0;
A a2 = const_cast<A&>(a1);
a2.m_iNum = 200;
assert(0 == a0.m_iNum);
assert(0 == a1.m_iNum);
//常量对象(或基本类型)不可以被转换成非常量对象(或基本类型)。
const A ca;
A a = const_cast<A>(ca); //error
const int i = 100;
int j = const_cast<int>(i); //error
return 0;
}
reinterpret_cast
很接近老式的显式类型转换,可以变更被转换对象的含义,最常用的场景是把一个已经失去类型信息的指针(如void*)重新解释,使其获得正确的类型。
static_cast
- 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证
- 把void指针转换成目标类型的指针(不安全!!)
- 把任何类型的表达式转换成void类型
注意:static_cast不能转换掉const、volitale、或者__unaligned属性
dynamic_cast
主要用于执行“安全的向下转型(safe downcasting)”和交叉转型。将基类的指针或引用安全的转换为派生类的指针或引用。
struct B1{
virtual ~B1(){}
};
struct B2{
virtual ~B2(){}
};
struct D1 : B1, B2{};
int main()
{
D1 d;
B1* pb1 = &d;
B2* pb2 = dynamic_cast<B2*>(pb1); //交叉转型,只要B1存在多态,dynamic_cast允许这种转型
B2* pb22 = static_cast<B2*>(pb1); //不允许,编译失败
return 0;
}
但是dynamic_cast对两种转型失败后的行为不一致,会引起程序员的困扰。
对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
conversion库针对dynamic_cast提供了增强的转型操作,用两个更安全命名更清晰的多态转型操作polymorphic_downcast和polymorphic_cast区分了多态对象的向下转型和交叉转型。
polymorphic_downcast
polymorphic_downcast提供对多态对象指针的向下转型功能,它使用static_cast提供高效的转型操作,但不具备dynamic_cast的错误检测功能。
template<class Target,class Source>
inline Target polymorphic_downcast(Source* x)
{
BOOST_ASSERT(dynamic_cast<Target>(x) == x);//断言转型成功
return static_cast<Target>(x);
}
我不能理解static_cast原本是用来做向上转型的,这里介绍它用来做向下转型的什么意思。感觉这个工具不太好理解,不太好用,还不如C++标准的dynamic_cast,看了一下后面,还不能用来转型引用,只能操作指针,所以,算了。
numeric conversion
C++标准为数值的极限提供了模板类numeric_limits,它以数值类型(char、int、double等)为参数模板,用静态成员变量和静态成员函数给出了数值的一些相关特征,但在处理整数和浮点数的极值时并不一致:整数返回数值区的上下限,浮点数返回最小绝对值和上限。
cout << numeric_limits<short>::min() << endl; //-32768
cout << numeric_limits<short>::max() << endl; //32767
cout << numeric_limits<unsigned short>::min() << endl; //0
cout << numeric_limits<unsigned short>::max() << endl; //65535
cout << numeric_limits<float>::min() << endl; //1.17549e-38
cout << numeric_limits<float>::max() << endl; //3.40282e+38
这种不一致导致在编写泛型代码必须手工区分数值类型,容易混淆概念
bounds弥补了numeric_limits的缺点,它用三个成员函数明确给出了数值类型的最大极值、最小极值和最小正规值
template<class N>
struct bounds
{
static N lowest(); //最小极值,下界
static N highest(); //最大极值,上界
static N smallest(); //最小正规值
};
using namespace boost::numeric;
cout << bounds<short>::lowest() << endl;
cout << bounds<short>::highest() << endl;
cout << bounds<short>::smallest() << endl;
cout << bounds<float>::lowest() << endl;
cout << bounds<float>::highest() << endl;
cout << bounds<float>::smallest() << endl;
assert(bounds<short>::lowest() == numeric_limits<short>::min());
assert(bounds<float>::lowest() == -numeric_limits<float>::max());
C++11修订了numeric_limits,使用constexpr关键字令所有函数都成为编译期常量,并且新增了lowest()函数,相当于bounds的lowest(),其他max()和min()没有变化。
numeric_cast
numeric_cast()函数可以在转型时进行范围检查,如果超出范围就抛出异常std::bad_cast,比static_cast安全。numeric_cast位于名字空间boost,需要头文件<boost/numeric/conversion/cast.hpp>
short s = bounds<short>::highest();
int i = boost::numeric_cast<int>(s);
assert(i == s);
try
{
char c = boost::numeric_cast<char>(s);
}
catch (std::bad_cast& e)
{
cout << e.what() << endl;
}