Posts C++ 左值、右值与移动
Post
Cancel

C++ 左值、右值与移动

1. 左值与右A值

  1. 从汇编角度来讲

    左值是有实际内存存储地址的值, 而右值是寄存器里的值(简单理解)。

  2. 从面向对象来讲

    当一个对象被用作右值的时候,用的是对象的值(内容)。 当一个对象被用作左值的时候,用的是对象身份(在内存中的位置)。

  3. 使用关键字decltype时, 左值与右值是不同的。 这个留坑。

2. 右值引用

右值引用有一个很重要的性质————只能绑定到一个即将销毁的对象上。因此,我们可以用一个const 的左值引用或者一个右值引用绑定到这类表达式上

左值持久, 右值短暂。

1
2
3
4
int i = 42;
int &r = i;
const int &r3 = i * 42;
int &&r4 = i * 42;

3. swap操作

  1. 若果一个类没有自定义的swap操作, 那么会调用std::swap。

  2. copy and swap 的本质还是: 为你打算修改的对象做出一个副本,然后在那副本上做一切必要的修改。若有任何修改动作抛出了异常,原对象仍然保持为改变状态。带所有改变都成功后,做出一个不抛出异常的swap动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class StrDemo {
public:
    friend void swap(StrDemo&, StrDemo&);
    StrDemo(const StrDemo &s) : str(s.str) {
    }
    StrDemo& operator=(StrDemo s);
private:
    string str;
};

StrDemo& StrDemo::operator=(StrDemo s) {
    swap(*this, s);
}

inline
void swap(StrDemo &d1, StrDemo &d2) {
    using std::swap;
    swap(d1.str, d2.str);
}

4. std::move函数

虽然不能将一个右值引用直接绑定到一个左值上,但是可以显示的将一个左值转换为对应的右值引用。

` int &&rr2 = std::move(rr1);`;

除了对rr1赋值或者销毁以外, 不能在对rr1有任何假设

5. 移动构造与移动赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class D {
public:
    ~D() {
    }
    D(D &&d):s(d.s) {  // 移动构造
        d.s = nullptr;
    }
    D& operator=(D &&d) {   // 移动赋值
        if (this != &d) {
            s = d.s;
            d.s = nullptr;
        }
        return *this;
    }
private:
    string s;
};
  1. 从一个对象移动数据并不会销毁次对象, 但是有时候移动操作完成后,源对象会被销毁。

  2. 必须要保证源对象移动后可进入一个析构的状态。

  3. 移动操作必须保证源对象仍然是有效的, 其可以指向其他的对象。

6. 最后一些细节

合成移动操作

编译器不会为某些类合成移动操作,如果一个类没有移动操作,通过正常的函数匹配,类会用拷贝操作来代替移动操作。

当一个类没有定义任何版本的拷贝控制成员,且类的每个非static 数据成员都可以移动时, 编译器会合成移动构造或者移动赋值运算符。

如果没有移动构造函数, 右值也被拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
public:
    Person(const string &name) : name(name) {}
    Person(const Person &p):name(p.name) {
        cout << "拷贝构造函数" << endl;
    }
private:
    string name;
};

    Person p("jay");
    Person p2(p);   // 拷贝构造
    Person p3(std::move(p)); // 拷贝构造

copy and swap 赋值运算符和移动操作

  1. 赋值运算符既是移动赋值运算符, 又是拷贝赋值运算符。

  2. 单一的赋值运算符实现了两种功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Person {
public:
    friend void swap(Person&, Person&);
    Person(const string &name) : name(name) {}
    Person(const Person &p):name(p.name) {
        cout << "拷贝构造函数" << endl;
    }
    Person(Person &&p) noexcept:name(p.name) {
        cout << "移动构造函数" << endl;
        p.name = nullptr;
    }
    Person& operator=(Person p) {   // 拷贝构造或者移动构造
        swap(*this, p);
        return *this;
    }
private:
    string name;
};

void swap(Person &p1, Person &p2) {
    using std::swap;
    swap(p1.name, p2.name);
    return;
}

int main() {
    Person p("jay");
    Person p2("jj");
    p2 = p;     // 拷贝构造函数
    p2 = std::move(p);   // 移动构造函数
}

右值引用和成员函数

vector 的两个push版本

1
2
void push_back(const X&);  // 绑定任意类型
void push_back(X&&);    // 绑定右值

当我们调用push时, 编译器根据类型选择调用那个版本。

1
2
3
vec.push_back("hey");  // 调用右值
string s = "jay";
vec.push_back(s);    // 调用左值

重载和引用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
public:
    Foo sorted() &&;   // 用户右值
    Foo sorted() const &;  // 用于任何类型的Foo
private:
    vector<int> data;
};

// 对象为右值 可以原地排序
Foo Foo::sorted() &&{
    sort(data.begin(), data.end());
    return *this;
}
// 对象为左值 拷贝后排序
Foo Foo::sorted() const &{
    Foo f(*this);
    sort(f.data.begin(), f.data.end());
    return f;
}
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags