Jump to content

  • Log In with Google      Sign In   
  • Create Account


understanding return by value.


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
9 replies to this topic

#1 slicer4ever   Crossbones+   -  Reputation: 3326

Like
0Likes
Like

Posted 04 September 2012 - 08:59 PM

hey good people of gamedev, i've been studying and working in c++ for a good number of years, but their has been a key component of c++ that i've simply shyed away from. Because each attempt i made to research it, and understand it. it left me worried in how to approach situations, so in general i avoided it at all costs, in short, i'm talking about retuning a class object which isn't just a POD type.

for example: http://codepad.org/yvEu6DqW

and just incase you don't like to click away, here's the sample code of the test i made:
#include <iostream>
using namespace std;
class Test{
public:
int m_ID;
Test Make(int ID){
  return Test(ID);
}
Test operator + (Test &o){
  return Test(m_ID+o.m_ID);
}
Test(int id) : m_ID(id){
  cout << "Created Object " << m_ID << endl;
}
~Test(){
  cout << "Destroyed Object " << m_ID << endl;
}
};
int main(int argc, char **argv){
Test A = Test(10);
Test B = A.Make(11);
Test C = A+B;
cout << "Object: "<<A.m_ID<<endl;
cout << "Object: "<<B.m_ID<<endl;
cout << "Object: "<<C.m_ID<<endl;
return 0;
}

essentially, from looking at the output, i read it as this:

Test A is created by declaring it, but then when i set it = to Test(10) it destroys the old object, and create's a new one in it's place.

then, i create B, again B being declared causes the constructor to be called, then immediately the destructor is called, after A.Make is even called, so the destroyed object contains the right value, but this extra code is still being called anyway.

I understand that many compiler's might compile out the return values so instead of storing a temp version to copy into the result, it simply copy's the result directly into the return value's location.

still, it's very confusing, and the memory overhead of what's going on is what scares me into only doing this sort of stuff via passing by reference, and using new/delete instead of ever using stack memory to hold the object.

Edited by slicer4ever, 04 September 2012 - 09:03 PM.

Check out https://www.facebook.com/LiquidGames for some great games made by me on the Playstation Mobile market.

Sponsor:

#2 Endurion   Crossbones+   -  Reputation: 3390

Like
2Likes
Like

Posted 04 September 2012 - 09:45 PM

If you want all the object creations you also need to look at the copy constructor. Currently you didn't define one, so the compiler did.

The signature looks like this:

Test::Test( const Test& rhs )



Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

#3 EddieV223   Members   -  Reputation: 1404

Like
1Likes
Like

Posted 04 September 2012 - 10:03 PM

Return by value is just copying the data. If you have an object that is 4 bytes like a int is. And you have a function
[source lang="cpp"]void SomeFunc(int x){x = x + 1;}[/source]
[source lang="cpp"][/source]

you call the function
[source lang="cpp"]int myint = 50;SomeFunc(myint);[/source]

what do you think the value of myint is now? It's still 50. When you called Somefunc it copied the 4 bytes into the functions parameter, on the stack. Every function data goes onto the stack as stack frames. When the function returns all that data is freed. Even when you write your own classes, the language knows the size of your objects and will just copy them bit for bit. Every time one is copied there is also a constructor call and when these variables are destroyed there is a destructor call as well. The only way to get around this behavior is using pointers. Even though the same thing is happening to the pointer data itself. If you copy a pointer it still has the same address which is the address of some object.

So if you wanted to make changes to myint inside the function you will have to use pointers or references( references use pointers ), to refer back to the variable you're really wanting to modify.

[source lang="cpp"]void SomeFunc(int* x) { *x = *x + 1; }[/source]

In this function body the * is the dereference operator, this is how you access the object the pointer is pointing to.

and call it like
[source lang="cpp"]int myint = 50;SomeFunc(&myint);[/source]

In this snippet we create a pointer by getting the address of myint using the & operator. Since myint is an int this is an int pointer. Which points to the original object.

Your issue is not understanding the stack and heap. Understanding how these two things work in cpp and how they interact is fundamental to this language. Read up on this until you have it, but it really is fundamentally simple. So once it clicks, you'll have it for life.

Edited by EddieV223, 04 September 2012 - 10:07 PM.

If this post or signature was helpful and/or constructive please give rep.

 

// C++ Video tutorials

http://www.youtube.com/watch?v=Wo60USYV9Ik

 

// Easy to learn 2D Game Library c++

SFML2.1 Download http://www.sfml-dev.org/download.php

SFML2.1 Tutorials http://www.sfml-dev.org/tutorials/2.1/

 

// SFML 2 book

http://www.amazon.com/gp/product/1849696845/ref=as_li_ss_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=1849696845&linkCode=as2&tag=gamer2creator-20

 


#4 Murdocki   Members   -  Reputation: 274

Like
1Likes
Like

Posted 05 September 2012 - 02:27 AM

If you want to avoid the intermediate constructor/destructor you could explicitly use the copy constructor like so:
Test B( A.Make(11) );

To avoid the copy during method return you either have to return a reference or a pointer, but for this you'll need to guarantee that the object will survive beyond the method's scope. You could also use reference out parameters like so:
bool Make(int ID, Test& outParam )
{
  outParam = Test(ID);
  return true; //Return something for the user to know if creation succeeded.
}
int main(int argc, char **argv)
{
  Test A = Test(10);
  Test B;
  A.Make(11, B);
}


