How Methods Return Objects

C

cppaddict

I'd like to know what goes on under the hood when methods return
objects. Eg, I have a simple Point class with two members _x and _y.
It's constructor, copy constructor, assignment operator and additon
operator (which returns another Point object, and which my question is
about) are as follows:

Point::point(int x, int y) :
_x(x),
_y(y) { }

Point::point(const Point& p) {
_x = p._x;
_y = p._y;
}

Point& Point::eek:perator=(const Point& p) {
_x = p._x;
_y = p._y;
return (*this);
}

Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}

What happens when operator+ returns the temporary Point object that it
creates?

Does it use the copy constructor to create a copy of the temporary
variable "ret"? Is ret destroyed after that?

If not, how does it return a Point object?

Also, why is that "new" never comes into play? How can the compiler
know beforehand how many Point objects will be created via operator+?
Is it because such objects must exist in statements like:

Point p3 = p1 + p2;

I know this is a simple point, but I'm confused nonetheless.

Thanks for any clarification,
cpp
 
V

Victor Bazarov

cppaddict said:
I'd like to know what goes on under the hood when methods return
objects. Eg, I have a simple Point class with two members _x and _y.
It's constructor, copy constructor, assignment operator and additon
operator (which returns another Point object, and which my question is
about) are as follows:

Point::point(int x, int y) :
_x(x),
_y(y) { }

Point::point(const Point& p) {
_x = p._x;
_y = p._y;

I wonder why you didn't use initialisation here...
}

Point& Point::eek:perator=(const Point& p) {
_x = p._x;
_y = p._y;
return (*this);
}

Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}

What happens when operator+ returns the temporary Point object that it
creates?

Does it use the copy constructor to create a copy of the temporary
variable "ret"? Is ret destroyed after that?

Generally speaking, yes. Since 'ret' is an automatic variable, it will
be destroyed right after the function ends (right after the 'return'
statement). In order for the Point object to exist after the function
has finished but before its value is used elsewhere, a temporary one
is created using the copy constructor.
If not, how does it return a Point object?

Also, why is that "new" never comes into play?

How do you know it doesn't? Creation of a temporary _might_ be internally
done using some kind of special form of 'new'. It's not defined, AFAIK.
Where the temporary is created and how is, well, unimportant. At least
from the langauge standpoint.
How can the compiler
know beforehand how many Point objects will be created via operator+?

Every time it has to call operator+ function, another Point object is
potentially created.
Is it because such objects must exist in statements like:

Point p3 = p1 + p2;

The compiler is allowed to forgo creation of a temporary in this case
and instead generate code so that when you return the 'ret' from the
function operator+, it is used to directly initialise the 'p3' object.

You might find all that in good books. Or by looking at the code (yes,
it's usually assembly language) that the compiler generates from your
C++ source.

Victor
 
M

Mark A. Gibbs

it is unwise to use a leading underscore in a variable name, although in
this case, not technically illegal (17.4.3.1.2).

mark
 
C

cppaddict

it is unwise to use a leading underscore in a variable name, although in
this case, not technically illegal (17.4.3.1.2).

mark

I was under the (mistaken) impression that this was a standard way to
indicate member variables. Do you know what leading underscores are
reserved for? Also, what is the standard way to indicate a member
variable -- trailing underscore?

Thanks,
cpp
 
D

Denis Remezov

John said:
Its a good way to indicate member variables. I don't understand why some
people claim its dangerous (I am aware of when underscore use is illegal).
Perhaps Mark could justify his claim?

john

Some say that it's hard to remember the exact rules (never use a leading
underscore followed by an uppercase letter; never use a double underscore;
in the global scope, don't use a leading underscore regardless of the
symbol that follows). There is some potential for an error here,
especially due to code maintenance by those who don't read too much
into language details.
This potential is arguably smaller when using a trailing underscore
instead.

I don't mind it either way (and much prefer them over any alternative),
but in my limited scope the trailing underscore has become more
conventional. I've got used to it and love it.

Denis
 
V

Victor Bazarov

