Boost组件实用工具

《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[]

扫描二维码关注公众号,回复: 16705478 查看本文章

两个函数对象(模板类)
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;
}

猜你喜欢

转载自blog.csdn.net/zuolj/article/details/78541229