Is it possible/legal to return reference to input temporary arguments which are passed by reference

StackOverflow https://stackoverflow.com/questions/22254181

  •  11-06-2023
  •  | 
  •  

My question is : is it legal to return a reference to an input variable which is passed by reference. I borrow the example from C++: Life span of temporary arguments? and return by rvalue reference

#include <iostream>

#include <string>

class MatrixClass
{
public:
int m_value;

std::string m_str;

MatrixClass(int a)
{
    m_value = a;
    std::cout << "hello: " << m_value << '\n';
}

MatrixClass(const MatrixClass& A)
{
    m_value = A.m_value;

    std::cout << "hello: " << m_value << '\n';

    if (A.m_str == "temp_in_*")
    {
        std::cout << "string: " << "copied from temp_in_*" << '\n';
    }

}

void operator=(const MatrixClass& A)
{
    m_value = A.m_value;

    std::cout << "hello: " << m_value << '\n';

    if (A.m_str == "temp_in_*")
    {
        std::cout << "string: " << "copied from temp_in_*" << '\n';
    }
}

~MatrixClass()
{
    std::cout << "bye: " << m_value << '\n';

    if (m_str == "temp_in_*")
    {
        std::cout << "string: " << "temp_in_*" << '\n';
    }
}

};

MatrixClass& operator+(MatrixClass& tempClassA, MatrixClass& tempClassB)
{
    tempClassA.m_value += tempClassB.m_value;
    return tempClassA;
}

MatrixClass operator*(MatrixClass& tempClassA, MatrixClass& tempClassB)
{
    MatrixClass Temp(101010);
    Temp.m_value = tempClassA.m_value * tempClassB.m_value;

    Temp.m_str = "temp_in_*";

    return Temp;
}

int main()
{
    MatrixClass A1(2);
    MatrixClass A2(3);
    MatrixClass A3(10);
    MatrixClass A4(11);
    MatrixClass A5(12);

    std::cout << "start of expression " << '\n';

    MatrixClass A6(0);

    A6 = A1 * A2 + A3 * A4 + A5 * A6;

    std::cout << "end of expression " << '\n';

    std::cout << "A6.m_value: " << A6.m_value << '\n';

    std::system("pause");
}

the operator return a reference to its input which is a temporary variable, and pass it to another operator: operator*(A1, A2) return a temporary variable, also operator*(A3, A4), operator*(A5, A6)

Is there any problem with the lifetime of the temporary variables ? I am developing a Matrix class.

What happens if the expression is more complicated, such as:

    (A+B*C)*((A*B + C)*A)

A general question is (take from return by rvalue reference)

is this possible:

change

A compute(A&& v)
{
  (do something to v) 

  return v;
}

to

A& compute(A& v)
{
  (do something to v) 
  return v;
}
有帮助吗?

解决方案

Yes, it is legal for a function to return references to input parameters, in the sense that it will compile and there are many uses where it will work without problems. The lifetime of any temporaries created in an expression is the lifetime of the full expression or statement, so as long as the reference is not used beyond the expression, the usage should work fine. It is somewhat risky, though, because the caller may not be aware of the reference propagation the function does, and the special rules for extension of temporary lifetimes don't generally apply when the reference is passed through an intermediary function.

Your examples involve modifying and returning a reference to non-const lvalue instance. These types of uses, in general, will be harder to run into the pitfalls than references to const or references to rvalues. A reference to non-const lvalue can't be bound to a temporary (at least not without going through some hoops to trick the compiler), so you will generally have to pass an actual l-value (non-temporary) named variable (or other long-lived object) into them. When that reference is then passed out of the function as a return value, it will refer to whatever long-lived object was passed in. (You can still get into trouble if you don't properly manage lifetimes, of course, but at least the lifetimes we're talking about in this case are generally more than a single statement/expression.)

If you pass rvalue references through your function, and especially if they get translated to a non-const lvalue somewhere down the expression tree (which is somewhat easy to do since the language, as a safety feature, decays rvalues into lvalues whenever they're bound to a name), the temporary nature of the reference can be lost and it is easier to accidentally bind the reference to a long-lived reference, which would outlive the temporary that it is bound to (which generally won't live beyond the statement/full-expression in which it is created). This is why I generally favor returning (and usually passing) r-values by value rather than by reference. Then, the compiler is more aware of the lifetime issues and the usages are generally more foolproof. In many cases, the compiler can elide the move constructions anyway, and when it can't, moves are generally cheap.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top