1

I have the following code, was wondering when Foo's destructor is called.

#include <iostream>

class Foo {
public:
  Foo() {
  }
  ~Foo() {
    std::cout << "destruct" << std::endl;
  }
};

void go(Foo f) {
  std::cout << "go" << std::endl;
}

int main() {
  go(Foo());
  std::cout << "main" << std::endl;
  return 0;
}

If I run the code, I got the following output

go
destruct
main

It shows Foo's destructor is called after go is done. My gcc is 4.8.3.

I had thought the temporary Foo's object should be deleted after it is copied to go's argument. But this is not the case, and only one object of Foo exists. Is this expected or undefined in terms of compiler's implementation?

Joe C
  • 2,757
  • 2
  • 26
  • 46
  • `go` uses the `Foo`, why should it be deleted before `go` is done with it? A really dumb compiler could create a temporary and then copy it to the argument and delete the temporary, but what is the point in making a temporary if all you do is delete it immediately? – nwp Sep 18 '16 at 20:06

1 Answers1

1

It's an optimization permitted by the C++ Standard.

The C++ standard draft, [class.temp/2] says and I quote (relevant parts only; emphasis are mine):

The materialization of a temporary object is generally delayed as long as possible in order to avoid creating unnecessary temporary objects. .....

Example:

class X {
public:
  X(int);
  X(const X&);
  X& operator=(const X&);
  ~X();
};

class Y {
public:
  Y(int);
  Y(Y&&);
  ~Y();
};

X f(X);
Y g(Y);

void h() {
  X a(1);
  X b = f(X(2));
  Y c = g(Y(3));
  a = f(a);
}

X(2) is constructed in the space used to hold f()'s argument and Y(3) is constructed in the space used to hold g()'s argument.

Formerly, in n3690, it said:

An implementation might use a temporary in which to construct X(2) before passing it to f() using X’s copy constructor; alternatively, X(2) might be constructed in the space used to hold the argument

That means, this:

void go(Foo) {
    std::cout << "go" << std::endl;
}

int main() {
    go(Foo());
}

is sometimes as "performant" as you want!, See, C++ is gradually getting there ;-).


But you see, using std::move will inhibit that behavior, because std::move produces an xvalue expression from a materialized object:

void go(Foo) {
    std::cout << "go" << std::endl;
}

int main() {
    go(std::move(Foo()));
}

In conclusion,

  1. When not using std::move in this your case, the object is created once as seen Live on Coliru

  2. But when you use std::move, it is created twice as seen Live on Coliru, this is because of materialization of the object. Read the complete paragraph of class.temp/2 to understand what materialization means.

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68