事情发生于我实习期间,当时的代码将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