cppaddict said:
I was under the (mistaken) impression that this was a standard way to
indicate member variables. Do you know what leading underscores are
reserved for? Also, what is the standard way to indicate a member
variable -- trailing underscore?

There is no "standard way" to indicate anything. The Standard prohibits
the use of reserved [by the implementation] names. Such names include
names with double underscores and names that begin with an underscore and
a capital letter.

I am, of course, picking at your use of the word "standard" here. Not
that I have ill will, but traditionally "standard" here means required
or defined by the Standard Document. You might want to use the word
"conventional", as in "what is the conventional way to indicate..."

As to the accepted conventions, there are several. The one I see used
the most is the prefix 'm_' for non-static data members and 's_' for
static data members. Leading underscores, just like trailing underscores,
are not easy to read, that's why most people try to avoid them, I guess.

Victor
 
J

John Harrison

I was under the (mistaken) impression that this was a standard way to
indicate member variables. Do you know what leading underscores are
reserved for? Also, what is the standard way to indicate a member
variable -- trailing underscore?

Thanks,
cpp

Its a good way to indicate member variables. I don't understand why some
people claim its dangerous (I am aware of when underscore use is illegal).
Perhaps Mark could justify his claim?

john
 
J

John Harrison

Denis Remezov said:
Some say that it's hard to remember the exact rules (never use a leading
underscore followed by an uppercase letter; never use a double underscore;
in the global scope, don't use a leading underscore regardless of the
symbol that follows). There is some potential for an error here,
especially due to code maintenance by those who don't read too much
into language details.
This potential is arguably smaller when using a trailing underscore
instead.

I don't mind it either way (and much prefer them over any alternative),
but in my limited scope the trailing underscore has become more
conventional. I've got used to it and love it.

Denis

I guess I prefer a leading underscore because its the first thing you see
when you read a name. I guess the same reason you often see m_something but
never something_m.

But yes, beauty is in the eye of the beholder.

john
 
S

Sharad Kala

Its a good way to indicate member variables. I don't understand why some
people claim its dangerous (I am aware of when underscore use is illegal).
Perhaps Mark could justify his claim?

Because they are used by the compiler implementations too.
To quote Herb Sutter here - "Try to avoid names with leading underscores.
Yes, I've habitually used them, and yes, popular books like "Design
Patterns" (Gamma et al) do use it... but the standard reserves some
leading-underscore identifiers for the implementation and the rules are hard
enough to remember (for you and for compiler writers!) that you might as
well avoid this in new code. (Since I'm no longer allowed to use leading
underscores as my "member variable" tag, I'll now use trailing
underscores!)"

This simple program on MS VC 7.0 cribs for ambiguous symbol -

#include <map>
using namespace std;
class _Tree{
};

int main()
{
_Tree t;
}

-Sharad
 
J

JKop

cppaddict posted:
I'd like to know what goes on under the hood when methods return
objects. Eg, I have a simple Point class with two members _x and _y.
It's constructor, copy constructor, assignment operator and additon
operator (which returns another Point object, and which my question is
about) are as follows:

Point::point(int x, int y) :
_x(x),
_y(y) { }

Point::point(const Point& p) {
_x = p._x;
_y = p._y;
}

Point& Point::eek:perator=(const Point& p) {
_x = p._x;
_y = p._y;
return (*this);
}

Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}

What happens when operator+ returns the temporary Point object that it
creates?

Does it use the copy constructor to create a copy of the temporary
variable "ret"? Is ret destroyed after that?

If not, how does it return a Point object?

Also, why is that "new" never comes into play? How can the compiler
know beforehand how many Point objects will be created via operator+?
Is it because such objects must exist in statements like:

Point p3 = p1 + p2;

I know this is a simple point, but I'm confused nonetheless.

Thanks for any clarification,
cpp


int Blah()
{
return 72;
}

int main()
{
int f = 45 + Blah();
}


When the end of Blah is reached, a temporary is created and this temporary
is given back to main. The temporary lives in main up until the next
semicolon, at which point it's destroyed.

Now, here's one that may result in an optimization:

int Blah()
{
int f = 76;

f *= 2;

f -= 4;

return f;
}

