用一个简单的方式理解右值引用在内存的状态:
从图中明白:左值引用是对变量的引用(const int &rint = 1这种方式例外)
右值引用就是对变量对应的值的引用,为了延长这块值所在内存的声明周期(如果一块内存的引用计数为0就释放这块内存)
另外从代码执行的结果中明白函数return语句执行了两个过程:
第一步:给接收本返回值的变量赋值,如果没有变量接收就创建一个临时变量
第二步:析构局部变量
#pragma once
/************************************************************************/
/* 如果返回class或者struct类型的局部变量,那么就需要添加移动构造和移动赋值 */
/* 容器的元素是class或者struct类型的值类型,那么就需要添加移动构造和移动赋值 */
/* 右值引用如果不是const就可以被赋值 */
/***************************************注意******************************/
/* 函数中不要返回std::move()形式 */
/* 不使用T&& =func() 形式接收函数返回值 */
/* 没有必要std::move(func()); 直接用变量T tvar = func()形式接收函数返回值 */
/************************************************************************/
class RValueRef
{
public:
explicit RValueRef(int a, int b) :m_a(new int(a)), m_b(b), m_varName{""}
{
cout << "构造函数" << endl;
}
RValueRef(const RValueRef& other) : m_a(new int(*(other.m_a))), m_b(other.m_b)
{
this->setVarName("=" + other.getVarName());
cout << "copy构造" << endl;
}
RValueRef& operator=(const RValueRef& other) & // 该函数只能用于左值
{
if (this != &other)
{
std::swap(*(this->m_a), *(other.m_a));
this->m_b = other.m_b;
cout << "赋值运算符" << endl;
}
return *this;
}
/************************************************************************/
/* 将移动构造和移动赋值定义为noexcept是有用的。在向容器添加元素时如果不定义noexcept,容器*/
/* 优先使用拷贝构造函数或者常规赋值运算符函数 */
/* 编译器不会默认创建移动构造或者移动赋值(满足默认创建的条件非常复杂,开发中不会遇到默认生成的这两个函数)*/
/************************************************************************/
RValueRef(RValueRef&& other) noexcept : m_a(other.m_a), m_b(other.m_b)
{
this->m_varName.assign("=" + other.getVarName());
other.m_a = nullptr;
cout << "移动copy构造" << endl;
}
RValueRef& operator=(RValueRef&& other) noexcept
{
if (this != &other)
{
*(this->m_a) = *(other.m_a);
this->m_b = other.m_b;
delete other.m_a;
other.m_a = nullptr;
cout << "移动赋值运算符" << endl;
}
return *this;
}
~RValueRef()
{
if (m_a)
{
delete m_a;
m_a = nullptr;
cout << ("真实析构函数:" + this->getVarName()) << endl;
}
else
{
cout << ("假象析构函数:"+this->getVarName()) << endl;
}
}
// 右值函数 或者 const右值函数
void printfFunc() const
{
cout << "右值函数" << endl;
}
// 左值函数 或者 const左值函数
void printFunc() const &
{
cout << "左值函数" << endl;
}
void setVarName(const string& name)
{
this->m_varName.assign(name);
}
const string& getVarName() const
{
return m_varName;
}
private:
int *m_a;
int m_b;
string m_varName;
};
// 通过右值引用占用局部变量的内存,避免了局部变量的析构
inline RValueRef testTmpFunc()
{
RValueRef refvar(12, 23);
refvar.setVarName("refvar");
return refvar;
}
inline RValueRef&& testRvalRefFunc()
{
RValueRef refvar2(25, 26);
refvar2.setVarName("refvar2");
return std::move(refvar2); // 不要返回std::move(), 因为std::move()的返回值会优先释放掉,那么在return中的移动赋值构造中会使用野指针。
}
inline void valueArg(const RValueRef ref)
{
cout << "值传递" << endl;
}
inline void testRVRef()
{
RValueRef com(1,2);
com.setVarName("com");
RValueRef comVar(121, 23);
comVar.setVarName("comVar");
RValueRef comVar2 = std::move(comVar);
comVar2.setVarName("comVar2");
// 不涉及任何构造函数,这个步骤之后comVar就不再使用了,即使comVar还能拿到这块内存的值但是从语义上讲我们不能再使用它。
RValueRef&& at = std::move(comVar2); // 将左值变成右值
RValueRef assignVar(33, 45);
assignVar.setVarName("assignVar");
assignVar = std::move(com);
assignVar.setVarName("com+");
at = assignVar; // 调用operator=(RValueRef&) 而不是operator=(RValueRef&&), 现在代表的是左值要改变其内存的值
at.printfFunc();
cout << "***************0***************" << endl;
RValueRef retvar1 = std::move(testTmpFunc());
retvar1.setVarName("retvar1");
cout << "***************1***************" << endl;
RValueRef&& retvar2 = std::move(testTmpFunc()); // retvar2 指针内存被释放
cout << "***************2***************" << endl;
RValueRef retvar3 = testTmpFunc();
retvar3.setVarName("retVar3");
cout << "***************3***************" << endl;
RValueRef&& Rretvar1 = testRvalRefFunc(); // Rretvar1 完全废弃的内存
cout << "***************4***************" << endl;
vector<RValueRef> vecRVR;
vecRVR.push_back(RValueRef(11, 22));
vecRVR[0].setVarName("vecRVR[0]");
cout << "***************5***************" << endl;
RValueRef argValue(2, 34);
argValue.setVarName("argValue");
valueArg(argValue);
cout << "***************6***************" << endl;
RValueRef argValue1(2, 34);
argValue1.setVarName("argValue1");
valueArg(std::move(argValue1)); // 函数调用结束, argValue1被释放
cout << "***************7***************" << endl;
}
输出结果
构造函数 // RValueRef com;
构造函数 // RValueRef comVar(12, 23);
移动copy构造 // RValueRef comVar2 = std::move(comVar);
构造函数 // RValueRef assignVar(33, 45);
移动赋值运算符 // assignVar = std::move(comVar2);
赋值运算符 // at = assignVar;
右值函数 // at.printfFunc();
***************0***************
构造函数 // testTmpFunc::RValueRef refvar(12,23);
移动copy构造 // return 先返回refvar,作为std::move()的参数
假象析构函数:refvar // return 再析构refvar
移动copy构造 // RValueRef retvar1 = std::move(testTmpFunc()); std::move()返回值初始化retvar1
假象析构函数:=refvar // std::move()返回值被析构
***************1***************
构造函数 // testTmpFunc::RValueRef refvar(12,23);
移动copy构造 // return 先返回refvar,作为std::move()的参数
假象析构函数:refvar // return 再析构refvar
真实析构函数:=refvar // 在retvar2被赋值前std::move()返回值被释放,所以retvar2中的指针是野指针
***************2***************
构造函数 // testTmpFunc::RValueRef refvar(12,23);
移动copy构造 // return 先返回refvar,赋值给RValueRef retvar3
假象析构函数:refvar // return 再析构refvar
***************3***************
构造函数 // testTmpFunc::RValueRef refvar(12,23);
真实析构函数:refvar2 // return 先把refvar返回给RValueRef&& Rretvar1,接着又释放了refvar的内存,造成Rretvar1引用一个完全废弃的内存。和部分被废弃还不一样。
***************4***************
构造函数 // RValueRef(11, 22) 构造
移动copy构造 // vecRVR.push_back() 赋值
假象析构函数: // 离开vecRVR.push_back()方法析构RValueRef(11, 22)
***************5***************
构造函数 // RValueRef argValue(2, 34);
copy构造 // 使用argValue初始化void valueArg(RValueRef ref)函数参数
值传递
真实析构函数:=argValue // valueArg()函数参数ref的释放
***************6***************
构造函数 // RValueRef argValue1(2, 34);
移动copy构造 // 使用std::move(argValue)初始化void valueArg(RValueRef ref)函数参数
值传递
真实析构函数:=argValue1 // ref释放内存,同时也标志着argValue1被是释放 。
***************7***************
假象析构函数:argValue1
真实析构函数:argValue
真实析构函数:vecRVR[0]
真实析构函数:retVar3
真实析构函数:retvar1
真实析构函数:com+
真实析构函数:comVar2
假象析构函数:comVar
假象析构函数:com