When exiting function scope, which occurs first...

T

Tim Clacy

When exiting function scope, which occurs first:
a) destruction of local objects
b) copy of value for return

From disassembly of a debug target, it looks like the return value is copied
before local objects are destroyed. Is this standard behaviour? Can the same
behaviour be expected for any optimisation level?

As an example, in the function 'int A::fn()' here, is 'b' destroyed before
'i' is copied or vice-versa?:

int A::fn()
{
B b;

return i;
}

class A
{
int fn();
int i;
};

class B
{
~B();
};


Cheers


Tim
 
V

Victor Bazarov

Tim said:
When exiting function scope, which occurs first:
a) destruction of local objects
b) copy of value for return

From disassembly of a debug target, it looks like the return value is copied
before local objects are destroyed. Is this standard behaviour? Can the same
behaviour be expected for any optimisation level?

I think it's standard, since copying occurs in the 'return' statement,
whereas local objects are destroyed when function exits (you may consider
the closing curly brace as the point of exiting)
As an example, in the function 'int A::fn()' here, is 'b' destroyed before
'i' is copied or vice-versa?:

int A::fn()
{
B b;

return i;
}

class A
{
int fn();
int i;
};

class B
{
~B();
};

Why would it matter, in this particular case?

V
 
R

red floyd

Tim said:
When exiting function scope, which occurs first:
a) destruction of local objects
b) copy of value for return

From disassembly of a debug target, it looks like the return value is copied
before local objects are destroyed. Is this standard behaviour? Can the same
behaviour be expected for any optimisation level?

Think about it, you have a local object that you are returning by value:

class X {
// ...
X() {
// ...
}
~X() {
// non-trivial destructor
}
};

X func()
{
X local_var;
// do stuff with local_var
return local_var;
}

If destructors were called before return values are copied, how could
local_var be copied for return? It would be destroyed before the return
copy is made. Therefore, the copy for return *MUST* be made before
local objects are destroyed.
 
T

Tim Clacy

red said:
Think about it, you have a local object that you are returning by
value:

class X {
// ...
X() {
// ...
}
~X() {
// non-trivial destructor
}
};

X func()
{
X local_var;
// do stuff with local_var
return local_var;
}

If destructors were called before return values are copied, how could
local_var be copied for return? It would be destroyed before the
return
copy is made. Therefore, the copy for return *MUST* be made before
local objects are destroyed.

Red,

Hi. Of course, if you're returning an object by value then the value must be
copied before the object is destroyed. However, that's not quite the same as
I was asking; what if your returning a POD? In this case, there is no such
compelling reason to copy the POD before destroying local objects; my
question is whether behaviour in that situation is specified (can be relied
upon) or not.

Cheers


Tim
 
E

E. Robert Tisdale

Tim said:
When exiting function scope, which occurs first:
a) destruction of local objects
b) copy of value for return
b

From disassembly of a debug target,
it looks like the return value is copied
before local objects are destroyed.
Is this standard behavior?
Yes.

Can the same behavior be expected for any optimisation level?

Yes.

As a [more interesting] example,
in the function 'A A::fn(void) const' here,
is 'b' destroyed before 'a' is copied or vice-versa?:
#ifndef GUARD_A_H
#define GUARD_A_H 1

#include <iostream>

class A {
private:
// representation
int I;
public:
// functions
A fn(void) const;
// operators
friend
std::eek:stream& operator<<(std::eek:stream& os, const A& a) {
return os << a.I;
}
// constructors
A(int i = 0): I(i) {
std::cerr << "A::A(int)" << std::endl;
}
A(const A& a): I(a.I) {
std::cerr << "A::A(const A&)" << std::endl;
}
~A(void) {
std::cerr << "A::~A(void)" << std::endl;
}
};

class B {
public:
// constructors
B(void) {
std::cerr << "B::B(void)" << std::endl;
}
~B(void) {
std::cerr << "B::~B(void)" << std::endl;
}
};

#endif//GUARD_A_H
#include "A.h"

A A::fn(void) const {
A a;
B b;
a.I = I + 13;
return a;
}
cat main.cc
#include "A.h"

int main(int argc, char* argv[]) {
const A a;
const A b = a.fn();
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
return 0;
}
g++ -Wall -ansi -pedantic -o main main.cc A.cc
./main
A::A(int) # a in main(int, char**)
A::A(int) # a in A::fn(void) const
B::B(void) # b in A::fn(void) const
B::~B(void) # b in A::fn(void) const
a = 0 # a in main(int, char**)
b = 13 # b in main(int, char**)
A::~A(void) # b in main(int, char**)
A::~A(void) # a in main(int, char**)

Notice that the copy constructor A::A(const A&) is *never* called!

The compiler emits code to reserve space for b
then passes a reference to b to A::fn(void) as a *hidden* argument.
In A::fn(void) const, the compiler recognizes that
b is just a reference to the return value
and initializes (and modifies) it directly.
(This is the Named Return Value Optimization (NRVO).)
A::~A(void) is *not* called in A::fn(void) const
because no local object of type A was actually created.
 
T

Tim Clacy

E. Robert Tisdale said:
Tim said:
When exiting function scope, which occurs first:
a) destruction of local objects
b) copy of value for return
b

From disassembly of a debug target,
it looks like the return value is copied
before local objects are destroyed.
Is this standard behavior?
Yes.

Can the same behavior be expected for any optimisation level?

Yes.

As a [more interesting] example,
in the function 'A A::fn(void) const' here,
is 'b' destroyed before 'a' is copied or vice-versa?:
#ifndef GUARD_A_H
#define GUARD_A_H 1

#include <iostream>

class A {
private:
// representation
int I;
public:
// functions
A fn(void) const;
// operators
friend
std::eek:stream& operator<<(std::eek:stream& os, const A& a) {
return os << a.I;
}
// constructors
A(int i = 0): I(i) {
std::cerr << "A::A(int)" << std::endl;
}
A(const A& a): I(a.I) {
std::cerr << "A::A(const A&)" << std::endl;
}
~A(void) {
std::cerr << "A::~A(void)" << std::endl;
}
};

class B {
public:
// constructors
B(void) {
std::cerr << "B::B(void)" << std::endl;
}
~B(void) {
std::cerr << "B::~B(void)" << std::endl;
}
};

#endif//GUARD_A_H
#include "A.h"

A A::fn(void) const {
A a;
B b;
a.I = I + 13;
return a;
}
cat main.cc
#include "A.h"

int main(int argc, char* argv[]) {
const A a;
const A b = a.fn();
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
return 0;
}
g++ -Wall -ansi -pedantic -o main main.cc A.cc
./main
A::A(int) # a in main(int, char**)
A::A(int) # a in A::fn(void) const
B::B(void) # b in A::fn(void) const
B::~B(void) # b in A::fn(void) const
a = 0 # a in main(int, char**)
b = 13 # b in main(int, char**)
A::~A(void) # b in main(int, char**)
A::~A(void) # a in main(int, char**)

Notice that the copy constructor A::A(const A&) is *never* called!

The compiler emits code to reserve space for b
then passes a reference to b to A::fn(void) as a *hidden* argument.
In A::fn(void) const, the compiler recognizes that
b is just a reference to the return value
and initializes (and modifies) it directly.
(This is the Named Return Value Optimization (NRVO).)
A::~A(void) is *not* called in A::fn(void) const
because no local object of type A was actually created.

Robert,

Hi. Thanks for the answers; I can sleep easy for a while now.

Regarding the interesting example, you say that "no local object of type A
was created"; if so, what's the "A::A(int) # a in A::fn(void) const"?

Tim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top