The compiler may choose to return a temporary, which has been copy-
initialized from "f", or it may return "f" itself. If it returns "f" itself,
then there's one less object created, and that's the optimization.

And another optimization:

int Blah()
{
int f = 76;

f *= 2;

f -= 4;

return f;
}

int main()
{
int x = Blah();
}

That x variable in main may be used in Blah, as no temporary is necessary.


And then there's functions that return references...

int k; //global

int& Blah()
{
k = 6783;

k *= 2;

k -= 64;

return k;
}

Here Blah is returning a reference to a global variable, so the global
variable will still exist after the end of Blah, and so it can be used in
main().

If, on the other hand, you do this:

int& Blah()
{
int k = 72;

return k;
}

The Blah function will in fact return a reference to k, but by the time you
get to use it in main, k no longer exists. Compilers warn whenever you
return a reference to a local variable.


And then there's binding to a const-reference:

int Blah()
{
int k = 72;

k +=4;

k *= 2;

return k;
}

int main()
{
const int& monkey = Blah();

//Here, the temporary returned from Blah (or its local variable)
//is directly bound to monkey
//The temporary does NOT get destroyed at the
//next semicolon, but at the end of the reference's
//scope, ie. the end of main
}


You may ask why one would bother binding to a const reference when they can
just do:

int main()
{
int monkey = Blah();
}

and rely on the optimization.

Well, there needn't be an optimization depending on the compiler, and so
monkey may be copy-constructed from the temporary returned by Blah(). Plus,
if Blah returns a const object, then "int monkey" won't optimize, because a
copy will have to be made to yield a non-const version. On the other hand
you could do "const int monkey".

Well anyway, take this code:

int Blah()
{
int k = 42;

k *= 2;

k += 4;

return k;
}

int main()
{
int monkey = Blah();
}


How many "int"s are made? Either 1, 2 or 3. The best compilers will only use
one int, the monkey from main. The monkey will be used in Blah, and then
when Blah finishes, a temporary *won't* be made, it'll just return its local
variable, which is monkey from main!

Another compiler may make 2 "int"s: the monkey in main, and the k in Blah.
The Blah function will return k directly.

And then ofcourse there's compilers that'll make 3 "int"s:

The monkey from main
k from Blah
The temporary returned from Blah

There's times when a function has no choice but to create a temporary to
return, eg.

int Blah()
{
int k = 7;

return k + 4;
}

But if you return the variable directly, and there's no constness problems,
then there's no problems with the actual local variable being returned.

All of these are just optimizations, and needn't be performed.

(Another even better compiler may turn the above function into:

int Blah()
{
int k = 7;

return k += 4;
}

And return "k" itself, as it can see that there's no consequences of adding
the 4 to k directly. One less temporary.


-JKop
 
J

John Harrison

Sharad Kala said:
Because they are used by the compiler implementations too.
To quote Herb Sutter here - "Try to avoid names with leading underscores.
Yes, I've habitually used them, and yes, popular books like "Design
Patterns" (Gamma et al) do use it... but the standard reserves some
leading-underscore identifiers for the implementation and the rules are hard
enough to remember (for you and for compiler writers!) that you might as
well avoid this in new code. (Since I'm no longer allowed to use leading
underscores as my "member variable" tag, I'll now use trailing
underscores!)"

I don't find the rule, 'use a leading underscore followed by a lower case
letter for member variables' hard to remember. I would always use a leading
lower case letter in any case even if I wasn't using a leading underscore. I
don't think the argument that you 'might as well' avoid it in new code very
convincing. Why exactly? No bad things are going to happen that I can see.
This simple program on MS VC 7.0 cribs for ambiguous symbol -

#include <map>
using namespace std;
class _Tree{
};

int main()
{
_Tree t;
}

-Sharad

That differs from what I would do in two different ways. First it is
underscore followed by an uppercase letter and second I am only talking
about member variable names.

john
 
N

Niels Dekker (no reply address)

cppaddict said:
Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}

I would prefer having binary operators as non-member functions, like
this:

Point operator+(const Point& p1, const Point& p2) {
Point ret(p1._x + p2._x, p1._y + p2._y);
return ret;
}


What do you think?

