移动语义是 C++11 引入的一项重要特性,它允许对象的资源(如堆上分配的内存)在不进行深度复制的情况下进行转移。通过移动语义,可以将对象的资源从一个对象转移到另一个对象,从而避免不必要的内存拷贝,提高程序性能和效率。

基本用法:

在上面的示例中,move(vec1)将 vec1 转换为右值引用,这允许vec2 的构造函数通过移动语义接管 vec1 的资源,而不是复制它们。这样,vec1 的资源被转移给 vec2,而 vec1 变为空。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int>vecl={1,2,3,4,5};
    vector<int>vec2=move(vecl);//将vecl的资源移动到 vec2
    cout<<"vecl size:"<< vecl.size()<< endl;
    //输出:vecl size:0
    cout<<"vec2 size:"<< vec2.size()<< endl;
    //输出:vec2 size:5
    return 0;
}

移动语义的作用:

优化性能:

减少不必要的复制:使用 move 可以减少在对象赋值或函数返回时发生的不必要的资源复制,特别是对于大型对象或容器,这可以显著提高性能。

       对于对象赋值或者函数返回操作,编译器会根据参数的类型(左值还是右值)来选择调用拷贝构造函数还是移动构造函数。如果没有move,对于一个左值对象,通常会调用拷贝构造函数,这可能导致资源的复制。

      以vector为例,它包含一个指向动态分配数组的指针、容量信息和当前大小信息等。如果在没有移动语义的情况下将一个vector对象赋值给另一个对象,会复制整个数组,这在数组很大时是非常耗时的。

      当我们使用move将一个vector左值转换为右值引用后,在赋值或者返回操作时,编译器会调用移动构造函数。移动构造函数可以简单地将原对象的内部指针(指向动态分配的数组)、容量和大小等信息 “移动” 到新对象中,而不是复制数组中的所有元素。

优化临时对象的使用:在函数参数传递中使用 move 可以避免临时对象的复制,提高效率。

     临时对象是指在表达式求值过程中临时创建的对象,比如函数返回值(如果不是返回引用)或者通过一些运算产生的临时结果。这些临时对象通常在完整的表达式执行完后就会被销毁。

     当我们在函数参数传递中使用move,它可以将左值转换为右值引用,从而触发移动语义。对于一些拥有移动构造函数的对象,移动语义允许将一个对象的资源(如内部指针、计数器等)直接转移到另一个对象,而不是进行复制。

代码如下:

#include <iostream>
#include <utility>
using namespace std;
class A {
public:
    int* data;
    size_t size;

    A(size_t n) : size(n) {
        data = new int[n];
        for (size_t i = 0; i < n; ++i) {
            data[i] = i;
        }
    }

    // 移动构造函数
    A(A&& other) noexcept {
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
    }

    // 拷贝构造函数
    A(const A& other) : size(other.size) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) 
        {
            data[i] = other.data[i];
        }
    }
    //析构函数
    ~A() {
        delete[] data;
    }
};

// 函数接收对象按值传递(无move),会触发拷贝构造函数
void p1(A res) {
    cout << "拷贝构造" << endl;
}

// 函数接收右值引用,使用move传递参数可触发移动构造函数
int p2(A&& res) {
    cout << "移动拷贝构造" << endl;
    int sum = 0;
    for (size_t i = 0; i < res.size; ++i)
    {
        sum += res.data[i];
    }
    return sum;
}

int main()
{
    A a(5);

    // 不使用move,调用p1会触发拷贝构造函数进行复制
    p1(a);

    // 使用move,调用p2会触发移动构造函数进行资源转移
    int cnt=p2(move(a));
    cout << "计算结果: " << cnt << std::endl;
    //计算结果为10
    return 0;
}

实现高效的数据结构:在实现数据结构如动态数组、链表等时,使用 move 可以在元素插入、删除或移动时减少资源复制,提高数据结构的性能。

下面是动态数组的实现,代码如下:

#include <iostream>
#include <utility>
#include <vector>
using namespace std;

// 简单的动态数组类实现
class A {
public:
    vector<int> d;
    // 在指定位置插入元素,使用move减少复制
    void insert(int v, int n) {
        d.push_back(0);  // 先在末尾添加一个占位元素,以便后面移动元素腾出空间

        // 从最后一个元素开始,逐个将元素向后移动一位,直到指定位置
        for (int i = d.size() - 1; i > n; --i) {
            d[i] = move(d[i - 1]);
        }
        // 将新元素插入指定位置
        d[n] = v;
    }

    // 删除指定位置的元素,使用move避免不必要的复制
    void remove(int n) {
        // 将指定位置之后的元素逐个向前移动一位,覆盖要删除的元素
        for (int i = n; i < d.size() - 1; ++i) {
            d[i] = move(d[i + 1]);
        }
        // 移除末尾的元素
        d.pop_back();
    }
};

int main() {
    A arr;
    arr.d = { 1, 2, 3, 4, 5 };
    // 插入元素示例
    arr.insert(10, 2);
    // 输出数组元素,验证插入操作
    for (int num : arr.d) {
        cout << num << " ";
    }
    cout << endl;
    // 删除元素
    arr.remove(3);
    // 再次输出数组元素,验证删除操作
    for (int num : arr.d) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

注意事项:

使用 move 后,原对象通常处于未定义的状态,不应再使用该对象,在使用 move 时需要谨慎,确保不会导致资源泄露或无效引用。

右值引用

右值应用的定义:

在 C++ 中,右值引用是一种引用类型,用于绑定到右值。右值是指那些不具有持久存储位置或者是即将被销毁的值。例如,字面常量(如5、3.14等)、临时对象(函数返回的临时值等)都是右值。右值引用的语法形式是类型&&,其中&&表示右值引用。

     例如,int&& rref = 5;,这里rref就是一个右值引用,它绑定到了字面常量5这个右值。

实现方式:

移动构造函数

移动构造函数是实现移动语义的关键。它的参数是一个右值引用,用于接收一个即将被销毁的对象的资源。

在这个移动构造函数B(B&& other)中,other是一个右值引用。当一个临时的B对象(右值)被用来构造另一个B对象时,就会调用这个移动构造函数。在函数内部,将other对象的data指针赋值给新对象的data指针,然后将other对象的data指针设置为nullptr,这样就实现了资源(字符串内容)从一个对象到另一个对象的 “移动”,而不是复制。

class B{
public:
    char* data;
    B() : data(nullptr) {}
    B(const char* str) {
        if (str) {
            data = new char[strlen(str) + 1];
            strcpy(data, str);
        } else {
            data = nullptr;
        }
    }
    // 移动构造函数
    MyString(B&& other) noexcept {
        data = other.data;
        other.data = nullptr;
    }
    ~MyString() {
        delete[] data;
    }
};

移动赋值运算符

除了移动构造函数,移动赋值运算符operator=(类型&&)也用于实现移动语义。它用于将一个右值引用的对象赋值给另一个对象。

在下面这个移动赋值运算符中,首先检查是否是自我赋值。然后释放当前对象的资源(delete[] data),接着将other对象的资源(data指针)赋值给当前对象,最后将other对象的data指针设置为nullptr,完成资源的移动赋值。这样,当使用右值引用进行赋值操作时,就可以避免不必要的资源复制,实现移动语义。

B& operator=(B&& other) noexcept {
    if (this!= &other) {
        delete[] data;
        data = other.data;
        other.data = nullptr;
    }
    return *this;
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部