一文读懂std::move()

左值和右值

  1. 左值和右值的根本区别在于是否允许取地址&运算符获得对应的内存地址
  2. 变量可以取地址,所以左值;但是常量和临时对象等不可以取地址,所以是右值
  3. 左值是表达式结束后依然存在的持久对象(代表一个在内存中占有确定位置的对象)
  4. 右值是表达式结束时不在存在的临时对象(不在内存中占有确定位置的表达式)

左值引用和右值引用
左值引用的声明符号为&,右值引用的声明符号为&&
右值引用是用来支持转移语义的
右值引用,用以引用一个右值,可以延长右值的声明周期。

std::move
std::move本身并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上来讲,std::move基本上等同于一个类型转换:static_cast(lvalue).

简单示例

#include <iostream>

int main() {
    std::string str1("hello");
    std::cout << str1 << std::endl;
    std::string &&str2 = std::move(str1); // std::move返回的是是一个右值引用
    std::cout << str1 << std::endl;
    std::cout << str2 << std::endl;
    std::cout << &str2 << std::endl;
    std::string str3 = std::move(str2);
    std::cout << str2 << 11 << std::endl;
    return 0;
}

// 返回值
// hello
// hello
// hello
// 0x7fffffffd800
// 11

对于第二个hello, std::move本身不会删除任何东西,只是进行相应的类型转换。对于第五个hello并没有答应出来,是由于std::string调用了移动赋值函数,该函数以右值引用为输入。将str2内的原始指针赋值给了类指正变量,只涉及了指针操作,不需要拷贝,所以速度快,同时将原始指针指向了空指针,所以打印出来的str2为空。

为了更形象说明这个问题

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

class Str {
	public:
		char *str = NULL;
		Str(char value[]) {
			std::cout << "普通的构造函数" << std::endl;
			int len = strlen(value);
			str = (char *)malloc(len + 1); // void *malloc(size_t size); size为内存块的大小,以字节为单位。 需要#include <cstdlib>
			memset(str, 0, len + 1); // 初始化内存。上面只是分配内存,内存保存的还是原有的。
			strcpy(str, value); // strcpy(*dst, *src)
		}
		Str(const Str &s) {  // 左值引用
			std::cout << "拷贝构造函数" << std::endl;
			int len = strlen(s.str);
			str = (char *)malloc(len + 1);
			memset(str, 0, len + 1);
			strcpy(str, s.str);
		}
		Str(Str &&s) { // 右值引用
			std::cout << "移动构造函数" << std::endl;
			str = s.str;
			s.str = NULL;
		}
		Str& operator=(const Str& s) { //返回引用的目的是为了a=b=c这种情况
			std::cout << "拷贝赋值运算符" << std::endl;
			if (this != &s) { //需要先释放内存,然后再拷贝,如果不进行这个判断的话,后面拷贝就会拷贝到空指针
				if(str) free(str); //先释放内存
				int len = strlen(s.str);
				str = (char *)malloc(len + 1);
				memset(str, 0, len + 1);
				strcpy(str, s.str);
			}
			return *this; 
		}
		Str & operator=(Str&& s) { // 右值引用
			std::cout << "移动赋值运算符" << std::endl;
			if (this != &s) {
				if(str) free(str);
				str = s.str;
				s.str = NULL;
			}
			return *this;
		}
		 ~Str() {
		 	std::cout << "析构函数" << std::endl;
		 	if (str != NULL) {
		 		free(str);
		 		str = NULL;
		 	}
		 }
};
int main() {
    Str str1("hello");
    std::cout << str1.str << std::endl;
    Str &&str2 = std::move(str1); // std::move返回的是是一个右值引用
    std::cout << str1.str << std::endl; // char*, std::cout会将其视为一个以null为结尾的字符串。
    std::cout << str2.str << std::endl;
    std::cout << &str2 << std::endl;
    Str str3 = std::move(str2);
    std::cout << str2.str << 11 << std::endl;
    return 0;
}

// 返回值
// 普通构造函数
// hello
// hello
// hello
// 移动赋值函数
// 0x7fffffffd800
// 11

// 普通构造函数和普通赋值函数都是以左值引用为输入,所以需要进行内存分配,内存初始化,拷贝操作。
// 而移动构造函数和移动赋值函数都是以右值引用为输入,不需要重新进行内存分配,拷贝操作,只需要将指针进行赋值即可,大大提高了运行效率。
// 通常情况下如果需要使用std::move,就需要类中定义移动构造函数和移动赋值函数
void move_vector() {
	std::vector<int> vec1 = {1, 2, 3};
	std::vector<int> vec2 = std::move(vec1);
	std::cout << "vec1 size: " << vec1.size();
	std::cout << "vec2 size: " << vec2.size();
  std::vector<Str> vec3;
  Str str = "hi";
  vec3.push_back(str); //传入左值引用,调用拷贝构造函数
  vec3.push_back(std::move(str)); // 传入右值引用,调用移动构造函数
  vec3.emplace_back("hi"); // emplace_back接受的参数是用于构造函数的构造函数的参数,不是元素,可以利用这些参数直接在容器的存储空间中进行原地构造
}