Niels Dekker
www.xs4all.nl/~nd/dekkerware
 
R

Richard Herring

"Niels Dekker (no reply said:
I would prefer having binary operators as non-member functions, like
this:

Point operator+(const Point& p1, const Point& p2) {
Point ret(p1._x + p2._x, p1._y + p2._y);
return ret;
}


What do you think?

I think I'd implement non-member operator+() in terms of member
operator+=().
 
N

Niels Dekker (no reply address)

cppaddict said:
Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}
I would prefer having binary operators as non-member functions [...]


Richard said:
I think I'd implement non-member operator+() in terms of member
operator+=().


So I guess you mean like this:

Point& Point::eek:perator+=(const Point& p) {
_x += p._x;
_y += p._y;
return *this;
}

Point operator+(const Point& p1, const Point& p2) {
return Point(p1) += p2;
}


Would you write other binary operators, like operator!=, as non-members
as well? In what situation would you prefer to implement a binary
operator as a member function?

Thanks in advance,

Niels Dekker
www.xs4all.nl/~nd/dekkerware
 
R

Richard Herring

"Niels Dekker (no reply said:
cppaddict said:
Point Point::eek:perator+(const Point& p) const {
Point ret(_x + p._x, _y + p._y);
return ret;
}
I would prefer having binary operators as non-member functions [...]


Richard said:
I think I'd implement non-member operator+() in terms of member
operator+=().


So I guess you mean like this:

Point& Point::eek:perator+=(const Point& p) {
_x += p._x;
_y += p._y;
return *this;
}

Point operator+(const Point& p1, const Point& p2) {
return Point(p1) += p2;
}
Exactly.


Would you write other binary operators, like operator!=, as non-members
as well?

Yes, if both their operands are const and of the same type. That way,
you get the same rules for automatic type conversions applied to both
operands. With member functions, the first operand is treated
differently, with the possibility that a op b and b op a produce
different conversions if a and b are of different (but convertible)
types.
In what situation would you prefer to implement a binary
operator as a member function?
If the function modifies its argument (e.g. operator+= above.).
 
V

Victor Bazarov

Richard said:
If the function modifies its argument (e.g. operator+= above.).

Operator += is an assignment operator and it cannot be implemented as
a non-member, no matter whether it in fact modifies its left operand
or not (and it is up to you to decide whether it does modify it).

There are operators that can be made non-members and there are others
that cannot. For example, type conversion operators must be members
because such is the requirement of the language. They most likely do
not modify their operand, however.

V
 
D

Denis Remezov

cppaddict said:
JKop,

Thanks for that discussion... very interesting.

cpp

Replace int with a user-defined type and what JKop wrote will
probably make sense (I haven't checked it closely).

In respect to type int, his story may still be "as-if"-correct
but is actually misleading in a practical sense. Very crudely
put, type int is small, cheap to copy, and is typically subject
to machine treatment not generally available to larger-sized
user-defined types.

For example, more often than not,
int const& f(int const& x) {...}

will be the same speed (if the compiler optimises it) as or a tiny
bit slower (because of indirection) than
int f(int x) {...}

Not that you should worry too much about that, but you can see
for yourself how things work by using your debugger to step
through the assembly code (optimised and unoptimised).

Denis
 
R

Rob Williscroft

Victor Bazarov wrote in @newsread1.dllstx09.us.to.verio.net in comp.lang.c++:
Operator += is an assignment operator and it cannot be implemented as
a non-member, no matter whether it in fact modifies its left operand
or not (and it is up to you to decide whether it does modify it).

#include <iostream>
#include <ostream>

struct X {};

X &operator += ( X &lhs, X const & )
{
std::cout << "Ok" << std::endl;
return lhs;
}

int main()
{
X a, b;
a += b;
}

Works fine for me.
There are operators that can be made non-members and there are others
that cannot. For example, type conversion operators must be members
because such is the requirement of the language. They most likely do
not modify their operand, however.

Also operator ->(), I can't remember any others (*).

*) Ok, new and delete, but I don't think they count :).

Rob.
 

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,057
Latest member
KetoBeezACVGummies

Latest Threads

Top