#5 rnlf   Members   -  Reputation: 1116

Like
2Likes
Like

Posted 05 September 2012 - 05:18 AM

Or, you can just trust your compiler to do the right thing: just return your object and let the compiler use return value optimization.

my blog (German)


#6 Bregma   Crossbones+   -  Reputation: 4836

Like
2Likes
Like

Posted 05 September 2012 - 07:24 AM

I think you're overthinking.
If a language was so subtle and inefficient it would not have remaind so dominant for so many decades.

Consider this complete and test example. Build it. Run it.
Observe the sequence in which the constructors and destructors are called.

It's important to understand the lidetime of objects in C++.
You also need to understand the cost of new/new[] and delete/delete[] if you're worried about
unnecessary resource consumption.
#include <iostream>

class Test
{
public:
  Test(int id)
  : id_(id)
  { std::cerr << " ctor(" << id_ << ")\n"; }

  Test(Test const& rhs)
  : id_(rhs.id_)
  { std::cerr << " copy-ctor(" << id_ << ")\n"; }

  ~Test()
  { std::cerr << " dtor(" << id_ << ")\n"; }

  Test& operator=(Test const& rhs)
  {
	std::cerr << " operator=(" << rhs.id_ << ")\n";
	id_ = rhs.id_;
	return *this;
  }

  Test& add(Test const& rhs)
  {
	std::cerr << " (" << id_ << ").add(" << rhs.id_ << ")\n";
	id_ += rhs.id_;
	return *this;
  }

private :
  int id_;
};

Test operator+(Test const& lhs, Test const& rhs)
{
  return Test(lhs).add(rhs);
}

int
main()
{
  std::cerr << "Test A(10);\n";
  Test A(10);
  std::cerr << "Test B = Test(11)\n";
  Test B = Test(11);
  std::cerr << "Test C = A + B\n";
  Test C = A + B;
  std::cerr << "end\n";
}

Here's the output.
Test A(10);
ctor(10)
Test B = Test(11)
ctor(11)
Test C = A + B
copy-ctor(10)
(10).add(11)
copy-ctor(21)
dtor(21)
end
dtor(21)
dtor(11)
dtor(10)
Notice that there are not extra calls to the basic constructor when using the assignment initialization form of declaration.
You might also notice the extra temporary constructed in the operator+() function.
Stephen M. Webb
Professional Free Software Developer

#7 nox_pp   Members   -  Reputation: 490

Like
2Likes
Like

Posted 05 September 2012 - 11:37 AM

still, it's very confusing, and the memory overhead of what's going on is what scares me into only doing this sort of stuff via passing by reference, and using new/delete instead of ever using stack memory to hold the object.


Don't be scared. Until you've profiled your code, and determined that it runs too slowly as a direct result of passing by value, always pass by value (where possible.) There are two sides to the argument. Heap allocated memory comes with its own set of difficulties, and they're rather more unpleasant than the problems associated with pass by value.

I've never been more productive since I virtually eliminated heap allocations from my own code: http://purplepwny.co...s_by_value.html

Between Scylla and Charybdis: First Look <-- The game I'm working on

 

Object-Oriented Programming Sucks <-- The kind of thing I say


#8 slicer4ever   Crossbones+   -  Reputation: 3326

Like
1Likes
Like

Posted 05 September 2012 - 01:46 PM

thanks for the help guys, i'm understanding alot more.

After adding the copy constructor, i can see where alot of my confusion was coming from, I couldn't figure out how the destructor twice had the correct value, when the object had only been created once. now with the copy constructor, I see what's going on better.

I've been messing around alot more with this, and thanks to you guys, I think i have a much better grasp of what's going on.

@Murdocki:
That's essentially what I have been doing for along time, but it's time I start doing things in a much better fashion.

thanks for the help guys=-)
Check out https://www.facebook.com/LiquidGames for some great games made by me on the Playstation Mobile market.

#9 Lightness1024   Members   -  Reputation: 702

Like
0Likes
Like

Posted 06 September 2012 - 06:56 AM

in the C++ spec, RVO is mentioned to work only with initialization of objects.
A a = MakeMeAnA();  // ok
...
A a;
a = MakeMeAnA();  // not ok.
be careful.

also, C++11 solved the issue thanks to std::move

#10 swiftcoder   Senior Moderators   -  Reputation: 9741

Like
0Likes
Like

Posted 06 September 2012 - 12:34 PM

in the C++ spec, RVO is mentioned to work only with initialization of objects.

A a = MakeMeAnA();  // ok
...
A a;
a = MakeMeAnA();  // not ok.
be careful.

That is not entirely correct, as I understand it. The second example wouldn't ever invoke a copy-constructor anyway. It would invoke the assignment operator (and side-effects in overloaded assignment operators aren't just ignored).

However, if your class does not have an explicitly defined assignment operator, GCC will use RVO to eliminate the copy (i.e. the copy constructor will not be called, even if it has non-trivial side-effects.

Tristam MacDonald - Software Engineer @Amazon - [swiftcoding]





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS