模板给引用挖了个坑

事情发生于我实习期间,当时的代码将bind的函数类传给线程,然后线程就会执行该函数类。现在我希望得到其中一个参数执行后的值,然而目前参数是传值的。我没细想就把这个参数改为传引用。结果我得到的值缺不是与其的值。

下面是该场景的一个简单的例子:

#include <tr1/functional>
#include <iostream>

void increase(int &inc) {
    inc++;
}

int main()
{
    int inc = 0;
    std::tr1::function<void()> func = std::tr1::bind(increase, inc);
    func();
    std::cout << inc << std::endl;
    return 0;
}
上面的程序输出结果为:0

由于实际的程序是多个线程,有些复杂,所以一开始没往模板参数推导的问题上想,结果白白浪费了我半天时间。

下面步入正题:

首先,传值和传引用虽然又本质的区别,但是两者的外在形式确是即为相似的,这就导致一个模板参数既可以推导为值,也可以推导为引用,两种情况都是可以的。然后编译结果却只能是唯一的,因此编译器只能规定再模棱两可的情况下一律推导为值。只在手动地在模板参数后加&的情况下才会将模板参数推导为引用。如下所示:

template <typename T>
A<T&> getA(T& x)
{
    return A<T&>(x);
}

这里的getA是一个模板函数,A是一个模板类(A的定义省略了)。注意getA的参数以及返回的A的模板参数,都写明了是引用。因此A的模板参数必须是引用。而如果是这样:的:

template <typename T>
A<T> getA(T& x)
{
    return A<T>(x);
}

即便getA的实参是一个引用值,最终编译器仍然认为T是值而非引用。

如果我们确实需要得到对模板参数的修改,而又不想使用指针这样危险的方法呢,stl早就考虑到了这个问题,请参考下一篇《填坑吧,模板君!》

其实到这里就已经差不多解释了问题的原因了,不过追根究底的码农精神还是指引着我研究了一把bind的代码。(旁白:明明就是研究了半天源码才突然意识到了这个低级错误。。。)

==========================================================================================================

首先找到bind的函数定义,这个函数十分简单,首先定义了三个类型(可以无视)。最后直接使用传入的函数和参数构造了_Bind对象返回。

  template<typename _Functor, typename... _ArgTypes>
    inline
    _Bind<typename _Maybe_wrap_member_pointer<_Functor>::type(_ArgTypes...)>
    bind(_Functor __f, _ArgTypes... __args)
    {
      typedef _Maybe_wrap_member_pointer<_Functor> __maybe_type;
      typedef typename __maybe_type::type __functor_type;
      typedef _Bind<__functor_type(_ArgTypes...)> __result_type;
      return __result_type(__maybe_type::__do_wrap(__f), __args...);
    }

_Bind有主要有两个成员,_M_f和_M_bound_args。前者是保存函数指针的一个类,它的实现细节略去不谈;后者是保存函数参数的对象,它的实现后面再说。构造函数首先将函数指针和参数分开保存:

     public:
      explicit _Bind(_Functor __f, _Bound_args... __bound_args)
        : _M_f(__f), _M_bound_args(__bound_args...) { }

_M_bound_args其实是一个tuple对象(感谢C++11):

      tuple<_Bound_args...> _M_bound_args;

而这个_Bind对象(更容易理解的说法则是Function对象)是如何完成对_M_f的函数调用的呢?

      template<typename... _Args, int... _Indexes>
        typename result_of<
                   _Functor(typename result_of<_Mu<_Bound_args>
                            (_Bound_args, tuple<_Args...>)>::type...)
                 >::type
        __call(const tuple<_Args...>& __args, _Index_tuple<_Indexes...>)
        {
          return _M_f(_Mu<_Bound_args>()
                      (tr1::get<_Indexes>(_M_bound_args), __args)...);
        }

      template<typename... _Args>
        typename result_of<
                   _Functo(rtypename result_of<_Mu<_Bound_args>
                            (_Bound_args, tuple<_Args...>)>::type...)
                 >::type
        operator()(_Args&... __args)
        {
          return this->__call(tr1::tie(__args...), _Bound_indexes());
        }

这两个函数有一系列的重载,上面只是列出其中一种。到这里也差不多了,再扯下去就没完没了。我一开始提到的两种情况(就是那两种getA的实现)在这里都出现了。

首先,来看一下tuple的构造函数,这里有关变长模板参数的知识请各位看官自行科普。

  template<typename... _Elements>
    class tuple : public _Tuple_impl<0, _Elements...>
    {
      typedef _Tuple_impl<0, _Elements...> _Inherited;

    public:
      tuple() : _Inherited() { }

      explicit
      tuple(typename __add_c_ref<_Elements>::type... __elements)
      : _Inherited(__elements...) { }

      template<typename... _UElements>
        tuple(const tuple<_UElements...>& __in)
    : _Inherited(__in) { }
      // .....

  template<int _Idx, typename _Head, typename... _Tail>
    struct _Tuple_impl<_Idx, _Head, _Tail...>
    : public _Tuple_impl<_Idx + 1, _Tail...>
    {
      typedef _Tuple_impl<_Idx + 1, _Tail...> _Inherited;

      _Head _M_head;

      _Inherited&       _M_tail()       { return *this; }
      const _Inherited& _M_tail() const { return *this; }

      _Tuple_impl() : _Inherited(), _M_head() { }

      explicit
      _Tuple_impl(typename __add_c_ref<_Head>::type __head,
          typename __add_c_ref<_Tail>::type... __tail)
      : _Inherited(__tail...), _M_head(__head) { }
_Tuple_impl的_M_head就是保存每个参数的变量,注意它的类型是_Head,也即是对应的tuple构造函数参数的类型。在往上追溯,就是传给bind的类型,正如一开始所说,这中间没有哪一处要求类型一定要是引用,因此最终将bind的参数类型推导为值。

然后,注意_Bind的()运算符函数中出现的tie函数,其定义如下:

  template<typename... _Elements>
    inline tuple<_Elements&...>
    tie(_Elements&... __args)
    {
      return tuple<_Elements&...>(__args...);
    }

tuple的模板参数显式地写明为_Elements&...,这将最终导致它的基类_Tuple_impl中的_M_head类型为引用。即用tie得到的tuple中的变量都是引用的,对它们的修改最终改变了tie传入的参数。

所以,tie得到的tuple其实是具有“左值引用”的语义的(严格来讲可能不能这么说,但是效果确实就是这样)。于是下面的代码输出结果会是:

int main()
{
    int a = 0;
    string str = "str1";
    tr1::tie(a, str) = tr1::make_tuple(1, "str2");
    cout << a << endl
         << str << endl;
    return 0;
}

输出结果:

1
str2

表示我C++居然都开始抢Python的饭碗了,哈哈!!!







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值