C++从函数返回对象

3
下面的代码展示了一个表示复数的类。我对理解 operator+ 函数很感兴趣。据我所知,Complex res 应该在 operator+ 函数的框架上分配。将该对象返回给调用者是否正确?当这个函数返回时,该框架已经弹出,但是 res 对象仍将被调用者使用。除非有更多的细节需要注意,例如实际的 return res 可能会将对象从当前框架复制到调用者的框架中。另一个可能性是,在主调用点上内联 operator+ 函数中的代码?根据我有限的语言理解,声明在类内部的函数默认情况下会在调用点上进行内联处理。任何帮助都将不胜感激。
#include<iostream>
using namespace std;

class Complex {
private:
    int real, imag;
public:
    Complex(int r = 0, int i =0) {real = r; imag = i;}
    
    Complex operator+(Complex const &obj) {
        Complex res;
        res.real = real + obj.real;
        res.imag = imag + obj.imag;
        return res;
    }
    void print() { cout << real << " + i" << imag << endl; }
};

int main()
{
    Complex c1(10, 5), c2(2, 4);
    Complex c3 = c1 + c2; 
    c3.print();
}

在阅读下面的评论和答案后,我添加了以下内容以澄清解决方案:

我使用上面代码进行了更新,添加了以下内容:

#include<iostream>
using namespace std;

class Complex {
private:
    int real, imag;
public:
    Complex(int r = 0, int i =0) {real = r; imag = i;}
    
    Complex operator+(Complex const &obj) {
        Complex res;
        res.real = real + obj.real;
        res.imag = imag + obj.imag;
        cout << "Address inside the function " << &res << "\n";
        return res;
    }
    void print() { cout << real << " + i" << imag << endl; }
};

int main()
{
    Complex c1(10, 5), c2(2, 4);
    Complex c3 = c1 + c2; 
    cout << "Address outside the function " << &c3 << "\n";
    c3.print();
}


输出结果显示栈的两个不同区域上有两个不同的地址,表明返回时进行了值复制
Address inside the function 0x7fffbc955610
Address outside the function 0x7fffbc955650

学习C++时不要考虑框架的问题。它是一种高级语言,框架的概念并不是其中的一部分。虽然当你掌握了这门语言并想要研究一些实际方面(如性能)时理解框架可能很重要,但在这个阶段只会让你感到困惑。 - SergeyA
1
你正在从 operator+() 函数中按值返回 Complex。因此,这将是 res 的一个副本。 - DS_London
@DS_London 非常感谢!我来自Java背景,无法超越引用的思维。我使用以下代码修改了上面的代码: 在operator+中,我添加了以下行 cout << "函数内部地址 " << &res << "\n";main中,我添加了以下行cout << "函数外部地址 " << &c3 << "\n";。我得到了两个不同的地址,表明了您提到的按值复制 - sshekhar1980
1
可能会有趣的是在您编写的Complex()默认构造函数中添加一个附加的输出行,并查看它被调用的次数。您将看到,虽然您有4个复数对象,但只调用了3次构造函数。operator()函数的返回值使用了另一个构造函数(复制或移动),您尚未定义,但已为您实现。如果您定义复制构造函数Complex(const Complex & c){},则会看到该函数被调用以进行返回值。这些其他构造函数是如何创建的以及何时创建的略微复杂... - DS_London
2个回答

1

将这个对象返回给调用者是否正确?

C++支持引用返回和值返回。由于您没有使用引用返回,因此您没有向调用者返回对象的引用。您使用的是值返回,因此您正在向调用者返回对象的值。请考虑:

int foo()
{
    int i = 2;
    return i;
}

这将返回值2,而不是对象i。在return之后,i本身不存在已经无关紧要了,因为它的值已经被用来确定返回的值。

感谢@David Schwartz!根据您的反馈,我更新了问题以显示对代码的修改。 - sshekhar1980

1

带有值的传输始终使用堆栈。 当您想要返回值时,根据调用者代码,复制构造函数或赋值运算符可能会隐式调用并将返回值分配给左侧的对象(lvalue)。

Complex  nwobj=cmpx1 + cmplx2; //copy constructor used to assign return object to lvalue

cmplx3=cmplx1+xmplx2;//operator= used to make a right assignment. 

注意:

在第一行中的复制构造函数可能会发生或被省略,具体取决于所使用的编译器及其设置。关于此的全面解释可以在以下链接中找到:
SO: 什么是复制省略和返回值优化?


复杂的nwobj = cmpx1 + cmplx2; //使用复制构造函数将返回对象分配给lvalue。这并不一定是真的。在完全信任复制省略和返回值优化的情况下,我在coliru上进行了演示,说明了相反的情况。请注意,恰好有三个构造函数调用,其中没有一个是复制构造函数。 - Scheff's Cat
如果我在OP的代码中定义了Complex(const Complex&){},那么它会在行Complex c3 = c1 + c2中被调用。如果我还定义了Complex(Complex&&),那么它将被调用。如果我修改operator+的定义为return Complex(real + obj.real, imag + obj.imag);,那么两者都不会被调用(我认为是因为优化)。 - DS_London
我定义了 Complex(const Complex&) 并且没有忘记使用 std::cout 输出 "diagnostic"。 (顺便说一下,也确实有3个析构函数调用。)问题是,你使用了什么编译器(以及什么C++标准)? 我使用的是 g++ -std=c++17。(在C++17中,复制省略变得强制性。)我忘记提到这点了... ;-) - Scheff's Cat
为了确保,我添加了移动构造函数和移动赋值,尽管我已经知道这不会改变任何东西:带有移动构造函数的演示(coliru) - Scheff's Cat
这就是复制省略的诀窍:编译器意识到结果 res 必须返回并存储到另一个正在构建中的对象中。因此,res 在为返回值分配的内存中构建,该内存位于 c3 的地址上,从而消除了复制。 - Scheff's Cat
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接