侧边栏壁纸
博主头像
LittleAO的学习小站 博主等级

在知识的沙漠寻找绿洲

  • 累计撰写 125 篇文章
  • 累计创建 27 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

C++之 移动和右值引用

LittleAO
2024-08-30 / 0 评论 / 0 点赞 / 11 阅读 / 0 字
温馨提示:
本文最后更新于2024-08-30,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

右值引用

左值引用

引用分为左值引用和右值引用。我们平常讲的引用指的都是左值引用。

int num = 6;
int& refer = num;  // 这是左值引用
// int& refer = 6; // 错误,不能将右值赋值到左值引用上

通过左值引用不能传入右值的这个特性,我们可以实现一个只能传入左值的函数:

void func(int& num)
{
    ++num;
}

int main()
{
    func(3);  // 错误,右值不能作为参数传入函数
}

右值引用

右值引用和左值引用正相反,右值引用只允许右值赋值,用&&来声明右值引用:

int&& refer1 = 3;
int num = 3;
// int&& refer2 = num; // 错误,左值不能赋值到右值引用

通过这个特性,我们就能创建一个只能传入右值的函数:

int func(int&& num)
{
    return ++num;
}

int main()
{
    int num = 4;
    // func(num); // 错误,不能传入左值
    cout << func(4) << endl; // 5
}

你可以尝试判断一下,这个有没有报错?

int func(int&& num)
{
    return ++num;
}

int func(int& num)
{
    return num + 5;
}

int main()
{
    int num = 4;
    cout << func(func(num)) << endl;
}

上面的函数不仅不会报错,还会输出10。解释如下:先调用func(num),匹配到左值的函数,返回值为9。然而返回值属于右值,这就会匹配到传入右值的函数,返回一个10。

右值引用是左值

右值引用传入右值,但本身是个左值,也就是说,这样的代码是不会报错的:

int&& refer1 = 8;
int& refer2 = refer1;
cout << refer2 << endl; // 8

左值变右值

通过std::move可以实现左值转换到右值。再平常使用中,如果将一个左值转换到了右值,应当保证以后再也不使用这个左值了,虽然没有硬性规定。

int&& refer1 = 8;
int& refer2 = refer1;
int&& refer3 = std::move(refer2);   // 之后使用遗忘掉refer2的存在吧
cout << refer3 << endl; // 8

使用move的代码应该显式标注使用了std::move,避免潜在的命名冲突。

移动构造函数和移动赋值运算符

通过右值引用的特性,我们可以创建传入右值的构造函数。这称作为移动构造函数。下面是一个示例:

class Example
{
public:
    Example() = default;                                // 默认构造函数
    Example(int v) : data(v) {}                         // 构造函数
    Example(const Example& lval) : data(lval.data) {}   // 拷贝构造函数
    Example(Example&& rval) : data(rval.data)           // 移动构造函数
    {
        rval.data = 0;
    }

private:
    int data;
};

移动构造函数可以避免额外的拷贝开销。下面用一个示例来说明:

auto p1 = new Example(3);
auto p2 = new Example(*p1);
auto p3 = new Example(std::move(*p2));
// 移动对象需要两步:右值构造调用一次构造函数,移动调用一次移动构造函数
// 移动构造函数只是转移了所有权,拷贝需要额外开辟内存,复制。

还可以创建移动赋值运算符,和重载赋值运算符类似:

class Example
{
public:
    Example() = default;                                // 默认构造函数
    Example(int v) : data(v) {}                         // 构造函数
    Example(const Example& lval) : data(lval.data) {}   // 拷贝构造函数
    Example(Example&& rval) : data(rval.data)           // 移动构造函数
    {
        rval.data = 0;
    }
    Example& operator=(Example&& rval)
    {
        data = 0;   // 先清空自己占有的内存,相当于析构函数
        data = rval.data;   // 然后执行移动操作
        return *this;
    }

private:
    int data;
};

0

评论区