reference:

  1. https://juejin.cn/post/7192171206030819385
  2. https://yongyuan.name/blog/from-cpp-std-move-to-move-constructor.html
  3. https://en.cppreference.com/w/cpp/utility/move
  4. https://blog.csdn.net/swartz_lubel/article/details/59620868

内存分配的相关函数

#include <cstdlib>

// void *malloc(size_t size);
// 输入申请内存的字节数,申请成功则返回指向该内存的void*指针;否则返回NULL
char * ch = (char *)malloc(100); // c++是一个强类型语言,通常需要明确指正的类型以便进行正确的内存操作和字节对齐
int *ptr_int = (int *)malloc(100); // 虽然这样是正确的,但还是建议写成(int *)malloc(25 * sizeof(int));

// 重新分配内存realloc(void *ptr, size_t new_size);
int *new_ptr_int = (int *)realloc(ptr_int, 60 * sizeof(int));

// 注意事项
// 1. 防止内存泄漏: 使用malloc分配的内存,必须显示的通过free函数进行释放,否则会导致内存泄漏
// 2. 初始化:malloc分配的内存不会自动初始化,内存中保存内原来的数据
// 3. 错误处理:存在分配失败的情况,需要检验malloc返回指是否是NULL, 以处理内存失败的情况。
// free
free(ch);
free(ptr_in);

// c++的动态内存管理
// 在c语言中,需要先分配内存,然后再进行类型转换,同时还需要处理内存申请失败的场景
// c++的内存管理工具为此提供了类型安全和异常处理,如new和delete
// new: 不仅分配内存,还会调用对象的构造函数
// delete: 用于释放内存,并且调用对象的析构函数
// new [] 和 delete[]是用于处理数组的组合
int * ptr_int = new int[25]; //分配25个int大小的内存,new在内存分配失败时会抛出异常
delete [] ptr_int; // 删除数组需要加[]
int * ptr_int = new int; // 分配一个指针
delete ptr_int; //释放内存

// c++中new和delete必须配合使用,如果只调用了new,而忘记了delete,通常会导致内存泄漏。为了避免内存泄漏,智能指针出现了。
// 智能指针是一种模拟指针行为的类,其目的是自动化资源管理,从而减少内存泄漏,使得代码更加安全和健壮。
// c++提供了几种类型的智能指针,主要包括std::unique_ptr、std::shared_ptr和std::week_ptr, 他们都定义在<memory>头文件中
// std::unique_ptr<type> 和std::make_unique<type>();
// std::unique_ptr是一种独占所有权的智能指针,意味着同一个时间智能有一个unique_ptr指向该特定资源的。当unique_ptr被销毁时,也就是离开作用域时,它指向的对象也会被自动删除。
// *解引用操作符, ->箭头操作符获取对象的成员, get()方法获得裸指针,std::move()移动语义
#include <memory>
void ProcessResource() {
	std::unique_ptr<int> ptr1 = std::make_unique<int>(10); // 初始化为10
	std::unique_ptr<int> ptr2 = std::make_unique<int []>(10); // 初始化为长度为10的list
	std::cout << "Resource value: " << *ptr1 << std::endl; // *为解引用操作符
	std::cout << ptr2[0] << std::endl;
}

void func(std::unique_ptr<int> ptr) { //由于std::unique_ptr去除了拷贝构造和赋值构造函数,调用该函数时,必须要用std::move()函数
	
}

// std::shared_ptr和std::make_shared
// std::shared_ptr是一种共享所有权的智能指针,允许多个shared_ptr实例指向同一个对象。内部使用引用计数机制来跟踪有多少个shared_ptr指向同一个资源,当最后一个shared_ptr被销毁时,它指向的对象也会被自动删除。
void ShareResource() {
	std::shared_ptr<int> shared_ptr = std::make_shared<int>(10);
	std::vector<std::shared_ptr<int>> ptr_list;
	ptr_list.push_back(shared_ptr); // 同一个资源被多个shared_ptr共享
	std::cout << "resource value: "  << *shared_ptr << " count: " << shared_ptr.use_count() << std::endl;
}

// std::weak_ptr是一种非拥有引用的智能指针,它指向一个由std::shared_ptr管理的对象,但不会增加对象的引用计数。
// 用于缓存和用于观察shared_ptr管理的对象,而不需要拥有对象
void ObserveResource() {
	std::shared_ptr<int>shared_ptr = std::make_shared<int>(0);
	std::weak_ptr<int> weak_ptr = shared_ptr;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值