Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > How Methods Return Objects

Reply
Thread Tools

How Methods Return Objects

 
 
JKop
Guest
Posts: n/a
 
      07-30-2004
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:oint(int x, int y) :
> _x(x),
> _y(y) { }
>
> Point:oint(const Point& p) {
> _x = p._x;
> _y = p._y;
> }
>
> Point& Point:perator=(const Point& p) {
> _x = p._x;
> _y = p._y;
> return (*this);
> }
>
> Point Point: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

 
Reply With Quote
 
 
 
 
John Harrison
Guest
Posts: n/a
 
      07-30-2004

"Sharad Kala" <> wrote in message
news:...
>
> "John Harrison" <> wrote in message
> newspsbxo1yjt212331@andronicus...
> > On Fri, 30 Jul 2004 03:18:57 GMT, cppaddict <> wrote:
> >

>
> > 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!)"


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


 
Reply With Quote
 
 
 
 
cppaddict
Guest
Posts: n/a
 
      07-30-2004
JKop,

Thanks for that discussion... very interesting.

cpp
 
Reply With Quote
 
Niels Dekker (no reply address)
Guest
Posts: n/a
 
      07-30-2004
cppaddict wrote:
>
> Point Point: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
 
Reply With Quote
 
Richard Herring
Guest
Posts: n/a
 
      07-30-2004
In message <>, "Niels Dekker (no reply
address)" <> writes
>cppaddict wrote:
>>
>> Point Point: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?


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


--
Richard Herring
 
Reply With Quote
 
Niels Dekker (no reply address)
Guest
Posts: n/a
 
      07-30-2004
cppaddict wrote:
>
> Point Point:perator+(const Point& p) const {
> Point ret(_x + p._x, _y + p._y);
> return ret;
> }


I wrote:
>
> I would prefer having binary operators as non-member functions [...]



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



So I guess you mean like this:

Point& Point: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
 
Reply With Quote
 
Richard Herring
Guest
Posts: n/a
 
      07-30-2004
In message <>, "Niels Dekker (no reply
address)" <> writes
>cppaddict wrote:
>>
>> Point Point:perator+(const Point& p) const {
>> Point ret(_x + p._x, _y + p._y);
>> return ret;
>> }

>
>I wrote:
>>
>> I would prefer having binary operators as non-member functions [...]

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

>
>
>So I guess you mean like this:
>
>Point& Point: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.).

--
Richard Herring
 
Reply With Quote
 
Victor Bazarov
Guest
Posts: n/a
 
      07-30-2004
Richard Herring wrote:
> In message <>, "Niels Dekker (no reply
> [...]
>> 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.).


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
 
Reply With Quote
 
Denis Remezov
Guest
Posts: n/a
 
      07-30-2004
cppaddict wrote:
>
> 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
 
Reply With Quote
 
Rob Williscroft
Guest
Posts: n/a
 
      07-30-2004
Victor Bazarov wrote in news:VZsOc.248$191.84
@newsread1.dllstx09.us.to.verio.net in comp.lang.c++:

> Richard Herring wrote:
>> In message <>, "Niels Dekker (no reply
>> [...]
>>> 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.).

>
> 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.
--
http://www.victim-prime.dsl.pipex.com/
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Re: How include a large array? Edward A. Falk C Programming 1 04-04-2013 08:07 PM
Designing superclasses so inherited methods return objects with sametype as the instance. Felix T. Python 1 11-19-2008 10:05 PM
Is there a way to find the class methods of a class, just like'methods' finds the instance methods? Kenneth McDonald Ruby 5 09-26-2008 03:09 PM
class objects, method objects, function objects 7stud Python 11 03-20-2007 06:05 PM
what value does lack of return or empty "return;" return Greenhorn C Programming 15 03-06-2005 08:19 PM



Advertisments