C++11中令人吐血的"移动语义"和"新的右值引用"

本文转载自:https://blog.csdn.net/xuwqiang1994/article/details/79924310

1.概述

没有C++11之前我都这样写代码的

Class* ptr = new Class(xxx);

感觉指针用的挺好的,逻辑清晰,很容易理解。
C++11出来之后,有了"移动语义"和"新的右值引用"的概念,可以预见以后看代码有多头疼。

2.左值与右值

左值(lvalue):代表一个在内存中占有确定位置的对象;
右值(rvalue):通过排他性来定义,不是lvalue的都属于rvalue,即在不在内存中占有确定位置的表达式;

2.1 例1:

int var;
var = 4;
4 = var;        //ERROR!
(var + 10) = 4; //ERROR!

如上, 显然"4、(var + 10)"这些表达式是不能作为左值的。

2.2 例2:

int foo() { return 2; }
int main()
{
    foo() = 2; //ERROR
    return 0;
}

这里的foo()也不能作为左值,而C++中的引用(reference)让这成为可能

2.3 例3:

int globalvar = 20;
int& foo()
{
    return globalvar;
}
int main()
{
    foo() = 10;
    return 0;
}

这里的"&"当表示引用含义的时候,修饰的需要是一个左值。

3.不用指针胜似指针的做法

3.1 疑问

拿C++11举例,C++11里引入了thread,下面的代码会不会让你感到疑惑?

  std::thread threads[5];
  for (int i=0; i<5; ++i)
    threads[i] = std::thread(pause_thread,i+1);

请问thread的构造函数到底被调用了几次呢?

3.2 样例1

我们不考虑thread,看下面这个Intvec代码样例:

class Intvec
{
public:
    explicit Intvec(size_t num = 0)
        : m_size(num), m_data(new int[m_size])
    {
        log("constructor");
    }

    ~Intvec()
    {
        log("destructor");
        if (m_data) {
            delete[] m_data;
            m_data = 0;
        }
    }

    Intvec(const Intvec& other)
        : m_size(other.m_size), m_data(new int[m_size])
    {
        log("copy constructor");
        for (size_t i = 0; i < m_size; ++i)
            m_data[i] = other.m_data[i];
    }

    Intvec& operator=(const Intvec& other)
    {
        log("copy assignment operator");
        Intvec tmp(other);
        std::swap(m_size, tmp.m_size);
        std::swap(m_data, tmp.m_data);
        return *this;
    }
	
private:
    void log(const char* msg)
    {
        cout << "[" << this << "] " << msg << "
";
    }

    size_t m_size;
    int* m_data;
};
int main()
{
    Intvec v2;
    cout << "assigning lvalue...
";
    v2 = Intvec(20);
    cout << "ended assigning lvalue...
";
    return 0;
}

运行结果

$ g++ main2.cpp -o main2 --std=c++11
$ ./main2
[0x7ffe204edc50] constructor
assigning lvalue...
[0x7ffe204edc60] constructor
[0x7ffe204edc50] copy assignment operator
[0x7ffe204edc20] copy constructor
[0x7ffe204edc20] destructor
[0x7ffe204edc60] destructor
ended assigning lvalue...
[0x7ffe204edc50] destructor

结果调用3次构造函数:
Intvec v2;
Intvec(20);
Intvec tmp(other);

3.3 样例2:

我们再加一个成员函数;(不用删除原来的operator=)

    Intvec& operator=(Intvec&& other)
    {
        log("move assignment operator");
        std::swap(m_size, other.m_size);
        std::swap(m_data, other.m_data);
        return *this;
    }

运行结果:

$ g++ main2.cpp -o main2 --std=c++11
$ ./main2
[0x7ffe5aa0ad70] constructor
assigning lvalue...
[0x7ffe5aa0ad80] constructor
[0x7ffe5aa0ad70] move assignment operator
[0x7ffe5aa0ad80] destructor
ended assigning lvalue...
[0x7ffe5aa0ad70] destructor

结果只调用了两次构造函数。
从外观上看
Intvec& operator=(Intvec&& other)和
Intvec& operator=(const Intvec& other)
在传参上并没有什么不同。但显然编译器知道自己该调用哪个函数。

4.总结

"&&"就是C++11支持的"新右值引用操作符",operator=(Intvec&& other)这个函数就是实现"移动语义"的一种方法。

PS:C++越改越像个脚本语言,图啥?
从个人角度看,以后写代码,我还是倾向于使用

Intvec* p2 = new Intvec(20);
delete p2;

的方式,这只调用一次构造函数,而且逻辑还很清晰,不用考虑类内部的实现。

Intvec v2 = Intvec(20); //也只调用一次,这就是另外一回事了。

本文转载自:https://blog.csdn.net/xuwqiang1994/article/details/79924310

原文地址:https://www.cnblogs.com/bugutian/p/13860234.html