1

Rvalue references

Rvalue references are a new reference type introduced in C++0x that help solver the problem of unnecessary copying and enable perfect forwarding. When the right-hand side of an assignment is an rvalue, then the left-hand side object can 'steal' resource from the right-hand side object rather than performing a separate allocation, thus enabing move semantics.

(右值引用是C ++ 0x中引入的新引用类型,可帮助解决不必要的复制问题并实现完美转发。 当赋值的右侧是右值时,则左侧对象可以从右侧对象窃取资源,而不必执行单独的分配,从而可以实现移动语义。)

  • Lvalue : 可以出现在 operator= 左侧、右侧
  • Rvalue : 只能出现在 operator= 右侧

以 int 测试:

int a = 9;
int b = 4;

a = b;       // OK
b = a;       // OK
a = a + b;   // Ok

a+b = 42;    // error: lvalue required as left operand of assignment

以 string 测试:

string s1("Hello ");   
string s2("Word!");

s1 + s2 = s2;      // 竟然编译通过!! 
string() = "Word"; // 竟然可以对 temp object 赋值!!

以 complex 测试:

complex<int> c1(3, 8);
complex<int> c2(1, 9);

c1 + c2 = c2;     // 竟然编译通过!
complex<int>() = complex<int>(4,9);  // 竟然可以对 temp object 赋值!!

已知 (a+b) 、临时对象为右值,只能出现在赋值符号右边,但 string,complex 的表现与我们认知不同。

C++ with its user-defined types has introduced some subtleties regarding modifiability and assignability that cause this definition to be incorrect.

(C ++及其用户定义类型引入了一些与可修改性和可分配性有关的细微之处,这些细微之处导致该定义不正确。)

总结:

  • 临时对象是一种右值
  • 右值不可以放在赋值符号左边
  • 右值引用只能用右值初始化

右值引用只能出现在赋值符号右边

int foo() 
{
    return 5;
}

int x = foo();    // OK
int *p = &foo();  // error: lvalue required as unary ‘&’ operand
foo() = 7;        // error: lvalue required as left operand of assignment

对 Rvalue 取 reference 不可以。没有所谓的 Rvalue reference(c++0x 之前)。

当 Rvalue 出现在 operator=(copy assignment) 的右侧,我们认为对其资源进行“偷取/搬移(move)”而非拷贝(copy)是可以的,合理的。
那么:

  1. 必须有语法让我们在调用端告诉编译器,这是一个 Rvalue。
  2. 必须有语法让我们在被调用端写出一个专门处理 Rvalue 的所谓 move assignment 函数。

Rvalue references and Move Semantics

伪代码:

using namespace std;

class MyString
{
private:
    char  *_data;

public:
    // copy ctor
    MyString(const MyString &str) : initializer_list
    {
        // ... 
    }   

    // move cpor
    MyString(const MyString &&str) noexcept : initializer_list  // 注意这里 !!
    {
        // ...
    }
    
    // copy assignment
    MyString& operator=(const MyString &str)
    {
        return *this;
    }
    
    // move assignment
    MyString& operator=(const MyString &&str) noexcept          // 注意这里 !!
    {
        return *this;
    }
};

template <typename M>
void test_moveable(M c, long  value)
{
    typedef typename iterator_traits<typename M::iterator>::value_type Vtype;

    char buf[10] = {0};

    clock_t timeStart = clock();

    for (long i=0; i<value; ++i)
    {
        snprintf(buf, 10, "%d", rand());    // 随即数转为字符串
        auto ite = c.end();                 // 定位到尾部
        c.insert(ite, Vtype(buf));          // 插入        注意这里!!
        output_static_data(*(c.begin()));   // copy/move ctor 被调用次数
    }

    cout << "millo - second : " << (clock() - timeStart) << endl;
    cout << "size()  = " << c.size() << endl;

    M c1(c);                // copy ctor
    M c2(std::move(c1));    // move ctor    注意这里!! (必须确保以后不再使用c1,因为c1 源数据被偷走)

    c1.swap(c2);
}

说明:

  • vector中的两个insert版本
iterator insert(const_iterator __position, const value_type& __x);

iterator insert(const_iterator __position, value_type&& __x)
    { return _M_insert_rval(__position, std::move(__x)); }
  • M c2(std::move(c1));
    std::move函数可以强制将左值引用转换为右值引用。 但需要使用者保证必须确保以后不再使用c1,因为c1 源数据被偷走。
  • copy and move 图解

具体发生在指针上

image.png

-
image.png

  • noexcept
You need to inform C++ (specifically std::vector) that your move constructotr and destructor does not throw, Then the move constructor will be called when the vector grows.

(你需要通知C ++(特别是std :: vector),你的move构造函数和析构函数不会抛出异常,然后,当vector增大时,将调用移动构造函数。)

