当一个函数有一个通过值传递的对象时,它使用复制构造函数或按位复制来创建临时放置在堆栈上以在函数内部使用,如何从函数返回一些对象?
//just a sample code to support the qn
rnObj somefunction()
{
return rnObj();
}
并解释了如何将返回值带到被调用函数。
答案 0 :(得分:6)
可以通过其他答案来判断 - 编译器可以对此进行优化。
使用MSVC生成的具体示例,用于解释如何这是可能的(如其中一条评论中所述) -
上课 -
class AClass
{
public:
AClass( int Data1, int Data2, int Data3 );
int GetData1();
private:
int Data1;
int Data2;
int Data3;
};
通过以下简单的实施 -
AClass::AClass( int Data1, int Data2, int Data3 )
{
this->Data1 = Data1;
this->Data2 = Data2;
this->Data3 = Data3;
}
int AClass::GetData1()
{
return Data1;
}
以下调用代码 -
AClass Func( int Data1, int Data2, int Data3 )
{
return AClass( Data1, Data2, Data3 );
}
int main()
{
AClass TheClass = Func( 10, 20, 30 );
printf( "%d", TheClass.GetData1() );
}
(添加printf()只是为了确保编译器不会优化所有内容......) 在非优化代码中,我们希望Func()在其堆栈上创建一个本地AClass,在那里构造它并将其复制为其返回变量。
但是,生成的程序集实际上看起来像(删除不需要的行) -
_TEXT SEGMENT
___$ReturnUdt$ = 8 ; size = 4
_Data1$ = 12 ; size = 4
_Data2$ = 16 ; size = 4
_Data3$ = 20 ; size = 4
mov eax, DWORD PTR _Data3$[esp-4]
mov ecx, DWORD PTR _Data2$[esp-4]
mov edx, DWORD PTR _Data1$[esp-4]
push esi
mov esi, DWORD PTR ___$ReturnUdt$[esp]
push eax
push ecx
push edx
mov ecx, esi
call ??0AClass@@QAE@HHH@Z ; AClass::AClass
mov eax, esi
pop esi
ret 0
从堆栈中提取3个函数变量并将其放入eax,ecx和edx中 另外的第四个值被放入esi(并传递给ecx) 使用堆栈上的3个参数调用构造函数,并且ecx仍然包含第四个值。
让我们看一下构造函数 -
_TEXT SEGMENT
_Data1$ = 8 ; size = 4
_Data2$ = 12 ; size = 4
_Data3$ = 16 ; size = 4
mov edx, DWORD PTR _Data2$[esp-4]
mov eax, ecx
mov ecx, DWORD PTR _Data1$[esp-4]
mov DWORD PTR [eax], ecx
mov ecx, DWORD PTR _Data3$[esp-4]
mov DWORD PTR [eax+4], edx
mov DWORD PTR [eax+8], ecx
ret 12 ; 0000000cH
3个构造函数参数被读入eax的偏移量 - eax是ecx的副本,是上面调用的第四个参数。
因此,构造函数构建了被告知的对象--Func()的第四个参数。
并且,你猜对了,Func()的第四个参数实际上是整个程序中存在构造的AClass的真正单个位置。让我们看看main()的相关部分 -
_TEXT SEGMENT
_TheClass$ = -12 ; size = 12
_main PROC
sub esp, 12
push 30
push 20
lea eax, DWORD PTR _TheClass$[esp+20]
push 10
push eax
call ?Func@@YA?AVAClass@@HHH@Z ; Func
为AClass保留12个字节,并传递Func()的三个参数以及第四个参数 - 指向这12个字节。
这是特定编译器的具体示例。其他编译器的做法不同。但这就是事物的精神。
答案 1 :(得分:3)
C ++标准规定编译器可以选择使用复制构造函数创建临时对象,也可以选择优化临时对象。
有一篇关于这个主题的维基百科文章,有很多参考文献here。
答案 2 :(得分:1)
允许编译器在返回时执行复制。大多数编译器都不会。但是,您必须确保rnObj
具有可访问的复制构造函数。
答案 3 :(得分:1)
如果打开优化,大多数现代编译器都可以避免临时对象创建。有关详细信息,请参阅Named Return Value Optimization。