SoFunction
Updated on 2025-03-10

The difference and description of the return value of C++ is a class name and the return value is a reference

The difference between the return value being a class name and the return value being a reference

Return non-referenced types

The return value of the function is used to initialize the temporary object created when calling the function. If the return type is not a reference, the return value of the function will be copied to the temporary object where the function is called.

When solving an expression, if you need a place to store its operation results, the compiler will create an unnamed object, which is a temporary object. C++ programmers usually use the term temporary instead of temporary object.

The method of initializing a temporary object with a function return value is the same as initializing a formal parameter with a real parameter.

When a function returns a non-referenced type, its return value can be either a local object or the result of solving the expression.

Return the reference type

When the function returns a reference type, the return value is not copied, instead, the object itself is returned.

Never return a reference to a local object! Never return a pointer to a local object!

When the function is executed, the storage space allocated to the local object will be freed. At this time, the reference to the local object will point to uncertain memory! The same is true for returning a pointer to a local object. When the function ends, the local object is released and the returned pointer becomes a dangled pointer to an object that no longer exists.

When returning a reference, it is required that the parameters of the function include parameters that exist in reference or pointer form and need to be returned.

If the object is returned, the copy constructor will be executed more than once in the end. If the reference is returned, the existing object will be returned directly.

#include <iostream>
using namespace std;

class Timer
{
public:
    Timer();
    Timer(int, int, int);
    friend Timer  &operator+(Timer&, Timer&);
    friend Timer operator-(Timer&, Timer&);
    friend ostream& operator<<(ostream &out, Timer &t);
    friend istream& operator>>(istream &in, Timer &t);
private:
    int hour, minute, second;
};

Timer::Timer()
{
    hour = 0;
    minute = 0;
    second = 0;
}

Timer::Timer(int hour, int minute, int second)
{
    this->hour = hour;
    this->minute = minute;
    this->second = second;
}

Timer & operator+(Timer& a, Timer &b)
{
     =  + ;
     =  + ;
     =  + ;
    if ( >= 60)
    {
         =  - 60;
        ++;
    }
    if ( >= 60)
    {
         =  - 60;
        ++;
    }
    return a;
}

Timer operator-(Timer &a, Timer &b)
{
    Timer c;
     =  - ;
     =  - ;
     =  - ;
    if ( < 0)
    {
         += 60;
        --;
    }
    if ( < 0)
    {
         += 60;
        --;
    }
    return c;
}

ostream& operator<<(ostream &out, Timer &t)
{
    out <<  << ":" <<  << ":" <<  << endl;
    return out;
}

istream& operator>>(istream&in, Timer &t)
{
    cout << "Input hours and minutes." << endl;
    in >>  >>  >> ;
    return in;
}

int main()
{
    Timer t1, t2, t3, t4;
    cin >> t1 >> t2;
    cout << "t1=" << t1;
    cout << "t2=" << t2;
    t3 = t1 + t2;
    cout << "t3=t1+t2=" << t3;
    t4 = t1 - t2;
    cout << "t4=t1-t2=" << t4;
    return 0;
}

At the beginning I declared the function as:

friend Timer&operator+(Timer&,Timer&);
friend Timer&operator-(Timer&,Timer&);

The overload declaration for <<,>> is the same as the long code. But after doing this I found an exception.

In Time &operator+(Timer&t1,Timer&t2), I declare a local variable of Timer, and when the function call is finished, the pointer becomes a hanging pointer.

Therefore, be sure to remember not to return local variables in functions whose return value is a reference.

C++ function returns value and return reference issues

The return process of C++ functions can be basically divided into two stages, the return stage and the binding stage. Different situations will occur depending on the type of the value to be returned in the two stages (return value and reference) and the type of the value to be bound (binding value and reference).

The most basic rule is to return first, then bind. When returning and binding, it is possible to move or copy the constructor call to create temporary objects, and it will only happen once. More specifically, when the value is returned, the copy constructor or the move constructor will be called once before the function returns. When the function is bound to the value, the copy constructor or the move constructor will occur once. If the constructor has been called when returning, the binding will not be called again, but will be directly bound. Return a reference or bind to a reference. In each stage, no constructor call will be generated.

The following examples illustrate various possible situations (in many cases we should not write this way, this article only discusses what will happen if we do this).

