1

We are creating a class designed to send information out from the current module (the specifics are not relevant to this question). An object of this type is created and populated with a portion of the data that needs to be sent, and then passed into a (different class) member function. That function provides the object with the rest of the data, and then triggers the send, via a call in the object itself. Because the information being passed in is dynamic, the intention is that the information transfer object be a temporary one, created with the latest data. The design we lined out is in the distilled source code below, but gcc/C++ does not allow this, giving the error shown.

The question is, how can we accomplish the intended behavior, using temporary objects (nice for avoiding memory leaks) that can be modified and used by the called function?

gcc compiler error:

infoxfer.cpp: In function ‘int main()’:
infoxfer.cpp:54:43: error: cannot bind non-const lvalue reference of type ‘XferInfo&’ to an rvalue of type ‘XferInfo’
   51 |     callee.doSomething("Something param", XferInfo("from main()"));
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~
infoxfer.cpp:36:62: note:   initializing argument 2 of ‘void Callee::doSomething(const string&, XferInfo&)’
   33 |     void doSomething(const string& somethingParam, XferInfo& xferInfo)
      |                                                    ~~~~~~~~~~^~~~~~~~

The distilled sample code:
infoxfer.cpp:

#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

class XferInfo
{
private:
    const string mCallerInfo;
    string mCalleeInfo;

public:
    XferInfo(const string& callerInfo) : mCallerInfo(callerInfo)
    {}

    void setCalleeInfo(const string& calleeInfo)
    {
        mCalleeInfo = calleeInfo;
    }

    void sendData()
    {
        // simulate sending data
        cout << mCallerInfo << " | " << mCalleeInfo << endl;
    }
};

class Callee
{
public:
    void doSomething(const string& somethingParam, XferInfo& xferInfo)
    {
        // complete data for xfer
        xferInfo.setCalleeInfo(somethingParam);

        // simulate doing something
        cout << "do something" << endl;

        // send the complete info
        xferInfo.sendData();
    }
};

int main()
{
    cout << "start" << endl;

    Callee callee;
    callee.doSomething("Something param", XferInfo("from main()"));

    cout << "end" << endl;

    return 0;
}
rtillery
  • 367
  • 1
  • 10
  • 4
    Why not simply pass the XferInfo by value? Or use an rvalue reference. It seems an lvalue reference is really not what you want. – jtbandes Oct 23 '20 at 17:19
  • "using temporary objects (nice for avoiding memory leaks)" your seem to be confusing concepts here. Memory leaks are bad, but dangling references are just as bad (or worse). If you copy by value, the object lifetime will be limited to the scope of the variable. No memory leak. – JHBonarius Oct 23 '20 at 17:39
  • Change `callee.doSomething("Something param", XferInfo("from main()"));` to `XferInfo info("from main()"); callee.doSomething("Something param", info);` – Eljay Oct 23 '20 at 17:57
  • The compiler has to create an `XferInfo` objects _somewhere_. Passing it by value wraps that up nicely and avoids other potential problems. – 1201ProgramAlarm Oct 23 '20 at 18:11

1 Answers1

2

As mentioned in the comments, you could simply change your doSomething function to accept an rvalue reference to the passed XferInfo object (using the double &&):

    void doSomething(const string& somethingParam, XferInfo&& xferInfo)
    {
        // complete data for xfer
        xferInfo.setCalleeInfo(somethingParam);
        // ... and so forth ...

From the linked cppreference page:

Rvalue references can be used to extend the lifetimes of temporary objects (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them)

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • OP here: @Adrian OK, this (T&&) is new to me. To be clear, my understanding was that the temporary XferInfo object created in the main() doSomething() call gets created, passed into the function call, and then destroyed on return. But you are saying that if the object were allowed to be passed with a single T&, the compiler would have destroyed the object BEFORE being passed to the function? I have verified that it will survive with a T&&. – rtillery Oct 23 '20 at 18:44
  • @rtillery It's more a case of the compiler conforming to standards. In theory, a reference to a temporary (non-const) object is not safe to pass as an lvalue reference, so the Standard doesn't allow it. But it is possible, of course, that the object *will* survive the function call - just not guaranteed. Using the `&&` (sort of) guarantees that it will. – Adrian Mole Oct 23 '20 at 18:48
  • @rtillery IIRC, you can also have two different definitions (overloads) of `doSomething` - one with an lvalue reference (single `&`) and one with an rvalue reference (double `&&`). The compiler will choose the latter only when it has to. – Adrian Mole Oct 23 '20 at 18:50
  • Is there some disadvantage to defining the member function with the rvalue reference and using it for non-temporary objects? Do I need to have both overloads? – rtillery Oct 23 '20 at 19:11
  • @rtillery Nothing that I can immediately think of, but there may be cases in complex classes, or things like multithreading. – Adrian Mole Oct 23 '20 at 19:14
  • Thank you very much, @Adrian! – rtillery Oct 23 '20 at 19:16
  • 1
    It does appear that using an rvalue reference for the function does have a drawback: Non-temporary objects have to be `std::move()`ed into the function. The only way I can find to avoid this is overloading the function with both rvalue & lvalue reference versions. Luckily, the rvalue reference version just needs to call the lvalue reference version to share code: `void doSomething(const string& somethingParam, XferInfo& xferInfo) {...}`, `void doSomething(const string& somethingParam, XferInfo&& xferInfo) { doSomething(somethingParam, xferInfo); }` – rtillery Oct 23 '20 at 20:06