栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++中的std::move函数到底是做什么的?

C/C++/C# 更新时间:发布时间: 百科书网 趣学号

变量的赋值有两种语义,move和copy,C++和Rust作为系统级的编程语言,都实现了这两种语义,不过在表现形式上稍有不同。这里通过这两种语言的对比简单总结下C++和Rust中move和copy的不同,以及他们对函数签名函数传参的影响,希望能对大家理解move和copy有所帮助。

一、rust中的move和copy

在rust中,栈上的值的赋值操作默认是copy语义(实现了Copy trait):

let x = 5;
let y = x;
println!("x {} y {}", x, y);

上面的代码中,y是x的一个copy,copy之后,x和y都可以访问,上面的代码输出x 5 y 5。较为复杂的堆上的对象,赋值操作默认是move语义:

如上面的代码,nums1通过赋值操作move给了nums2,转移了对这个值的ownership,这样nums1就不可以再访问了,上面第3行代码会报borrow of moved value: 'nums1'的编译错误。修复上面编译报错有两种方法,一种是显示的clone:

let nums1 = vec![1, 2, 3];
let nums2 = nums1.clone();
println!("nums1 {:?} nums2 {:?}", nums1, nums2);

另外一种是borrow:

let nums1 = vec![1, 2, 3];
let nums2 = &nums1;
println!("nums1 {:?} nums2 {:?}", nums1, nums2);

      上面这两种方法都会输出nums1 [1, 2, 3] nums2 [1, 2, 3]。第一种方法nums1.clone()显示的把nums1复制了一份,这样并不影响nums1的ownership,复制完成之后,两个值是相互独立的;第二种方法&nums1是对nums1的一个引用,从语义上来说是对nums1这个值的借用。另外,rust在编译期检查了引用不能超过值的lifetime,保证引用的有效性,不会存在值已经释放而引用还在(dangling reference)的场景;同时,rust还对borrow做了限制,可修改的borrow和普通的只读的borrow不能同时存在,防止data race的产生。

通过上面的例子可以看出,在rust中,比较小的存储在栈上的值的赋值操作默认是copy的,大的存储在堆上的较为复杂的值默认是move的,并且move之后,原来的变量不可再访问,如果要实现复制语义的话,需要显示的调用clone()方法。

二、C++中的move和copy

     在C++中move和copy理解起来要稍微复杂一些,C++中分为值类型和引用类型,值分为左值和右值,同样的引用也分为左值引用和右值引用,左值引用和右值引用本质上类似于指针,和指针的一个不同是指针可以指向空nullptr,而引用在声明时一定要初始化,不允许空的引用。C++中的左值和右值的概念来自于C语言,能在等式左边的是左值(能取其的地址)、只能在等式右边出现的是右值(常量、临时变量比如说s1 + s2等)。

int var = 42;
int& ref = var;
ref = 99;
assert(var == 99);

如上面的代码,var是一个左值,ref是它的一个引用,针对这个引用的修改会修改原始的变量。但是下面的代码会报错:

int& ref1;      // ‘ref1’ declared as reference but not initialized
int& ref2 = 4;  // cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’

4是一个临时值,只能出现在等式的右边,因此是一个右值,左值的引用不能绑定到右值上。但是下面是可以的:

const int& ref = 4;

C++中允许const的左值引用绑定到右值,这应该是C++的一个特例,这样才能在调用void print(const std::string& s);函数的时候传递临时的值print("hello"); 。

int&& i = 42;
int j = 42;
int&& k = j;  // cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’

如上面的代码,int&&定义了右值引用类型,上面的第3行,j是一个左值,右值引用不能绑定到左值,上面的代码会报cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’编译错误,要修复这个错误,可以通过std::move把一个左值转成一个右值,上面的代码修改成:

int&& i = 42;
int j = 42;
int&& k = std::move(j);

注意:std::move并没有移动任何东西,上面的代码中,std::move类似于static_cast,只是把左值转成转成了一个右值引用。std::move语义上是说我把一个有地址的左值转成了一个临时的右值,后续可以对这个右值做任意的操作,比如说把它转移走等。