An explanation of rvalue references and moving constructors, I will write a summary later. There is only one rule needed here, that is, when you need to call the moving constructor. If the class does not define the moving constructor, then call its copy constructor. In the case where the moving constructor is not defined, I will not repeat it again.

For the convenience of narrative, define the bound value and return value:

The bound value is the variable assigned to the function, and the return value is the return value of the function. The final call form is similar:

Return value fun()
{
    .....
    return Return value;
}

Bind value = func();

Define a class:

class myClass
{
public:
    //Constructor    myClass()
    {
        cout &lt;&lt; "construct" &lt;&lt; endl;
    }
    //Copy constructor    myClass(const myClass&amp; rhs)
    {
        cout &lt;&lt; "copy construct" &lt;&lt; endl;
    }
    //Move the constructor    myClass(myClass&amp;&amp; rhs)
    {
        cout &lt;&lt; "move consturct" &lt;&lt; endl;
    }
    //Destructor    ~myClass()
    {
        cout &lt;&lt; "desctruct" &lt;&lt; endl;
    }
};

1. The binding value type is a value type

The return value type is a value type, and the return local variable:

In the function return stage, the class's move constructor is called to create the return value and bind it to the bound value. The call to move the constructor occurs during the function return stage.

example:

myClass fun()
{
    myClass mc;
    return mc;
}
 
int main()
{
    myClass result = fun();
}

Output:

construct
move consturct
desctruct
desctruct

2. The binding value type is a value type

The return value type is a value type, and it returns a reference to a local variable:

First, this is not a good practice, and attempting to return a reference result of a local variable is usually not satisfactory.

After the function call is finished, the function frame is recycled (actually just setting the address at the top of the stack), and the temporary variables in the function stack are no longer meaningful.

However, doing this usually does not cause any side effects, because during the function return stage, the copy constructor will be called (note that it will not be called even if the move constructor is defined), and the generated new temporary object will be bound to the bound value.

example:

myClass fun()
{
    myClass mc;
    myClass&amp; mcRef = mc;
        //If you want to return a reference to this local variable and modify it outside the function, you will inevitably be disappointed.    return mcRef;
}
 
int main()
{
    myClass result = fun();
}

Output:

construct
copy construct
desctruct
desctruct

3. The binding value type is a value type

The return value type is a value type, and it returns a reference to the global variable:

Similar to the case in 2, but since the life cycle of the global variable exceeds the current function, the variable still survives even if the function returns. During the function return stage, the copy constructor will still be called, and the temporary object will be bound to the bound value, but the global object will still not be returned.

example:

Generally, it is more common to return references passed in as parameters. At this time, the parameters are similar to global variables for functions, that is, the life cycle of the variable will not end when the function returns.

myClass fun(myClass&amp; param)
{
        //In the end, only the copy of param can be obtained    return param;
}
 
int main()
{
    myClass mc;
    myClass result = fun(mc);
}

Output:

construct
copy construct
desctruct
desctruct

4. The binding value type is a value type

The return value type is a reference type, and it returns a reference to a local variable or a local variable:

Similar to the case of case 2, it is usually impossible to obtain satisfactory results if you want to return local variables or references to local variables in the form of reference; but unlike the case of case 2, case 2 basically does not produce any side effects. In case 2, the object is copied in the function return stage, and the object is complete at this time; but in this case, serious side effects will occur.

Since the return value type is a reference type, the copy constructor will not be called in the function return stage. In the binding stage, the binding value is a value type, which will cause the copy constructor to be called. At this time, the function has returned, and the copy function copies the temporary variable in the function. At this time, it has been deconstructed. After copying, it will only get a copy of the destructor object. Usually, this will cause serious errors.

Simply put, in this case, the temporary object will be destructed first and the temporary object will be copied.

example:

myClass&amp; fun()
{
    myClass mc;
    //or myClass mcRef = mc; return mcRef    return mc;
}
 
int main()
{
    myClass result = fun();
}

Output:

construct
desctruct
copy construct
desctruct

5. The binding value type is a value type

The return value type is a reference type, and it returns a reference to the global variable:

