C++关于返回引用和非引用的区别

本文探讨了C++中类方法返回引用和非引用的区别,特别是涉及对象拷贝和生命周期的问题。通过分析代码和查看汇编层面,揭示了返回引用时对象在函数结束后不会被销毁,而在寄存器中保持,而返回非引用则涉及对象的拷贝过程。此议题对于理解和避免迭代器重载时可能导致的潜在错误至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++关于返回引用和非引用的区别

这几天刚好遇到一个题目。我们跳过背景,直接切入正题。

template<class T>
class HyperVectorIterator : public std::iterator< std::random_access_iterator_tag, T >;

这是类的定义,这个类的目的是继承iterator,然后完成相应的++, --, <, ==等相应符号的重载,作为HyperVector类的迭代器。HyperVector类是一个自定义的容器类,支持随机访问,这里先不多介绍此类。下面我们拿自减一的操作符重载来实验:

// An highlighted block
template<class T>
HyperVectorIterator<T> HyperVectorIterator<T>::operator--()
{
	--(this->m_pos);
	return HyperVectorIterator(this->m_pContainer, this->m_pos);
}
// An highlighted block
template<class T>
HyperVectorIterator<T>& HyperVectorIterator<T>::operator--()
{
	--(this->m_pos);
	return HyperVectorIterator(this->m_pContainer, this->m_pos);
}

几乎一样的代码,唯一不同的是下面这个类操作符重载返回的是一个引用。有什么区别呢?
如果抛开底层汇编代码,那么直观的理解应该是第一份代码返回对象的时候做了创建对象、对象拷贝、释放对象的操作,而第二份代码返回对象的时候仅做了创建对象一步操作。但不是很能理解的是:函数在结束的时候,应该会释放函数内的局部变量的内存空间,那么返回的引用会不会被销毁?现实是C++使用了那么多年,如果真发生这样的bug,应该轮不到被我发现。所以问题应该出自于自己理解的偏差。
我很懒,不是很喜欢翻书。但这个问题一直积压着又很难受,所以,我尝试往下再看一层,汇编层。

template<class T>
HyperVectorIterator<T> HyperVectorIterator<T>::operator--()
{
00419390  push        ebp  
00419391  mov         ebp,esp  
00419393  sub         esp,0CCh  
00419399  push        ebx  
0041939A  push        esi  
0041939B  push        edi  
0041939C  push        ecx  
0041939D  lea         edi,[ebp-0CCh]  
004193A3  mov         ecx,33h  
004193A8  mov         eax,0CCCCCCCCh  
004193AD  rep stos    dword ptr es:[edi]  
004193AF  pop         ecx  
004193B0  mov         dword ptr [this],ecx  
	--(this->m_pos);
004193B3  mov         eax,dword ptr [this]  
004193B6  mov         ecx,dword ptr [eax+4]  
004193B9  sub         ecx,1  
004193BC  mov         edx,dword ptr [this]  
004193BF  mov         dword ptr [edx+4],ecx  
	return HyperVectorIterator(this->m_pContainer, this->m_pos);
004193C2  mov         eax,dword ptr [this]  
004193C5  mov         ecx,dword ptr [eax+4]  
004193C8  push        ecx  
004193C9  mov         edx,dword ptr [this]  
004193CC  mov         eax,dword ptr [edx]  
004193CE  push        eax  
004193CF  mov         ecx,dword ptr [ebp+8]  
004193D2  call        HyperVectorIterator<float>::HyperVectorIterator<float> (0411A82h)  
004193D7  mov         eax,dword ptr [ebp+8]  
}
004193DA  pop         edi  
004193DB  pop         esi  
004193DC  pop         ebx  
004193DD  add         esp,0CCh  
004193E3  cmp         ebp,esp  
004193E5  call        __RTC_CheckEsp (04114C9h)  
004193EA  mov         esp,ebp  
004193EC  pop         ebp  
004193ED  ret         4  

(先补充一个知识:call指令返回值的时候都是用ax寄存器来保存的)
返回非引用的时候,004193D7地址的指令果然是做了一次对象的拷贝。而且可以看到保存到了ebp+8的地址处,ebp应该函数内临时变量分配的基址,局部变量保存的位置应该都比ebp来的小,那么这个比ebp大的ebp+8位置的地方是哪里呢?应该是上一个调用者的分配到的内存空间。这也很合理,不然在函数退出时局部变量都被销毁了还怎么拿到新分配的对象呢?
再看下面一段代码:

template<class T>
HyperVectorIterator<T>& HyperVectorIterator<T>::operator--()
{
00419390  push        ebp  
00419391  mov         ebp,esp  
00419393  sub         esp,0DCh  
00419399  push        ebx  
0041939A  push        esi  
0041939B  push        edi  
0041939C  push        ecx  
0041939D  lea         edi,[ebp-0DCh]  
004193A3  mov         ecx,37h  
004193A8  mov         eax,0CCCCCCCCh  
004193AD  rep stos    dword ptr es:[edi]  
004193AF  pop         ecx  
004193B0  mov         dword ptr [this],ecx  
	--(this->m_pos);
004193B3  mov         eax,dword ptr [this]  
004193B6  mov         ecx,dword ptr [eax+4]  
004193B9  sub         ecx,1  
004193BC  mov         edx,dword ptr [this]  
004193BF  mov         dword ptr [edx+4],ecx  
	return HyperVectorIterator(this->m_pContainer, this->m_pos);
004193C2  mov         eax,dword ptr [this]  
004193C5  mov         ecx,dword ptr [eax+4]  
004193C8  push        ecx  
004193C9  mov         edx,dword ptr [this]  
004193CC  mov         eax,dword ptr [edx]  
004193CE  push        eax  
004193CF  lea         ecx,[ebp-0D8h]  
004193D5  call        HyperVectorIterator<float>::HyperVectorIterator<float> (0411A82h)  
}
004193DA  pop         edi  
004193DB  pop         esi  
004193DC  pop         ebx  
004193DD  add         esp,0DCh  
004193E3  cmp         ebp,esp  
004193E5  call        __RTC_CheckEsp (04114C9h)  
004193EA  mov         esp,ebp  
004193EC  pop         ebp  
004193ED  ret  

在004193D5地址执行完call指令后什么都没做就进入函数退出前的恢复动作了!也就是返回引用的时候不会改变eax的值。临时对象一直在寄存器里面,没有进入过内存。
今天就写这么多,第一次写优快云的文章。这个问题其实是为另一个问题做铺垫的,大背景其实是写自己的迭代器,重载符号函数的时候返回引用还是非引用,会直接影响容器类对象调用sort方法时是否产生崩溃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值