右值、右值引用的等相关的持续学习

恰好一年半这段时间没更新,其实不是啥好事,但另一方面又庆幸能来写一写。

 

概念都搞的比较模糊。持续杂记、整理下。

这记录的是当前(2024-11-21)的理解,有错的话持续修正。

 

三一法则五一法则

默认构造、拷贝构造、拷贝赋值、移动构造、移动赋值。这五个是编译器会默认合成的,且说他们的是否会合成规则就挺绕的了。它们一起构成了对象生命周期和内部数据的一些管理。涉及它们的有些是约定、有些是规定。

 

另管理资源的还有析构函数。

析构函数多半是要加virtual的,移动函数多半是要加noexcept的 。

 

左值、右值、右值引用。

右值引用也只是引用。

一个名字,它被定义为右值引用,引用到了某个对象,那这个名字的修改就会体现在这个变量上。

而右值引用,其实就是默认只能绑定到右值上的引用。

 

std::move

就是个强制类型转换。作用于一个非右值的实参后,让一个右值引用可以绑定到一个非右值上。

 

移动函数的书写约定

默认合成的是什么样的,和显式手写的有什么区别,和default修饰的,这三者之间关系。 default的和默认合成的,在实现本身上完全一致,但为什么default有啥用,

可以改变函数可访问性;

对复制的相关函数参数是否是const的改变;(移动的两个函数本身就必须是非const的形参)

增加可读性;

 

自定义的移动构造函数,当然你可以任意的写。但它存在的目的是能在某些重载被匹配时,使用起来更高效。如何高效,对于一些资源,在使用方已知并遵守规则情况下,对资源做“窃取”,为啥这样做,还是为了高效,因为使用方知道这个对象可以被窃取,所以让使用方有的选,可以选择走移动这条道,省掉不必要的复制代价。

 

复制控制的几个函数存在就是为了对一些特殊资源的管理。

如果都是普通资源,那直接各层级都走默认的就是合理且高效的。

而对于一些特殊资源,典型的例子就比如一块内存。

复制控制,合理适当的被使用场景是 当要深拷贝的地方,而移动发生在构造实参后面不在被使用的场景,(通常右值做为实参后不会再被使用,显示的std::move后的变量应该也被约定为不再被使用。)

对于移动,有了这些约定和使用规范后(需要使用者自己遵守),就可以再 移动构造函数或移动赋值操作符中,进行“窃取”之事了。仍以一块内存为例,那就是直接将实参的指针值赋值给当前被构造对象的对应属性,而不是new个新的再做深拷贝复制。

 

何时被调用

有了这些高效的 移动构造函数或移动赋值操作符 后,什么时候被调用呢,理论上当然是被优先调用为好了(当条件满足,能被匹配的前提下),这个应该就看重载匹配相关的知识点了。

除了这些收规矩的自动优化调用,显式约定调用的就是 强制类型转换,比如std::move来修饰下实参的情况下了。其实,还是走的重载匹配的路子,只是让这个实参做了 更深的修饰,让它符合被右值版本重载匹配的条件。

 

 

一些测试代码

通过类类型的属性,在起构造时,被调用的构造函数类型来观察:

 

#include <iostream>
 
 
class Nest {
public:
	Nest() {
		std::cout << "run in Nest()" << std::endl;
	}
 
	Nest(const Nest& robj) {
 
		std::cout << "run in Nest(const Nest &)" << std::endl;
	}
 
	Nest(Nest&& robj) /*noexcept*/ {
		std::cout << "run in Nest(const Nest &&)" << std::endl;
	}
};
 
class X {
public:
// 	X() :pt(new char[3]) {
// 		std::cout << "run in X()" << std::endl;
// 	}
	X(int ,int){}
 
	//X(const X& robj) = default;
	//X(X && robj) = default;
 
// 	X(X&& robj) /*noexcept*/ :pt(robj.pt) {
// 		robj.pt = nullptr;
// 		std::cout << "run in X(const X &&), curr robj.pt:" << (int)robj.pt << std::endl;
// 	}
 
	X& operator=(const X&) = default;
 
private:
	char* pt;
	Nest m_n;
};
 
void funTestObj(X& robj) {
	std::cout << "run in funTestObj(X& robj)" << std::endl;
}
 
void funTestObj(X&& robj) {
	std::cout << "run in funTestObj(X&& robj)" << std::endl;
}
 
void funt1120(X&& robj) {
	//X newobj = robj;  // 拷贝构造函数
	X newobj = std::move(robj);  // 拷贝构造函数
	//funTestObj(robj);
}
 
 
int main() {
	//X obj;
	X obj(1, 2);
 
	//funt1120(std::move(obj));
 
	X obj2 = std::move(obj); //移动构造函数
 
	return 0;
}

  

 

例如上面的版本,我们可以测试,当有这句时“X& operator=(const X&) = default;”,Nest类走的是拷贝构造;咩有的时候,能成功走移动。

 

posted @ 2024-11-21 11:16  xiarunliang  阅读(6)  评论(0编辑  收藏  举报