Re: Function Returning a local object???

From: E. Robert Tisdale (E.Robert.Tisdale_at_jpl.nasa.gov)
Date: 09/08/04


Date: Tue, 07 Sep 2004 16:00:52 -0700

Old Wolf wrote:

> Olumide wrote:
>
>>I know its ok to return by value -
>>for simple data types for example:
>>
>> int foo(void) {
>> int number = 5;
>> return 5;
>> }
>>
>> int bar = foo();

>>This is no problem, because I can imagine the return value of foo()
>>i.e. 5, being copied to the integer bar.
>>But when the foo() returns a derived class whose member variables
>>are themselves class objects or worse still objects(?) of a derived class,
>>I suspect there will be a fair amount of copying taking place
>>when foo returns such a class by value.

Your suspicions are unfounded.

>>My question therefore, "How is this all the necessary copying handled?"

No copying is required.

Suppose, instead, that you write:

        MFVec foo(const MFVec& z1, const MFVec& z2) {
          MFVec res = z1;
          res += z2
          return res;
          }

In the typical implementation, the compiler will emit code similar to:

        MFVec& foo(MFVec& res, const MFVec& z1, const MFVec& z2) {
          res = z1; // using the copy constructor
          res += z2;
          return res;
          }

In other words,
the compiler creates the return value in the calling program
and passes a [hidden] reference to it to function foo.
Function foo does *not* create a local object named res
but recognizes that res is another name for the return value.

This is especially efficient if you use foo to initialize an object
in the calling program:

        MFVec z3 = foo(z1, z2);

The compiler emits code to allocate storage for z3
that passes a reference to z3 as a hidden argument to foo
which foo interprets as a reference to the return value --
z3 is initialized by foo directly and no temporary is required.

>>By the overlaoded assignament operator, operator= ?
>>If so, what happens is this operator= has not been defined by the
>>programmer. Is it generated by the compiler?
>>(I've read enough to know that the copy constructor is only invloved
>>in the initialization of newly created variables, global or otherwise,
>>but this is not the case here.
> All that's happening here is assignment.)
>
> Actually the copy constructor is used here, "newly created
> variables" includes temporary objects. When the function
> returns, a temporary object is created (in the scope of the
> calling function) using the copy-constructor, with the returned
> value as parameter.
> The calling function usually does something with this object,
> eg. if it initializes another object then the copy-constructor
> will be called again, with the temporary return-value object as
> a parameter.
> Being a temporary object, it will be destroyed at the end of the
> full-expression it was created in, eg if we have at local scope:
>
> Foo func() { Foo g; return g; }
> Foo f( func() );
>
> then we have:
> - g is default-constructed
> - temporary return value is copy-constructed from g
> - g is destroyed
> - f is copy-constructed from temporary return-value
> - temporary return-value is destroyed
>
> The compiler is allowed to optimise all this, so you can't test
> it with a compiler, but if you make Foo with a private copy
> constructor then it should fail to compile.

Let's try that:

> cat main.cc
         #include <iostream>

         class Foo {
         private:
           // representation
           int I;
         public:
           // operators
           Foo& operator=(const Foo& foo) {
             I = foo.I;
             std::cout << "Foo::operator=(const Foo&)" << std::endl;
             return *this;
             }
           friend
           std::ostream& operator<<(std::ostream& os, const Foo& foo);
           // constructors
           Foo(int i = 0): I(i) {
             std::cout << "Foo::Foo(int)\tI = " << I << std::endl;
             }
           Foo(const Foo& foo): I(foo.I) {
             std::cout << "Foo::Foo(const Foo&)" << std::endl;
             }
          ~Foo(void) {
             std::cout << "Foo::~Foo(void)" << std::endl;
             }
           };

         Foo func(void) {
           Foo g;
           g = Foo(13);
           std::cout << "func(void)" << std::endl;
           return g;
           }

         inline
         std::ostream& operator<<(std::ostream& os, const Foo& foo) {
           return os << foo.I;
           }

         int main(int argc, char* argv[]) {
           Foo f(func());
           std::cout << "f = " << f << std::endl;
           return 0;
           }

> g++ -Wall -ansi -pedantic -o main main.cc
> ./main
         Foo::Foo(int) I = 0
         Foo::Foo(int) I = 13
         Foo::operator=(const Foo&)
         Foo::~Foo(void)
         func(void)
         f = 13

I defined a copy constructor but it was never called.
Notice that I modified func(void)
to assign a new value to g before it returns.
Function func(void) initializes object f in function main
directly using the default constructor Foo(int i = 0)
then it constructs a temporary Foo(13)
and assigns this temporary value to object f directly.
Finally, function func(void) destroys the temporary
just before it returns. No destructor is required for g
because g is just another name for the return value
which, in this case, is just object f in function main.