4

I've tried to understand the semantics of c++ temporary objects lifetime extension. I've tried to simulate simple situation and was a bit surprised.

Below I'm providing my code.

#include <iostream>

struct C
{
    C(const int new_a) { a = new_a; };

    int a = 0;
};

C return_num()
{
    C num(20);

    std::cout << "From func(): num = " << num.a << ", by adress: " << &num.a << std::endl;

    return num;
}

void pass_num(const C& num)
{
    std::cout << "From func(): num = " << num.a << ", by adress: " << &num.a << std::endl;
}

int main()
{
    std::cout << "\nLifetime extention:" << std::endl;
    {
        const C& ext_num = return_num();

        std::cout << "From main(): num = " << ext_num.a << ", by adress: " << &ext_num.a << std::endl;
    }

    std::cout << "\nPassing by reference:" << std::endl;
    {
        C num(20);

        std::cout << "From main(): num = " << num.a << ", by adress: " << &num.a << std::endl;

        pass_num(num);
    }
}

Here is the main question: return_num() works curiously from my point of view, cause I expected that address of the variable, which I'm trying to output in main, would be the same as internally in return_num(). Could you please explain me why it is not?

For example in pass_num() output address matches the external address which I got in main.

Here is example output:

Lifetime extention:
From func(): num = 20, by adress: 0x7fff44fc8b4c
From main(): num = 20, by adress: 0x7fff44fc8b70

Passing by reference:
From main(): num = 20, by adress: 0x7fff44fc8b6c
From func(): num = 20, by adress: 0x7fff44fc8b6c

alex
  • 53
  • 4

3 Answers3

2

Move constructors typically "steal" the resources held by the argument (e.g. pointers to dynamically-allocated objects, file descriptors, TCP sockets, I/O streams, running threads, etc.) rather than make copies of them, and leave the argument in some valid but otherwise indeterminate state.

Please see Move Constructor

I changed the below in your code and I hope it is working as expected. I changed int a to int* a

#include <iostream>

class C
{
   public:
   int *a;
   C( int new_a) 
   { 
      a = new int();
      *a = new_a;
   };
   C(const C& rhs) { std::cout << "Copy " << std::endl; this->a = rhs.a; }
   C(C&& rhs):a(std::move(rhs.a)) 
   {
      std::cout << "Move!!" <<"Address resource a " << &(*a) << ", Address of 
      resource rhs.a" << &(*rhs.a) << std::endl; rhs.a = nullptr;
      std::cout << "Value of a:: " << *a << std::endl;
   }  

  };

  C return_num()
  {
     C num(20);

     std::cout << "From return_num(): num = " << *num.a << ", Address of resource a : 
     "<< &(*num.a)<< std::endl;
    return (std::move(num));
  }

  void pass_num(const C& num)
  {
     std::cout << "From pass_num(): num = " << *num.a << ", by adress: " << &num.a << 
     std::endl;
  }

  int main()
  {
      std::cout << "\nLifetime extention:" << std::endl;
      {
         const C& ext_num = return_num();

         std::cout << "From main() 1 : num = " << *(ext_num.a) << ", by resource 
         adress: " << &(*ext_num.a) << std::endl;
      }

      std::cout << "\nPassing by reference:" << std::endl;
      {
         C num(20);

         std::cout << "From main() 2 : num = " << *num.a << ", by adress: " << &num.a 
         << std::endl;

         pass_num(num);
       }
       return 0;
    }

The above code produces below output:

Lifetime extention:
From return_num(): num = 20, Address of resource a : 0x7fffeca99280
Move!!Address resource a 0x7fffeca99280, Address of resource rhs.a0x7fffeca99280
Value of a:: 20
From main() 1 : num = 20, by resource adress: 0x7fffeca99280

Passing by reference:
From main() 2 : num = 20, by adress: 0x7ffff466f388
From pass_num(): num = 20, by adress: 0x7ffff466f388

I hope it helps!

Abhishek Sinha
  • 480
  • 4
  • 9
1

Imagine this function:

int getNumber(){
    int num = 10;

    return num;
}

This function does not return num as an object, it returns a no-named copy of it (r-value, if you will) with the same value. Therefore, it has a different address.

The same thing happens with your return_num function.

Rokas Višinskas
  • 533
  • 4
  • 12
  • Thank you, but I thought that the following line const C& ext_num = return_num(); would be understood by compiler as though we want to take a copy of reference and save the value of variable by this reference until the copy of reference dies. Isn't that correct? – alex Jun 25 '19 at 09:25
-2

I suspect that taking the address of a member is inhibiting the optimization because the compiler doesn't know how to deal with all the possible edge cases. Eliminating taking the address of the member makes the optimization work.

#include <iostream>

struct C
{
    C(const int new_a) { a = new_a; };

    int a = 0;
    struct C* t = this;
};

C return_num()
{
    C num(20);

    std::cout << "From func(): num = " << num.a << ", by adress: " << num.t << std::endl;

    return num;
}

void pass_num(const C& num)
{
    std::cout << "From func(): num = " << num.a << ", by adress: " << num.t << std::endl;
}

int main()
{
    std::cout << "\nLifetime extention:" << std::endl;
    {
        const C& ext_num = return_num();

        std::cout << "From main(): num = " << ext_num.a << ", by adress: " << ext_num.t << std::endl;
    }

    std::cout << "\nPassing by reference:" << std::endl;
    {
        C num(20);

        std::cout << "From main(): num = " << num.a << ", by adress: " << num.t << std::endl;

        pass_num(num);
    }
}

Lifetime extention:
From func(): num = 20, by adress: 0x7ffd61f48a50
From main(): num = 20, by adress: 0x7ffd61f48a50

Passing by reference:
From main(): num = 20, by adress: 0x7ffd61f48a90
From func(): num = 20, by adress: 0x7ffd61f48a90

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 2
    `ext_num.t` will probably not match `&ext_num`. I think you got a copy there too. – Ted Lyngmo Jun 25 '19 at 09:14
  • @TedLyngmo I think that you're right! When you will use `return_num()` method, you will get copy of `C` struct, AND `ext_num` will have pointer (`C::t`) to not existing struct. I don't know why this answer is accepted – Raffallo Jun 25 '19 at 12:43
  • @Raffallo could you please clarify your answer? 1st question is: why do you think `C::t` pointer of copied structure, if it really was copied, points to not existing structure. From my point of view it points on "this" cause it contains "this" address any time. 2nd question: Could you please clarify how can it be possible that 2 simultaneously existing objects have the same address? – alex Jun 25 '19 at 13:27
  • @alex `t` is just a `C*` and may point somewhere where there used to be a living `C` if that `C` gets copied. If copying is elided, it'll be pointing at the alive object. – Ted Lyngmo Jun 25 '19 at 14:45
  • @alex It's simple. `struct C* t = this;` means that your constructor creating a pointer to the current structure. When you will use `return_num();` method, the structure is still COPIED, and so the new structure is a copy of the previous that resulting in that the pointer to the structure is also copied from previous one. So the `C::t` pointers are the same, but if you will check `&num` and `&ext_num` you will see differences. To solve this problem you should change this: `C return_num()` to this: `C& return_num()` – Raffallo Jun 26 '19 at 07:46
  • and `C::t` won't be needed anymore – Raffallo Jun 26 '19 at 07:52