C++ Return Value Created Before or After Auto Var Destruction

C++ return value created before or after auto var destruction?

Yes, the auto variable will remain in scope until after the return is finished. This is especially true if you are using a compiler that optimizes the return, eg:

Gift get() 
{
Lock lock(mutex);
return gift;
}

Gift g = basket.get();

Which would be equivilent to this sequence:

Gift g;
Lock lock(mutex);
g = Gift(gift);
lock.~Lock();

May be optimized to act more like this:

void get(Gift &ret) 
{
Lock lock(mutex);
ret = gift;
}

Gift g;
basket.get(g);

Which would be equivilent to this sequence:

Gift g;
Lock lock(mutex);
g = gift;
lock.~Lock();

In other words, a temporary can be removed during the return.

in C++ which happens first, the copy of a return object or local object's destructors?

For previous standards (here I will use C++ 03), the closest the standard comes to declaring the sequence of operations in a return is from 6.6

6.6 Jump statements


  1. On exit from a scope (however accomplished), destructors (12.4) are called for all constructed objects with automatic storage duration (3.7.2) (named objects or temporaries) that are declared in that scope, in the
    reverse order of their declaration. Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of variables with automatic storage duration that are in scope at the point transferred from...

The return statement must complete in order to exit the [function] scope, implying that the copy-initialization must also complete. This order is not explicit. Various other quotes from 3.7.2 and 12.8 concisely state the same as above without providing explicit order. Working revisions (after Nov. 2014) include the quote below to address that. The defect report clarifies the change.

From the current working draft (N4527) of the standard as seen on the date of this question

6.6.3 The Return Statement


  1. The copy-initialization of the returned entity is sequenced before the destruction of temporaries at the end
    of the full-expression established by the operand of the return statement, which, in turn, is sequenced before
    the destruction of local variables (6.6) of the block enclosing the return statement.

Notice that this quote refers directly to 6.6. So I think it is safe to assume that the Mutex object will always be destroyed after the return expression has copy-initialized the return value.

Is a copy-on-return operation executed prior or after lock_guard destructor?

[stmt.return]p3:

The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables of the block enclosing the return statement.

This means that the following happen in order:

  1. The return object is copy-initialized
  2. Any temporaries inside the return statement are destroyed
  3. Local variables are destroyed

So, we can infer that get_a is completely safe.

Sequencing between the destruction of local automatic function variables vs the construction of the return value

[stmt.return]/3:

The copy-initialization of the result of the call is sequenced before
the destruction of temporaries at the end of the full-expression
established by the operand of the return statement, which, in turn, is
sequenced before the destruction of local variables ([stmt.jump]) of
the block enclosing the return statement.

Which will be executed first, RAII or function return value

The version 2 is correct as well (in fact, that is better than the first version!).

The value is copied first before mutex is released through the destructor of the local object. The opposite is not possible because the local object gets destroyed when it goes out of scope, but you must note that the return statement must be executed in the scope, so it must happen before destruction. Conversely, the return statement cannot be executed after the local object goes out of scope.

From call-stack point of view, the local objects are destroyed when the stack starts unwinding, but the function including the return statement is executed long before stack-unwinding. That ensures m_name is copied much before the releasing the mutex.

Or think of this simple code:

std::string f()
{
std::string s = "Nawaz";
return s; //Think of this line!
}

Is s copied1 after its destruction? Is that even possible? Wouldn't it make programming in C++ impossible if s is copied after its destruction?

1. Or better say, moved. :-)

C++ destructor with return

No, you can't prevent the object from being destroyed by return statement, it just means the execution of the dtor's body will end at that point. After that it still will be destroyed (including its members and bases), and the memory still will be deallocated.

You migth throw exception.

Class2::~Class2() noexcept(false) {
if (status != FINISHED) throw some_exception();
}

Class1::~Class1() {
myClass2->status = FINISHED;
try {
delete myClass2;
} catch (some_exception& e) {
// what should we do now?
}
}

Note it's a terrible idea indeed. You'd better to reconsider the design, I'm sure there must be a better one. Throwing exception won't stop the destruction of its bases and members, just make it possible to get the process result of Class2's dtor. And what could be done with it is still not clear.

Returning a pointer to an automatic variable

It won't cause a memory leak. It'll cause a dangling reference. The local variable is allocated on the stack and will be freed as soon as it goes out of scope. As a result, when the function ends, the pointer you are returning no longer points to a memory you own. This is not a memory leak (memory leak is when you allocate some memory and don't free it).

[Update]:
To be able to return an array allocated in a function, you should allocate it outside stack (e.g. in the heap) like:

char *test() {
char* arr = malloc(100);
arr[0] = 'M';
return arr;
}

Now, if you don't free the memory in the calling function after you finished using it, you'll have a memory leak.

Scope and return values in C++

When the function terminates, the
following steps happen:

  • The function’s return value is
    copied into the placeholder that was
    put on the stack for this purpose.

  • Everything after the stack frame
    pointer is popped off. This destroys
    all local variables and arguments.

  • The return value is popped off the
    stack and is assigned as the value
    of the function. If the value of the
    function isn’t assigned to anything,
    no assignment takes place, and the
    value is lost.

  • The address of the next instruction
    to execute is popped off the stack,
    and the CPU resumes execution at
    that instruction.

The stack and the heap

Who copies the return value of a function?

The return value is copied out before the local variables go out of scope. The copy/move might be to a temporary location (stack or register(s)) or directly to the caller's own buffer or preferred registers - that's an optimisation/inlining issue.

Where a temporary location's involved the compiler must arrange some division of work between the caller and callee, and there are a number of OS- and binary object/executable-format-specific conventions for return values (and function parameters of course), such that libraries/objects compiled with one compiler can typically still be used with another.

Would the line...

auto item = q.pop();

...be strongly exception safe?

Assuming pop_front() can't throw, the interesting case is where a temporary location is returned, from which the value is again copied into the caller buffer after the function's returned. It would seem to me that you haven't protected adequately against that. Elision (the callee directly constructing the return value in the caller's result buffer/register(s)) is permitted but not required.

To explore this, I've written the following code:

#include <iostream>

struct X
{
X() { std::cout << "X::X(this " << (void*)this << ")\n"; }
X(const X& rhs) { std::cout << "X::X(const X&, " << (void*)&rhs
<< ", this " << (void*)this << ")\n"; }
~X() { std::cout << "X::~X(this " << (void*)this << ")\n"; }

X& operator=(const X& rhs)
{ std::cout << "X::operator=(const X& " << (void*)&rhs
<< ", this " << (void*)this << ")\n"; return *this; }
};

struct Y
{
Y() { std::cout << "Y::Y(this " << (void*)this << ")\n"; }
~Y() { std::cout << "Y::~Y(this " << (void*)this << ")\n"; }
};

X f()
{
Y y;
std::cout << "f() creating an X...\n";
X x;
std::cout << "f() return x...\n";
return x;
};

int main()
{
std::cout << "creating X in main...\n";
X x;
std::cout << "x = f(); main...\n";
x = f();
}

Compiling with g++ -fno-elide-constructors, my output (with extra comments) was:

creating X in main...
X::X(this 0x22cd50)
x = f(); main...
Y::Y(this 0x22cc90)
f() creating an X...
X::X(this 0x22cc80)
f() return x...
X::X(const X&, 0x22cc80, this 0x22cd40) // copy-construct temporary
X::~X(this 0x22cc80) // f-local x leaves scope
Y::~Y(this 0x22cc90)
X::operator=(const X& 0x22cd40, this 0x22cd50) // from temporary to main's x
X::~X(this 0x22cd40)
X::~X(this 0x22cd50)

Clearly, the assignment happened after f() left scope: any exception therefrom would be after your scope guard (here represented by Y) had been destroyed.

The same kind of thing happens if main contains X x = f(); or X x(f());, except it's the copy constructor that's invoked after destruction of the f()-local variables.

(I appreciate that one compiler's behaviour is sometimes a poor basis for reasoning about whether something is required by the Standard to work, but it's considerably more reliable the other way around: when it doesn't work either that compiler's broken - which is relatively rare - or the Standard doesn't require it. Here, the compiler behaviour's just used to add anecdotal weight to my impression of the Standard's requirements.)

Fiddly details for the curious: not that it's typically useful to have code that can only be called in one way, but something that might be safe is const X& x = f();, as the const reference extends the lifetime of the temporary, but I can't convince myself that the Standard requires to have the temporary whose lifetime's extended be the temporary the function copied into sans any additional copy; for what little it's worth - it "worked" in my program and interestingly the temporary occupies the same stack location used if eliding a return value, which suggests f() code is effectively compiled with an ability to elide and the -f-no-elide-constructors option is not so much disabling an optimisation as going out of its way to add a pessimisation: leaving additional stack space for a temporary before calling the function then adding the extra code to copy therefrom and destruct the temporary then readjust the stack pointer....



Related Topics



Leave a reply



Submit