#include 

void test(std::vector&& nums) {
  // TODO
}

int main() {
  std::vector nums = {1,2,3};
  test(std::move(nums));
  return 0;
}

比如说上面的代码,std::move把一个左值的nums转成了右值,语义上是说这个值传递给test函数的时候,test函数内部可以对这个值做任意的操作,比如说把它移动走,也可以不把它移动走,完全取决于test函数的实现,test函数调用之后,nums值变成了什么呢?什么值都有可能,如果test函数内部把它移动走了的话,nums就变成了空的状态,如果没有移动,甚至test内部直接对nums做了修改的话,nums就变成了修改后的值(右值引用本质上也类似于指针哈~),所以这里建议一个变量的值被std::move转成右值之后,这个变量就不要再使用了,除非对这个变量重新进行赋值。另外需要注意的是在test函数中,如果要把nums的内容移走的话,要再加一次std::move才行:

void test(std:vector&& nums) {
  std::vector nums1 = nums;
  std::vector nums2 = std::move(nums);
}

如上面的代码,nums到nums1的赋值走的是拷贝构造函数,而下面的nums2走的才是移动构造函数,这是由于nums作为函数的参数是有地址的,是左值,虽然它的类型是右值引用。

注意:在代码中要尽量避免self move,可以试一试下面的代码会输出什么?

#include 
#include 

int main() {
  std::vector nums = {1,2,3};
  nums = std::move(nums);
  for (auto i : nums) {
    std::cout << i << std::endl;
  }
  return 0;
}

另外在C++中,函数模板的参数如果是T&&的话,比如下面的例子:

template
void foo(T&& t) {}

如果传入的是左值的话,t会被实例化成左值的引用类型,如果传入的是右值的话,t会被实例化成右值的引用类型,借此可以用来实现完美转发。

三、如何设计函数的参数类型

在rust中比较简单,如果要consume这个参数的话就直接传值,如果要borrow的话函数的参数类型就是引用,如果要修改传入的参数的话就mut borrow。C++中比较复杂,函数参数的类型可以是普通的值类型、const左值引用、普通左值引用、右值引用、指针等,函数的参数到底用哪种类型好呢,这是一个复杂的问题,可以参考这里:

https://isocpp.github.io/CppCor

这里就不再过多的说明了。

总之,C++和Rust都能实现move和copy语义,但是在使用上有一些区别。

附录 一、源码编译安装最新版本gcc操作步骤

从gcc官网下载源码包gcc-12.1.0.tar.xz并放置到~/gcc-building/目录下,然后执行下面的一系列的编译安装配置命令:

cd ~/gcc-building/ && tar xvf ./gcc-12.1.0.tar.xz
cd ~/gcc-building/gcc-12.1.0 && ./contrib/download_prerequisites

# 配置gcc,如果是在centos 7环境下,要兼容系统默认安装的gcc 4.8.5的话,
# 可以加上`--with-default-libstdcxx-abi=gcc4-compatible`
mkdir ~/gcc-building/building && cd ~/gcc-building/building 
../gcc-12.1.0/configure --prefix=/opt/gcc-12.1.0 --disable-multilib --enable-languages=c,c++

# 编译并安装
cd ~/gcc-building/building && make -j
cd ~/gcc-building/building && make install

## 配置环境变量
export PATH=/opt/gcc-12.1.0/bin:$PATH
export CC=/opt/gcc-12.1.0/bin/gcc
export CXX=/opt/gcc-12.1.0/bin/g++

## 配置ldconfig
echo -e "/opt/gcc-12.1.0/libn/opt/gcc-12.1.0/lib64" > /etc/ld.so.conf.d/gcc-12.1.0-x86_64.conf

参考资料:

《C++ Concurrency in Action, Second Edition》

https://en.cppreference.com/w/c

 

还有更多免费资料可以领取哦!加q:【828339809】获取
转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/1033383.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号