The basic situation is similar to 2, that is, it cannot really refer to the global variable, but it will not have any side effects (unless the caller just hopes or changes the global variable). Since global variables are not destructed after the end of the function, it is safe to call the copy constructor on it. Similar to Case 4, the call to copy functions does not occur in the function return stage, but in the binding stage.

example:

Examples of functions that use parameters as reference types:

myClass& fun(myClass& param)
{
    return param;
}
 
int main()
{
    myClass mc;
    myClass result = fun(mc);
}

Output:

construct
copy construct
desctruct
desctruct

The situation in the above 5 is when the return value is bound to the value type. In all cases, calls to copy or move the constructor will occur. After the final binding, it is impossible to get the original object. Attempts to modify the original object in this way will inevitably fail.

If the bound value type is a reference type and the return value type is a value type, and the return value type is a local variable, what will happen if the reference or global variable is a local variable?

In this case, the compiler will report an error. Since the function returns a value type, it will inevitably produce a moving constructor or a copy constructor, resulting in a temporary object. The temporary object is an rvalue. The reference we often call is actually an lvalue reference. An rvalue object cannot be bound to an lvalue reference, so the compiler will report an error.

We can bind the return version of the function to an rvalue reference variable (using myClass&&). Let's discuss the situation where the rvalue reference is returned below. However, sometimes returning an rvalue reference and using it may not produce the desired result (as in the last example of this article).

6. The binding value type is an rvalue reference

The return value type is a value type, and it returns a reference to a local variable or a reference to a global variable:

The function returns the stage. Since it is a return value type, it will call the move constructor or copy constructor to create an unnamed temporary variable (actually it is an rvalue), which can be bound to an rvalue reference.

However, the modification to it is meaningless. The unnamed temporary variable created by the function will be destructed after the function returns. Since we add a reference to it, the life cycle of this temporary variable is extended until the reference to it leaves the scope, it will not be destructed.

example:

myClass fun()
{
    myClass mc;
    return mc;
}
int main()
{
    myClass&& result = func();
}

Output:

construct
move consturct
desctruct
desctruct 

myClass fun()
{
    myClass mc;
    myClass& mcRef = mc;
    return mcRef;
}
int main()
{
    myClass&& result = fun();
}

Output:

construct
copy construct
desctruct
desctruct

7. The binding value type is a reference type

The return value type is a reference type, which returns a reference to a local variable or a local variable:

Since the function return stage and the binding stage are both reference types, no copy or move the constructor call will be generated. The final binding value class is the local variable of the function. However, since the local variable has been destructed after the function returns, it is meaningless to save the reference of this local variable, which often causes some strange errors.

example:

myClass&amp; fun()
{
    myClass mc;
    //or myClass& mcRef = mc; return mcRef;    return mc;
}
 
int main()
{
        //After the function returns, we can even use this result, but the compiler only continues to interpret the original result position as the result variable.        //It is very likely that this position will be overwritten by a new stack frame soon. If you continue to use result, you may have a "strange" problem.    myClass&amp; result = fun();
}

Output:

construct
desctruct

8. The binding value type is a reference type

The return value type is a reference type, and returns a reference to the global variable:

Usually, when we are our true intention, there is no constructor call in the function in the return stage and the binding stage. The final binding value is the global variable that needs to be returned. Any modification to the final binding value will be reflected on the global variable.

example:

Still use parameters as reference type functions as examples.

myClass& fun(myClass& mc)
{
    return mc;
}
int main()
{
    myClass mc;
    myClass& result = fun(mc);
}

Output:

construct
desctruct

9. A thought

Rvalues ​​cannot be bound to lvalue references, but what happens when the rvalue is paid to the lvalue?

as follows

int main()
{
    myClass mc;
    myClass result = move(mc);
}

The answer is that the move constructor will be called to create a new object and bound to the bound value.

Output:

construct
move consturct
desctruct
desctruct

Another very similar example:

int main()
{
    myClass mc;
    myClass&& mcRightRef = move(mc);
    myClass result = mcRightRef;
}

Very similar to the first one, but the generation of result is done by calling the copy constructor rather than moving the constructor.

Output:

construct
copy construct
desctruct
desctruct

This involves the timing of moving constructor calls. More detailed research on rvalues, rvalue references and moving constructors will be continued.

The above is personal experience. I hope you can give you a reference and I hope you can support me more.