模板元函数(七)



2.3 数值元函数

你甚至惊讶于用上面的表达可,元函数就可以产生数值。不,我们并不是要你给一个已经是数字的名字一个类型。一个具有数值结果的元函数的 ::type 结果是一个真正的类型,一个整型常数的包装器。而它的内嵌 ::value 是一个整形常量。我们会在第 4 章中展示其具体细节,但这个时候,下面的例子可以给你一个直观的感受:

    struct five // 整形常量 5 的常数包装
    {
        static int const value = 5;
        typedef int value_type;
        ...更多声明...
    };


因此,为了取得数据元函数的 value 结果,我们可以这样写:

metafunction-name<type arguments...>::type::value

与此相应,整型常量以相似的包装器方式传递给元函数。这多出的另一层间接看起来没有那么方便,但是就现在你可以猜测这样做的意图:要求所有的元函数接收并返回的类型更加统一、多态、以及更具有交互性。你将会在下面的几个章节中看到许多 FTSE(“软件工程的基础理论”) 这样的例子在应用时的好处。[5]

[5] 你可能已经注意到元函数的协议看起来是要阻止我们取得这样的目标,此目标正是我们使得元函数具有多态性的原因:我们希望能够写以其他元函数为参数的元函数。因为元函数就是模板,而不是类型,我们不能将他们传递给需要类型的地方。在现在,我们将只是请你先不要怀疑这一章的剩余内容。我们保证会在第三章中处理这个问题。

除开那些所有的好处,在任何时候你想计算某个实际整常数的时候写 ::type::value 也有点郁闷,纯粹是为了方便,数值元函数的作者有可能在元函数中提供一个相同的内嵌的 ::value,在此书中我们所引用的 Boost 库中的所有的数值元函数都是这么处理的。你应该注意到,尽管在我们知道在调用元函数时知道其提供了 ::value 成员,并且可以充分利用它,但是一般来说,你也不要总是依赖于 ::value  这样的成员,就算你知道元函数产生一个数值结果。


2.4 编译时选择
如果你此时信息没有因为类型计算而使你心潮澎湃,我们也不能怪你。老实说,使用元函数去查找一个迭代器的 value_type 并不比表查找有更多内容。如果这样的“类型计算”注意有腿的话,那将会比类型关联的内容要丰富得多。

2.4.1 更多的 iter_swap
为了在实际代码中展示如何使用元函数,让我们回到“C++ 标准库实现者”这个角色中来。非常抱歉地说,我们现在将从性能要求很高的用户那里收到一堆的 Bug 报告,报怨我们在 2.1.3 中所定义的 iter_swap 对于某些迭代器非常的低效。很明显有个家伙试着将 std::list<std::vector<std::string> > 的迭代器传递了进去,而这个迭代器则对 string 的 vectors 进行迭代,他们性能指示器会告诉他, iter_swap 就是性能瓶颈。


后知后觉中,我们也不会惊讶:在 iter_swap 中的第一条语句就是对迭代器所参考的值生成一个复本。因为复制一个 vector 意味着复制其所有的元素,而 string 元素的复值及拷贝有可能需要动态内存的分配以及对 string 的字符进行按位复制。这咋看之下就是性能不佳。

幸运的是,我们有解决办法。因为标准库提供了一个高效版本的 swap 版本可以在交换 vector 时只交换少数几个内部指针,我们可以告诉用户只要简单地解引用迭代器并调用结果调用 swap 函数:

    std::swap(*i1, *i2);


这个回答并不很满意。我们为什么不让 iter_swap 也这么高效呢?灵光一闪,我们回忆起软件工程的一个基础理论:我们不能加上一个间接层来代表高效的 swap 的责任呢?

    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        std::swap(*i1,*i2);
    }


那看起来不错,但是在运行我们的测试例子时显示调用 swap 时并不总是奏效。你没有注意到 iter_swap 接收两个不同的迭代器类型?这看起来,我们的测试例子试图交换 int * 以及 long * 所指定的两个值。而 swap 只操作两个相同类型的对象:

    template <class T> void swap(T& x, T& y);


上面的 iter_swap 的实现,当使用 int * 及 long* 时会导致编译时错误,因此没有一个 std::swap 的重载会匹配参数类型(int, long)。
我们可以试着解决这个问题,这得通过不动将低效版本的 iter_swap,而增加一个重载:

    // 通用低效版本函数
    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        typename
          iterator_traits<ForwardIterator1>::value_type
        tmp = *i1;
        *i1 = *i2;
        *i2 = tmp;
    }

    // 当两个迭代器相同时,这个函数更匹配
    template <class ForwardIterator>
    void iter_swap(ForwardIterator i1, ForwardIterator i2)
    {
        std::swap(*i1, *i2); // 有时会更快
    }


当进行匹配时,C++  的规则对于部分排序的函数模板会说,新的重载有更好匹配。这就解决了 int* 和 long * 所带来的问题。我们发布吧!




2.4.2 膏药上的一只苍蝇

没过多久,某个人注意到我们依然没有考虑一个重要的优化机会。设想一下当我们以 std::vector<std::string> 和 std::list<std::string> 的迭代器调用 iter_swap 时,两个迭代器会有相同的 value_type,并有自己的高效 swap 函数,但是因为他们两个的迭代器的类型是不相同的,快速 iter_swap 的版本重载就不会被调用。在这里所需要的就是 iter_swap 对两个不同,但有相同 value_type 的迭代器有作用。

因为我们是“标准库的实现者”,我们总是试图重写 swap ,因此他们可以工作在两个不同的类型上:
    template <class T1, class T2>
    void swap(T1& a, T2& b)
    {
        T1 tmp = a;
        a = b;
        b = tmp;
    }

