zoukankan      html  css  js  c++  java
  • C++中引用转发、引用折叠及move、forward函数

    一、reference forwarding(引用转发)gcc实现及C++标准的规定

    gcc-4.8.2gcccppt.c
    /* Adjust types before performing type deduction, as described in
    [temp.deduct.call] and [temp.deduct.conv]. The rules in these two
    sections are symmetric. PARM is the type of a function parameter
    or the return type of the conversion function. ARG is the type of
    the argument passed to the call, or the type of the value
    initialized with the result of the conversion function.
    ARG_EXPR is the original argument expression, which may be null. */

    static int
    maybe_adjust_types_for_deduction (unification_kind_t strict,
    tree* parm,
    tree* arg,
    tree arg_expr)
    {
    ……
    /* From C++0x [14.8.2.1/3 temp.deduct.call] (after DR606), "If P is
    of the form T&&, where T is a template parameter, and the argument
    is an lvalue, T is deduced as A& */
    if (TREE_CODE (*parm) == REFERENCE_TYPE
    && TYPE_REF_IS_RVALUE (*parm)
    && TREE_CODE (TREE_TYPE (*parm)) == TEMPLATE_TYPE_PARM
    && cp_type_quals (TREE_TYPE (*parm)) == TYPE_UNQUALIFIED
    && (arg_expr ? real_lvalue_p (arg_expr)
    /* try_one_overload doesn't provide an arg_expr, but
    functions are always lvalues. */
    : TREE_CODE (*arg) == FUNCTION_TYPE))
    *arg = build_reference_type (*arg);
    ……
    }
    从这个注释的描述,找到下面网址,但是其中并没有注释中引号内的内容。但这其实并不是该功能被废除,只是被转换了一种描述方式
    A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
    这里将"form T&&"换成了更加学究的"forwarding reference":“If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.”。但是核心的意义依然没有变化,就是lvalue在作为forwarding reference类型参数的时候,它推导出的类型为“lvalue reference”类型。这是一个语法规定,编译器也是这么按照语法规定实现的,所以并没有什么魔法。

    二、reference collapsing(引用折叠)的gcc实现及C++标准

    gcc-4.8.2gcccppt.c
    tree
    tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
    {
    ……
    else if (TREE_CODE (type) == REFERENCE_TYPE)
    /* In C++0x, during template argument substitution, when there is an
    attempt to create a reference to a reference type, reference
    collapsing is applied as described in [14.3.1/4 temp.arg.type]:

    "If a template-argument for a template-parameter T names a type
    that is a reference to a type A, an attempt to create the type
    'lvalue reference to cv T' creates the type 'lvalue reference to
    A,' while an attempt to create the type type rvalue reference to
    cv T' creates the type T"
    */
    r = cp_build_reference_type
    (TREE_TYPE (type),
    TYPE_REF_IS_RVALUE (t) && TYPE_REF_IS_RVALUE (type));
    else
    r = cp_build_reference_type (type, TYPE_REF_IS_RVALUE (t));
    r = cp_build_qualified_type_real (r, cp_type_quals (t), complain);

    if (r != error_mark_node)
    /* Will this ever be needed for TYPE_..._TO values? */
    layout_type (r);

    return r;
    }
    ……
    }
    这里注释中的文字同样也不存在,它对应的内容在C++标准中其实也就是一句话
    If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.decltype]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.
    [Note 3: This rule is known as reference collapsing. — end note]
    [Example 3:
    int i;
    typedef int& LRI;
    typedef int&& RRI;

    LRI& r1 = i; // r1 has the type int&
    const LRI& r2 = i; // r2 has the type int&
    const LRI&& r3 = i; // r3 has the type int&

    RRI& r4 = i; // r4 has the type int&
    RRI&& r5 = 5; // r5 has the type int&&

    decltype(r2)& r6 = i; // r6 has the type int&
    decltype(r2)&& r7 = i; // r7 has the type int&
    — end example]
    这里核心的意思其实是对于一个引用类型,如果是添加额外的右值引用(&&)则以原来类型为准;如果是左值引用(&)则生成对原始类型的左值引用。或者通俗一点说,右值引用是加法中的(任何数加上0还是零);而左值引用是乘法中的零(任何数乘以零还是零)。

    三、一些例子

    1、函数参数推导的例子

    可以看到的是,对于字面常量(1,也就是右值),此时类型转换为类型本身(int);相反,当传入的是一个左值(argc)的时候,根据转发规则,这个时候推导出来的参数类型左值引用(int&)
    tsecer@harry: cat perf.forward.cpp
    template<typename Tsecer>
    void tsecer(Tsecer && t)
    {
    }

    int main(int argc, const char *argv[])
    {
    tsecer(1);
    tsecer(argc);
    return 0;
    }
    tsecer@harry: g++ -std=c++11 perf.forward.cpp
    tsecer@harry: nm a.out |c++filt -t | fgrep tsecer
    00000000004005e5 W void tsecer<int>(int&&)
    00000000004005ef W void tsecer<int&>(int&)
    tsecer@harry:

    2、std::forward和std::move函数实现的讨论

    其中提到了重要的一点,在使用std::forward的时候需要显示(明确的)提供模板类型,而这个类型通常就是通过前面的perfect reference功能来实现的。也就是说,提供给forward的类型参数通常是通过具有右值引用的模板函数提取(extract)出来的。
    The argument in the second case is a non-deduced context, preventing automatic deduction of the template argument. This forces you to write std::forward<T>(x), which is the right thing to do.
    gcc-4.8.2libstdc++-v3includeitsmove.h
    /**
    * @addtogroup utilities
    * @{
    */

    /**
    * @brief Forward an lvalue.
    * @return The parameter cast to the specified type.
    *
    * This function is used to implement "perfect forwarding".
    */
    template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

    /**
    * @brief Forward an rvalue.
    * @return The parameter cast to the specified type.
    *
    * This function is used to implement "perfect forwarding".
    */
    template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
    " substituting _Tp is an lvalue reference type");
    return static_cast<_Tp&&>(__t);
    }

    /**
    * @brief Convert a value to an rvalue.
    * @param __t A thing of arbitrary type.
    * @return The parameter cast to an rvalue-reference to allow moving it.
    */
    template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

    3、为什么需要类型转发

    C++标准中提供的例子为例,参数通常只是一些数据,而转发函数也不仅仅只是转发,而是将这些参数喂给特定的动作(例如new 操作符),这个动作封装在函数中的时候就可以实现的任意复杂。但是这里其实可以看到的是,它的确是通过&&来识别出特定类型供std::forward作为类型参数。
    template<class T, class... U>
    std::unique_ptr<T> make_unique2(U&&... u)
    {
    return std::unique_ptr<T>(new T(std::forward<U>(u)...));
    }

    4、为什么需要std::move

    和forward不同,move是可以进行类型推导的,或者说使用的时候并不需要提供类型信息,它的返回结果始终是一个右值类型。这样的典型场景就是可以把转换之后的变量作为右值提供给一个类作为构造函数,从而可以在新创建类中李代桃僵,也就是传入对象的内容被先创建对象窃取。
    该文档给出了一个典型的应用场景,就是在变量互换的时候执行的操作。如果默认的替换可能会进行拷贝/赋值操作,但是这个互换的本质是为了进行结构内部的资源转移/互换,所以如果能够识别出这种情况那将是极好的

    template <class T>
    swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);
    b = std::move(tmp);
    }
    当一个容器的作用域要退出的时候,可以将这个容器的内容放在另外一个变量中,此时避免了使用赋值的源变量来重新malloc出来内存要方便的多
    /usr/include/c++/4.8.2/bits/stl_vector.h
    1358 // Constant-time move assignment when source object's memory can be
    1359 // moved, either because the source's allocator will move too
    1360 // or because the allocators are equal.
    1361 void
    1362 _M_move_assign(vector&& __x, std::true_type) noexcept
    1363 {
    1364 vector __tmp(get_allocator());
    1365 this->_M_impl._M_swap_data(__tmp._M_impl);
    1366 this->_M_impl._M_swap_data(__x._M_impl);
    1367 if (_Alloc_traits::_S_propagate_on_move_assign())
    1368 std::__alloc_on_move(_M_get_Tp_allocator(),
    1369 __x._M_get_Tp_allocator());
    1370 }

    tsecer@harry: cat std.move.cpp
    #include <vector>
    #include <utility>

    int main(int argc, const char * argv[])
    {
    std::vector<int> vec1, vec2;
    vec1.push_back(1);
    vec2.push_back(2);
    vec1 = std::move(vec2);
    return 0;
    }

    (gdb) r
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /home/tsecer/./a.out

    Breakpoint 1, main (argc=1, argv=0x7fffffffe558) at std.move.cpp:6
    6 std::vector<int> vec1, vec2;
    (gdb) n
    7 vec1.push_back(1);
    (gdb)
    8 vec2.push_back(2);
    (gdb)
    9 vec1 = std::move(vec2);
    (gdb) p &vec2._M_impl->_M_start
    $3 = (std::_Vector_base<int, std::allocator<int> >::pointer *) 0x7fffffffe420
    (gdb) watch *(std::_Vector_base<int, std::allocator<int> >::pointer *) 0x7fffffffe420
    Hardware watchpoint 2: *(std::_Vector_base<int, std::allocator<int> >::pointer *) 0x7fffffffe420
    (gdb) c
    Continuing.
    Hardware watchpoint 2: *(std::_Vector_base<int, std::allocator<int> >::pointer *) 0x7fffffffe420

    Old value = (std::_Vector_base<int, std::allocator<int> >::pointer) 0x604030
    New value = (std::_Vector_base<int, std::allocator<int> >::pointer) 0x0
    std::swap<int*> (__a=@0x7fffffffe440: 0x604030, __b=@0x7fffffffe420: 0x0) at /usr/include/c++/4.8.2/bits/move.h:178
    178 }
    (gdb) bt
    #0 std::swap<int*> (__a=@0x7fffffffe440: 0x604030, __b=@0x7fffffffe420: 0x0) at /usr/include/c++/4.8.2/bits/move.h:178
    #1 0x000000000040106f in std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_M_swap_data (this=0x7fffffffe440, __x=...) at /usr/include/c++/4.8.2/bits/stl_vector.h:103
    #2 0x0000000000400cdb in std::vector<int, std::allocator<int> >::_M_move_assign(std::vector<int, std::allocator<int> >&&, std::integral_constant<bool, true>) (this=0x7fffffffe440,
    __x=<unknown type in /home/tsecer/a.out, CU 0x0, DIE 0x2510>) at /usr/include/c++/4.8.2/bits/stl_vector.h:1366
    #3 0x0000000000400b1a in std::vector<int, std::allocator<int> >::operator=(std::vector<int, std::allocator<int> >&&) (this=0x7fffffffe440,
    __x=<unknown type in /home/tsecer/a.out, CU 0x0, DIE 0x2510>) at /usr/include/c++/4.8.2/bits/stl_vector.h:444
    #4 0x00000000004009ca in main (argc=1, argv=0x7fffffffe558) at std.move.cpp:9
    (gdb) list
    173 __glibcxx_function_requires(_SGIAssignableConcept<_Tp>)
    174
    175 _Tp __tmp = _GLIBCXX_MOVE(__a);
    176 __a = _GLIBCXX_MOVE(__b);
    177 __b = _GLIBCXX_MOVE(__tmp);
    178 }
    179
    180 // _GLIBCXX_RESOLVE_LIB_DEFECTS
    181 // DR 809. std::swap should be overloaded for array types.
    182 /// Swap the contents of two arrays.
    (gdb)

  • 相关阅读:
    对象引用与对象克隆
    谁被回收了
    SpringMVC 返回json
    html基础
    org.hibernate.HibernateException: No Session found for current thread
    VB6之SendMessage模拟拖放事件
    VB6之GIF分解
    VB6之反编译工具VBRezQ
    VB6之WebBrowser控件
    (转载)VB6之鼠标移出事件
  • 原文地址:https://www.cnblogs.com/tsecer/p/14192976.html
Copyright © 2011-2022 走看看