Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • Cpp小记
  • C++学习笔记-220705-V1
  • 进阶

    • CC++模板
    • CC++STL-常用容器
    • CC++STL-函数对象
    • CC++STL-常用算法
    • 现代C++:常量、变量与类型推导
    • 现代C++:函数式编程(lambda、函数对象包装器)
    • 现代C++:右值引用、移动语义与完美转发
      • 右值引用
        • 右值与左值
        • 常值修饰符,使用int const &
      • 移动语义
      • 完美转发
    • 现代C++:智能指针
    • C++11多线程编程
  • 奇思妙用

  • 库的使用

  • 《C++》学习笔记
  • 进阶
Sirius0v0
2023-08-15
目录

现代C++:右值引用、移动语义与完美转发

# 现代C++:右值引用、移动语义与完美转发

# 右值引用

# 右值与左值

左值,常常位于赋值符号左边,通常对应着一个长时间存在于内存中的变量;右值,常常位于赋值符号右边,通常是一个表达式,代表计算过程中临时生成的中间变量,即将消失的,不长时间存在于内存中的值。C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值

  • 纯右值,纯粹的右值,要么是纯粹的字面量,要么是求值结果相当于字面量或匿名临时对象。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda表达式都属于纯右值

  • 将亡值,是 C++11 为了引入右值引用而提出的概念,也就是即将被销毁、却能够被移动的值

    要拿到一个将亡值,就需要用到右值引用:T &&,右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活

C++11 提供了 std::move 这个方法将左值无条件转换为右值

# 常值修饰符,使用int const &

int const 和 int 可以看做是两个不同的类型,C++ 规定 int && 能自动转换成 int const &,但不能转换成 int &,因而,使用 int const & 也可以延长将亡值的生命周期。

小技巧:如果有移动赋值函数,可以删除拷贝赋值函数 当拷贝赋值函数被删除后,对于 v2 = v1; 编译器会尝试 v2 = List(v1); ,从而变成了移动语义,调用移动赋值函数。

# 移动语义

传统 C++ 通过拷贝构造函数和赋值操作符为类对象设计了拷贝/复制的概念,但为了实现对资源的移动操作,调用者必须使用先复制、再析构的方式,这是非常反人类的一件事情,造成了大量的时间和空间上的浪费。

class A {
public:
    int *pointer;
    A():pointer(new int(1)) {
        std::cout << "构造" << pointer << std::endl;
    }
    A(A& a):pointer(new int(*a.pointer)) {
        std::cout << "拷贝" << pointer << std::endl;
    } // 无意义的对象拷贝
    A(A&& a):pointer(a.pointer) {
        a.pointer = nullptr;
        std::cout << "移动" << pointer << std::endl;
    }
    ~A(){
        std::cout << "析构" << pointer << std::endl;
        delete pointer;
    }
};

# 完美转发

#include <iostream>
#include <utility>
void reference(int& v) {
    std::cout << "左值引用" << std::endl;
}
void reference(int&& v) {
    std::cout << "右值引用" << std::endl;
}
template <typename T>
void pass(T&& v) {
    std::cout << "              普通传参: ";
    reference(v);
    std::cout << "       std::move 传参: ";
    reference(std::move(v));
    std::cout << "    std::forward 传参: ";
    reference(std::forward<T>(v));
    std::cout << "static_cast<T&&> 传参: ";
    reference(static_cast<T&&>(v));
}
int main() {
    std::cout << "传递右值:" << std::endl;
    pass(1);

    std::cout << "传递左值:" << std::endl;
    int v = 1;
    pass(v);

    return 0;
}

结果为:

传递右值:
              普通传参: 左值引用
       std::move 传参: 右值引用
    std::forward 传参: 右值引用
static_cast<T&&> 传参: 右值引用
传递左值:
              普通传参: 左值引用
       std::move 传参: 右值引用
    std::forward 传参: 左值引用
static_cast<T&&> 传参: 左值引用

由于 v 是一个引用,所以同时也是左值。 因此 reference(v) 会调用 reference(int&) ,输出『左值』。

对于 pass(l),左值为什么可以成功传递给 pass(T &&)?

这是基于引用坍缩规则:

函数形参类型 实参参数类型 推导后形参类型
T& 左引用 T&
T& 右引用 T&
T&& 左引用 T&
T&& 右引用 T&&

总之,无论模板参数是什么引用,当且仅当实参类型为右值引用,模板参数才能被推导为右值引用类型。

完美转发就是基于上述规则,会保持原来的参数类型。我们应该使用 std::forward 对参数进行传递。

编辑 (opens new window)
#Cpp
上次更新: 2023/08/16, 16:01:49
现代C++:函数式编程(lambda、函数对象包装器)
现代C++:智能指针

← 现代C++:函数式编程(lambda、函数对象包装器) 现代C++:智能指针→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2024 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式