Perfect Forwarding

Unperfect Forwarding

文件:Test.cpp

#include <iostream>

using namespace std;

void process(int &i)
{
    cout << "process(int&): " << i << endl;
}

void process(int &&i)
{
    cout << "void process(int &&i): " << i << endl;
}

void forward(int &&i)
{
    cout << "forward(int &&i): " << i << endl;
    process(i);
}

int main()
{
    int a = 0;

    process(a);
    process(1);
    process(move(a));

    cout << "**************" << endl;

    forward(2);             // 注意这里输出!! 右值引用在数据传递时丢失了信息
    forward(move(a));       // 注意这里输出!! 右值引用在数据传递时丢失了信息
    // forward(a);          // error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’

    return 0;
}

输出:

process(int&): 0
void process(int &&i): 1
void process(int &&i): 0
**************
forward(int &&i): 2
process(int&): 2
forward(int &&i): 0
process(int&): 0

说明:初始化之后的右值引用将变成一个左值,如果是non-const还可以被赋值

int x = 20;        // 左值
int&& rx = x * 2;  // x*2的结果是一个右值,rx延长其生命周期
int y = rx + 2;    // 因此你可以重用它:42
rx = 100;          // 一旦你初始化一个右值引用变量,该变量就成为了一个左值,可以被赋值

Perfect Forwarding

Perfect forwarding allows you to write a single function template that takes n arbitrary arguments and forwards them transparently to another arbitrary function. The nature of the argument(modifiable, const, lvalue or rvalue) is preserved in this forwarding process.
(完美转发使您可以编写一个带有n个任意参数的函数模板,并将其透明地转发给另一个任意函数。 在此转发过程中保留了参数的性质(可修改,const,左值或右值)。)
template <typename T1, typename T2>
void functionA(T1 &&t1, T2 &&t2)
{
    functionB(std::forward<T1>(t1), std::forward<T2>(t2));
}

文件:Test.cpp

#include <iostream>

using std::cout;
using std::endl;
using std::move;

void process(int &i)
{
    cout << "process(int&): " << i << endl;
}

void process(int &&i)
{
    cout << "void process(int &&i): " << i << endl;
}

template <typename T>
void forward(T &&i)
{
    cout << "forward(T &&i): " << i << endl;
    process(std::forward<T>(i));
}

int main()
{
    int a = 0;

    process(a);
    process(1);
    process(move(a));

    cout << "**************" << endl;

   forward(2);             // 注意这里输出!!
   forward(move(a));       // 注意这里输出!! 

    return 0;
}

输出:

process(int&): 0
void process(int &&i): 1
void process(int &&i): 0
**************
forward(T &&i): 2
void process(int &&i): 2
forward(T &&i): 0
void process(int &&i): 0

文件:move.h

/**
*  @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); }

// ----------------

/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type; };

move aware class

文件:Test.cpp

#include <cstring>
#include <ctime>

#include <iostream>
#include <string>
#include <typeinfo>
#include <unordered_set>
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <unordered_set>

using namespace std;

class MyString
{
public:
    static size_t DCtor;    // 累计 default-ctor 调用次数
    static size_t Ctor;     // 累计 ctor 调用次数
    static size_t CCtor;    // 累计 copy-ctor 调用次数
    static size_t CAsgn;    // 累计 copy-asgn 调用次数
    static size_t MCtor;    // 累计 move-ctor 调用次数
    static size_t MAsgn;    // 累计 move-asgn 调用次数
    static size_t Dtor;     // 累计 dtro 调用次数

private:
    char *_data;
    size_t _len;

    void _init_data(const char *s)
    {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\0';
    }

public:
    // default constructor
    MyString() : _data(nullptr), _len(0)
    {
        ++DCtor;
    }

    // constructor
    MyString(const char *p) : _len(strlen(p))
    {
        ++Ctor;
        _init_data(p);
    }

    // copy constructor
    MyString(const MyString &str) : _len(str._len)
    {
        ++CCtor;
        _init_data(str._data);
    }

    // move constructor, woth "noexcept"
    MyString(MyString &&str) noexcept : _data(str._data), _len(str._len)
    {
        ++MCtor;
        str._len = 0;
        str._data = nullptr;
    }

    // copy assignment
    MyString& operator=(const MyString &str)
    {
        ++CAsgn;

        if (this != &str)           // 自赋值检查
        {
            if (_data) delete _data;
            _len = str._len;
            _init_data(str._data);  // copy
        }

        return *this;
    }

    // move assignment
    MyString& operator=(MyString&& str) noexcept
    {
        ++MAsgn;

        if (this != &str)           // 自赋值检查
        {
            if (_data) delete _data;

            _len = str._len;
            _data = str._data;      // move
            str._len = 0;
            str._data = nullptr;    // 重要
        }

        return *this;
    }

    // dtor
    virtual ~MyString();

    char *get() const
    {
        return _data;
    }

    // 为了 set 测试使用
    bool operator < (const MyString &rhs) const
    {
       return string(this->_data) < string(rhs._data);  // 借助标准库已有实现
    }

    // 为了 set 测试使用
    bool operator == (const MyString &rhs) const
    {
        return string(this->_data) == string(rhs._data);  // 借助标准库已有实现
    }
};

size_t MyString::DCtor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;

MyString::~MyString()
{
    if (_data)
        delete _data;
}

namespace std
{

template<>
struct hash<MyString>   // 为了 unordered containers
{
    size_t operator () (const MyString &s) const noexcept
    {
        return hash<string>()(string(s.get())); // 借助标准库已有 hash<string>
    }
};

}

// ----------------------------------

class MyStrNoMove
{
public:
    static size_t DCtor;    // 累计 default-ctor 调用次数
    static size_t Ctor;     // 累计 ctor 调用次数
    static size_t CCtor;    // 累计 copy-ctor 调用次数
    static size_t CAsgn;    // 累计 copy-asgn 调用次数
    static size_t MCtor;    // 累计 move-ctor 调用次数
    static size_t MAsgn;    // 累计 move-asgn 调用次数
    static size_t Dtor;     // 累计 dtro 调用次数

private:
    char *_data;
    size_t _len;

    void _init_data(const char *s)
    {
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\0';
    }

public:
    // default constructor
    MyStrNoMove() : _data(nullptr), _len(0)
    {
        ++DCtor;
    }

    // constructor
    MyStrNoMove(const char *p) : _len(strlen(p))
    {
        ++Ctor;
        _init_data(p);
    }

    // copy constructor
    MyStrNoMove(const MyStrNoMove &str) : _len(str._len)
    {
        ++CCtor;
        _init_data(str._data);
    }

    // copy assignment
    MyStrNoMove& operator=(const MyStrNoMove &str)
    {
        ++CAsgn;

        if (this != &str)           // 自赋值检查
        {
            if (_data) delete _data;
            _len = str._len;
            _init_data(str._data);  // copy
        }

        return *this;
    }

    // dtor
    virtual ~MyStrNoMove();

    char *get() const
    {
        return _data;
    }

    // 为了 set 测试使用
    bool operator < (const MyStrNoMove &rhs) const
    {
       return string(this->_data) < string(rhs._data);  // 借助标准库已有实现
    }

    // 为了 set 测试使用
    bool operator == (const MyStrNoMove &rhs) const
    {
        return string(this->_data) == string(rhs._data);  // 借助标准库已有实现
    }
};

size_t MyStrNoMove::DCtor = 0;
size_t MyStrNoMove::Ctor = 0;
size_t MyStrNoMove::CCtor = 0;
size_t MyStrNoMove::CAsgn = 0;
size_t MyStrNoMove::MCtor = 0;
size_t MyStrNoMove::MAsgn = 0;
size_t MyStrNoMove::Dtor = 0;

MyStrNoMove::~MyStrNoMove()
{
    if (_data)
        delete _data;
}

namespace std
{

template<>
struct hash<MyStrNoMove>   // 为了 unordered containers
{
    size_t operator () (const MyStrNoMove &s) const noexcept
    {
        return hash<string>()(string(s.get())); // 借助标准库已有 hash<string>
    }
};

}

//-------------------------------------------
//-------------------------------------------

template <typename T>
void output_static_data(const T &myStr)
{
    cout << typeid(myStr).name() << "--" << endl;
    cout << " CCtor = " << T::CCtor
         << " MCtor = " << T::MCtor
         << " CAsgn = " << T::CAsgn
         << " Dtor = " << T::Dtor
         << " Ctor = " << T::Ctor
         << " DCtor = " << T::DCtor
         << endl;
}

//-------------------------------------------
//-------------------------------------------
template <typename M, typename NM>
void test_moveable(M c1, NM c2, long value)
{
    char buf[10] = {0};

    // 1. 测试 moveable
    typedef typename iterator_traits<typename M::iterator>::value_type V1type;

    cout << "test, with moveable elements" << endl;
    clock_t timeStart = clock();
    for (long i=0; i<value; ++i)
    {
        snprintf(buf, 10, "%d", rand());
        auto ite = c1.end();
        c1.insert(ite, V1type(buf));    // 安插于尾端
    }

    cout << "construction, milli-seconds : " << (clock() - timeStart) << endl;
    cout << "size() : " << c1.size() << endl;
    output_static_data(*(c1.begin()));

    timeStart = clock();
    M c11(c1);
    cout << "copy, milli-seconds : " << (clock() - timeStart) << endl;

    timeStart = clock();
    M c12(move(c1));
    cout << "move, milli-seconds : " << (clock() - timeStart) << endl;

    timeStart = clock();
    c11.swap(c12);
    cout << "swap, milli-seconds : " << (clock() - timeStart) << endl;

    cout << endl << endl;

    // 2. 测试 non-moveable
    typedef typename iterator_traits<typename NM::iterator>::value_type V2type;

    cout << "test, with no-moveable elements" << endl;
    timeStart = clock();
    for (long i=0; i<value; ++i)
    {
        snprintf(buf, 10, "%d", rand());
        auto ite = c2.end();
        c2.insert(ite, V2type(buf));    // 安插于尾端
    }

    cout << "construction, milli-seconds : " << (clock() - timeStart) << endl;
    cout << "size() : " << c2.size() << endl;
    output_static_data(*(c2.begin()));

    timeStart = clock();
    NM c21(c2);
    cout << "copy, milli-seconds : " << (clock() - timeStart) << endl;

    timeStart = clock();
    NM c22(move(c2));
    cout << "move, milli-seconds : " << (clock() - timeStart) << endl;

    timeStart = clock();
    c21.swap(c22);
    cout << "swap, milli-seconds : " << (clock() - timeStart) << endl;
}

对于 vector 速度效能的影响

int main()
{
    test_moveable(vector<MyString>(), vector<MyStrNoMove>(), 3000000L);

    return 0;
}

输出:

test, with moveable elements
construction, milli-seconds : 818
size() : 3000000
8MyString--
 CCtor = 0 MCtor = 7194303 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 235
move, milli-seconds : 0
swap, milli-seconds : 0


test, with no-moveable elements
construction, milli-seconds : 1473
size() : 3000000
11MyStrNoMove--
 CCtor = 7194303 MCtor = 0 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 231
move, milli-seconds : 0
swap, milli-seconds : 0
  • 思考:3000000 个数据元素,为什么会调用 7194303 次构造函数呢?

vector 会进行2倍成长,原数据会被拷贝到新空间。

1_meitu_1.jpg

对于 list 速度效能的影响

int main()
{
    test_moveable(list<MyString>(), list<MyStrNoMove>(), 3000000L);

    return 0;
}

输出:

test, with moveable elements
construction, milli-seconds : 945
size() : 3000000
8MyString--
 CCtor = 0 MCtor = 3000000 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 568
move, milli-seconds : 0
swap, milli-seconds : 0


test, with no-moveable elements
construction, milli-seconds : 1249
size() : 3000000
11MyStrNoMove--
 CCtor = 3000000 MCtor = 0 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 568
move, milli-seconds : 0
swap, milli-seconds : 0

2_meitu_2.jpg

对于 deque 速度效能的影响

int main()
{
    test_moveable(deque<MyString>(), deque<MyStrNoMove>(), 3000000L);

    return 0;
}

输出:

test, with moveable elements
construction, milli-seconds : 734
size() : 3000000
8MyString--
 CCtor = 0 MCtor = 3000000 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 269
move, milli-seconds : 0
swap, milli-seconds : 0


test, with no-moveable elements
construction, milli-seconds : 1039
size() : 3000000
11MyStrNoMove--
 CCtor = 3000000 MCtor = 0 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 250
move, milli-seconds : 0
swap, milli-seconds : 0

4.jpg

对于 set 速度效能的影响

int main()
{
    test_moveable(set<MyString>(), set<MyStrNoMove>(), 3000000L);

    return 0;
}

输出:

test, with moveable elements
construction, milli-seconds : 5002
size() : 32768
8MyString--
 CCtor = 0 MCtor = 32768 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 6
move, milli-seconds : 0
swap, milli-seconds : 0


test, with no-moveable elements
construction, milli-seconds : 5111
size() : 32768
11MyStrNoMove--
 CCtor = 32768 MCtor = 0 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 6
move, milli-seconds : 1
swap, milli-seconds : 0

5.png

int main()
{
    test_moveable(unordered_set<MyString>(), unordered_set<MyStrNoMove>(), 3000000L);

    return 0;
}

输出:

test, with moveable elements
construction, milli-seconds : 1497
size() : 32768
8MyString--
 CCtor = 0 MCtor = 32768 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 8
move, milli-seconds : 0
swap, milli-seconds : 0


test, with no-moveable elements
construction, milli-seconds : 1557
size() : 32768
11MyStrNoMove--
 CCtor = 32768 MCtor = 0 CAsgn = 0 Dtor = 0 Ctor = 3000000 DCtor = 0
copy, milli-seconds : 9
move, milli-seconds : 0
swap, milli-seconds : 0

6_meitu_6.jpg

vector 的 copy ctor

7_meitu_7.jpg

vector 的 move ctor

8_meitu_8.jpg


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