这个简单的代码解决了大部份用户所遇到的问题。


2.4.3 又一只苍蝇!

很不幸的是,有一种类型的迭代器,上面的例子依然不能工作:那些 operator* 会产生代理引用类型的迭代器。这个代理引用实际上根本就不是一个引用,只是一个用 class 来模拟的:对于一个可读可能迭代器,一个代理引用就只是一个 class,这个 class 即可以成为 value_type 的转换目标,又可以从这个 class 赋值给 value_type。

最有名的这样的迭代器的例子就是 vector<bool>[6],就是每个元素都以单独的位进行存储的容器。因为并没有某个真正的引用可以指向一个单独的位,因此一个代理被使用,使用这个 vector 与其他类型的 vector 表现相当。代理的 operator=(bool) 将相应位的值设定成参数指定的值,而operator bool() 则返回相应位的值:

    struct proxy
    {
       proxy& operator=(bool x)
       {
           if (x)
               bytes[pos/8] |= (1u << (pos%8));
           else
               bytes[pos/8] &= ~(1u << (pos%8));
           return *this;
       }

       operator bool() const
       {
           return bytes[pos/8] & (1u << (pos%8));
       }

       unsigned char* bytes;
       size_t pos;
    };

    struct bit_iterator
    {
       typedef bool value_type;
       typedef proxy reference;
       more typedefs...

       proxy operator*() const;
       more operations...
    };

[6] 这样的问题可能已经很容易地就被我们的退化测试报忽略。甚至有此人都没有被说服说 vector<bool>::iterator 不是一个有效的迭代器。vector<bool> 及其迭代器如何添加到标准库中这个主题也是许外争论的主题。Herb Sutter 甚至针对此文件为 C++ 标准委员会写过两个论文 ([n1185], [n1211]),以及一个 Guru of the Week [GotW50], C++ 在标准委员会也已经开始了工作,旨在新的迭代器系统 concent [n1550],我们希望此举能够解决这个问题。



现在考虑一下,当 iter_swap 解引用一个位迭代器并且将这两个代理引用传递给 std::swap 时所发生的问题。回忆一下,因为 swap 以 non-const 引用的方式传递参数,使得 swap 会修改他们。问题是这个代理 operator * 返回的是一个临时对象,当我们把这些对象传递给 non-const 引用参数时,编译器会报错。在大多数情况下,这是个正确的决策,但是我们对临时变量的任何更改都会消失在以太中。可是最初的 iter_swap 实现,也能够对 vector<bool> 的迭代器工作良好。

2.4.4 苍蝇包括器

最后我们需要的的东西,就是针对具有相同 value_type,而它们的引用是真正的引用,而不是代理的迭代器检取高效的 iter_swap 实现。为了进行这个选择,我们需要一种方式来提问(回答)“类型 T 是一个引用吗?”,以及“这此 value_type 是相同的类型吗?”


Boost 包括一整个库,其中的元函数设计时就是用来操作及查询基本 traits, 像类型证同及 “引用性”。给定一个类型 traits ,我们就可以决定是否使用 swap 或者是自己进行交换:

    #include <boost/type_traits/is_reference.hpp>
    #include <boost/type_traits/is_same.hpp>
    #include <iterator>  // for iterator_traits
    #include <utility>   // for swap

    template <bool use_swap> struct iter_swap_impl; // see text

    namespace std {

    template <class ForwardIterator1, class ForwardIterator2>
    void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
    {
        typedef iterator_traits<ForwardIterator1> traits1;
        typedef typename traits1::value_type v1;
        typedef typename traits1::reference r1;

        typedef iterator_traits<ForwardIterator2> traits2;
        typedef typename traits2::value_type v2;
        typedef typename traits2::reference r2;

        bool const use_swap = boost::is_same<v1,v2>::value
                              && boost::is_reference<r1>::value
                              && boost::is_reference<r2>::value;




我们还没有关闭 iter_swap 的最后一括号,但是在这一点时,我们所要做的事情就是找到一种方式根据 use_swap 的值来检取不同行为。有许多的方式来达到这个目标,很多的方法我们将在第9章中讲述。我们已经很聪明地预测为派发而需要进行 iter_swap_impl 的前向声明。[7]。我们在 iter_swap_impl 的特化中,可以提供两种行为 (在 iter_swap 之外):

    template <>
    struct iter_swap_impl<true>  // 高效版本
    {
        template <class ForwardIterator1, class ForwardIterator2>
        static void do_it(ForwardIterator1 i1, ForwardIterator2 i2)
        {
            std::swap(*i1, *i2);
        }
    };

    template <>
    struct iter_swap_impl<false>  // 通用版本
    {
        template <class ForwardIterator1, class ForwardIterator2>
        static void do_it(ForwardIterator1 i1, ForwardIterator2 i2)
        {
            typename
              iterator_traits<ForwardIterator1>::value_type
            tmp = *i1;
            *i1 = *i2;
            *i2 = tmp;
        }
    };

[7] 一点点不自然的预见是作者特权。


现在 iter_swap_impl <use_swap>::do_it 对于每个可能的 use_swap 值都提供了一个恰当 的 iter_swap 实现。因为 do_it 是一个静态成员函数,iter_swap 可以在不用构造 iter_swap_impl 对象的情况下调用:

    iter_swap_impl<use_swap>::do_it(*i1,*i2);

现在我们可以将括号关闭,并且因为放松而深深呼吸一下,因为我们的退化测试都将通过了。我们发布吧!真是开心,我们的客户有了一个正确而高效的 iter_swap 实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值