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?
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,
When not using std::move in this your case, the object is created once as seen Live on Coliru
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.
Related
This question already has answers here:
Does returning a local variable return a copy and destroy the original(nrvo)?
(1 answer)
What is meaning of destroying local variable in function
(7 answers)
How is destroying local variables when a block is exited normally called in C++?
(6 answers)
Closed 7 months ago.
After a function is called, when the local (non-static) objects will be destroyed has been vague to me, especially after C++17 where prvalue is redefined. So I decide to ask this question.
(In C++14 and earlier)
Assume there is no optimization, consider the following toy code:
class Y
{
public:
~Y() { cout << "quitting f()\n"; }
};
class X
{
public:
X& operator=(const X& x)
{
cout << "assignment\n";
return *this;
}
};
X f()
{
X x;
Y y;
return x;
}
int main()
{
X x;
x = f();
}
The outputs are as follows:
quitting f()
assignment
Question 1:
In my current understanding, the local variables of f() are destroyed immediately after the returned temporary is created. May I ask if this is true?
(In C++17 and newer)
Consider the following toy code:
class Z { };
class Ya
{
public:
~Ya() { cout << "quitting f1()\n"; }
};
class Yb
{
public:
~Yb() { cout << "quitting f2()\n"; }
};
class X
{
public:
X() {}
X(Z z) { cout << "constructing X\n"; }
X& operator=(const X& x)
{
cout << "assignment\n";
return *this;
}
};
X f1()
{
Z z;
Ya y;
return X(z);
}
X f2()
{
Yb y;
return f1();
}
int main()
{
X x;
x = f2();
}
The outputs are as follows:
constructing X
quitting f1()
quitting f2()
assignment
In my current understanding, X(z) is a prvalue which is used to initialize the prvalue represented by f1(), which is then used to initialize the prvalue represented by f2(). The prvalue represented by f2() is then materialized into a temporary which is then used in the copy assignment.
If my understanding to question 1 is correct, I would guess that the local variables z and y are destroyed immediately after the initialization of the prvalue represented by f1(). Assume this is true, there is a problem: before the prvalue represented by f2() is materialized, there is NO object constructed from X(z) exists, so how could the materialized temporary be created from X(z) at the point when z is already destroyed?
Question 2:
As a result, my guess is that the local variables of f1() are destroyed after the prvalue represented by f2() is materialized (or if the prvalue is used to initialize a variable, had we written X x = f2(); instead). May I ask if this is true?
Your question can be answered with a slightly more detailed example. Here is code that traces when every object is constructed and destroyed:
#include <algorithm>
#include <iostream>
#include <typeinfo>
#include <type_traits>
// Compile-time string suitable as a non-type template argument.
template<size_t N>
struct fixed_string {
char value[N];
consteval fixed_string(const char (&str)[N]) { std::copy_n(str, N, value); }
};
template<fixed_string Name>
class tracer {
std::ostream &out() const { return std::cout << Name.value << " "; }
public:
tracer() { out() << "default constructed\n"; }
tracer(const tracer &) { out() << "copy constructed\n"; }
tracer(tracer &&) { out() << "move constructed\n"; }
template<typename T> tracer(T t) {
out() << "template constructed [" << typeid(T).name() << "]\n";
}
~tracer() { out() << "destroyed\n"; }
tracer &operator=(const tracer &) {
out() << "copy assigned\n"; return *this;
}
tracer &operator=(tracer &&) { out() << "move assigned\n"; return *this; }
};
tracer<"X">
f1()
{
tracer<"Z"> z;
tracer<"==== f1 temp"> y;
return {z};
}
tracer<"X">
f2()
{
tracer<"==== f2 temp"> y;
return f1();
}
int
main()
{
tracer<"X"> x = f2();
}
The output (with C++20) is as follows:
==== f2 temp default constructed
Z default constructed
==== f1 temp default constructed
Z copy constructed
X template constructed [6tracerIXtl12fixed_stringILm2EEtlA2_cLc90EEEEE]
Z destroyed
==== f1 temp destroyed
Z destroyed
==== f2 temp destroyed
X destroyed
So you can see that only one X is ever created, but that X is created before the Z it is constructed from is destroyed. How does that happen? Well, in the implementation you can think of the location in which to construct the X being passed as a kind of implicit argument to the functions that return a prvalue, so that the X is constructed inside f2, but it is located in main's stack.
Your example is slightly more complicated, but the same phenomenon is happening:
int
main()
{
tracer<"X"> x;
x = f2();
}
Produces the following output:
X default constructed
==== f2 temp default constructed
Z default constructed
==== f1 temp default constructed
Z copy constructed
X template constructed [6tracerIXtl12fixed_stringILm2EEtlA2_cLc90EEEEE]
Z destroyed
==== f1 temp destroyed
Z destroyed
==== f2 temp destroyed
X move assigned
X destroyed
X destroyed
So now obviously we have two X's, the one called x, and the temporary one that is materialized to pass into x's move assignment operator. However, the temporary X is created on main's stack, and its lifetime is the full expression, so if there were more code in main, the temporary X would be destroyed at the semicolon after the assignment, rather than at main's closing brace.
let's say I'm working with an idiomatic Cpp library (e.g. Intel's TBB) and have an in-place member in some class (e.g. TsCountersType _ts_counters;). Such member is automatically initialized by its default constructor (if it exists, otherwise compile error) unless my own KMeans's constructor initialize it explicitely by calling its constructor directly.
Then if I assign a new (created by calling constructor) value to the member field in a normal non-constructor method (e.g. init), what exactly is safe to assume?
I know that in that case the right side of the assignment is rvalue and thus move assignment operator will be called.
I think it is safe to assume that well behaving idiomatic Cpp code should call the original objects's (e.g. _ts_counters) destructor before moving the new object to the memory location. But is such assumption sound?
What about default move assignment operator? Does it call destructor on the original object before moving in? Is that even relevant question or is default move assignment operator created by the compiler only if (among other conditions) no explicit destructor is defined?
If that's the case what happens if I have a scenario similar to the TBB one where instead of TsCountersType I have simple custom tipe with a single unique_ptr and default everything else (constructors, destructors, move assignments, …). When will the unique_ptr get out of scope then?
Specifically in TBB's case, can I assume it happens given their documentation: Supported since C++11. Moves the content of other to *this intact. other is left in an unspecified state, but can be safely destroyed.
Example code:
class KMeans
{
private:
//...
// thread specific counters
typedef std::pair<std::vector<point_t>, std::vector<std::size_t>> TSCounterType;
typedef tbb::enumerable_thread_specific<TSCounterType> TsCountersType;
TsCountersType _ts_counters;
public:
//...
KMeans() : _tbbInit(tbb::task_scheduler_init::automatic) {}
virtual void init(std::size_t points, std::size_t k, std::size_t iters)
{
// When _ts_counters is replaced by a new object the destructor is automatically called on the original object
_ts_counters = TsCountersType(std::make_pair(std::vector<point_t>(k), std::vector<std::size_t>(k)));
}
};
Consider this code:
#include<iostream>
struct S
{
S() { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S(S const &) { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S(S&&) { std::cout << __PRETTY_FUNCTION__ << std::endl;}
S& operator=(S const &) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this;}
S& operator=(S&&) { std::cout << __PRETTY_FUNCTION__ << std::endl; return *this;}
~S() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};
struct W
{
W() : s{} {}
void init() { s = S{}; }
private:
S s;
};
int main()
{
W w{};
w.init();
}
The output this produces (clang and gcc tested):
S::S()
S::S()
S &S::operator=(S &&)
S::~S()
S::~S()
So, what happens exactly:
W's constcructor is called, which default initializes S
A temporary S is default constructed and immediately moved from into W::s member
Temporary object is now destructed (just before init exits)
W::s is destructed on main exit.
I'm trying to write a class that contains a function returning one of the class members, and I want to allow the caller to either move or copy the returned value. I wrote some dummy structs to test this; and after trying different variations, this seems to give me what I want.
#include <iostream>
using namespace std;
struct S {
int x;
S() : x(10) { cout << "ctor called\n"; }
S(const S& s) : x(s.x) { cout << "copy ctor called\n"; }
S(S&& s) : x(s.x) { cout << "move ctor called\n"; }
// I'm implementing move and copy the same way since x is an int.
// I just want to know which one gets called.
};
struct T {
S s;
T() : s() {}
S&& Test() && {
return move(s);
}
const S& Test() & {
return s;
}
};
int main() {
T t;
auto v = move(t).Test();
cout << v.x << "\n";
T t2;
auto w = t2.Test();
cout << w.x << "\n";
return 0;
}
The code prints out (with clang++-5.0 c++14):
ctor called
move ctor called
10
ctor called
copy ctor called
10
Is this an acceptable way to implement what I want? I have a few questions:
In the first Test function, I tried both S&& and S for the return type and it doesn't change the output. Does && mean anything for the (non-template) returned type?
Is it guaranteed that auto v = move(t).Test() would only invalidate the "moved" member? If struct T had other member variables, can I assume this call wouldn't invalidate them?
In the first Test function, I tried both S&& and S for the return type and it doesn't change the output. Does && mean anything for the (non-template) returned type?
There are little differences:
S&& is a (r-value) reference, so object is not yet moved.
returning S would move-construct S, so member is moved once the method is called.
For move(t).Test();, return ingS&& does nothing whereas returning S would move the member.
Is it guaranteed that auto v = move(t).Test() would only invalidate the "moved" member? If struct T had other member variables, can I assume this call wouldn't invalidate them?
Yes, only T::s is moved. std::move is just a cast to rvalue.
Yes it is acceptable way to implement this.
It does the same thing because returned value is temporary object, thus rvalue.
Depends on what you mean by invalidating
I wanted to restrict a specific class to be creatable on the stack only (not via allocation). The reason for this is that on the stack, the object which lifetime has begun last, will be the first to be destroyed, and I can create a hierarchy. I did it like this:
#include <cstddef>
#include <iostream>
class Foo {
public:
static Foo createOnStack() {
return {};
}
~Foo () {
std::cout << "Destructed " << --i << std::endl;
}
protected:
static int i;
Foo () {
std::cout << "Created " << i++ << std::endl;
}
Foo (const Foo &) = delete;
};
int Foo::i = 0;
The constructor normally should push the hierarchy stack, and the destructor pops it. I replaced it here for proof of concept. Now, the only way you can use such an object is by storing it in a temporary reference like this:
int main() {
Foo && a = Foo::createOnStack();
const Foo& b = Foo::createOnStack();
return 0;
}
My question now is, how safe is this with the C++ standard? Is there still a way to legally create a Foo on the heap or hand it down from your function into another frame (aka return it from your function) without running into undefined behaviour?
EDIT: link to example https://ideone.com/M0I1NI
Leaving aside the protected backdoor, C++17 copy elision breaks this in two ways:
#include<iostream>
#include<memory>
struct S {
static S make() {return {};}
S(const S&)=delete;
~S() {std::cout << '-' << this << std::endl;}
private:
S() {std::cout << '+' << this << std::endl;}
};
S reorder() {
S &&local=S::make();
return S::make();
}
int main() {
auto p=new S(S::make()),q=new S(S::make()); // #1
delete p; delete q;
reorder(); // #2
}
The use of new is obvious and has been discussed.
C++17 also allows prvalues to propagate through stack frames, which means that a local can get created before a return value and get destroyed while that return value is alive.
Note that the second case already existed (formally in C++14 and informally long before) in the case where local is of type S but the return value is some other (movable) type. You can't assume in general that even automatic object lifetimes nest properly.
In C++ it is valid to use take a const reference to a temporary:
const std::string& s = std::string("abc");
std::cout << s.length() << std::endl; // valid because string instance is still alive
But does this hold true if the temporary was created via a conversion from another type?
For example:
struct Foo
{
~Foo()
{
cout << "Foo destructor?" << endl;
}
};
struct Bar
{
operator Foo()
{
return Foo();
}
~Bar()
{
cout << "Destructor" << endl;
}
};
Foo getFoo()
{
return Foo();
}
Bar getBar()
{
return Bar();
}
int main()
{
const Foo& f = getBar();
/* is f valid here, or is it a dangling reference? */
std::cout << "We're still in main!" << std::endl;
}
I note that Bar's destructor is called before We're still in main is output, which makes me think that Foo& f is a dangling reference. Am I correct?
It does not matter how the temporary was created. If you bind a const X& or an X&& to a local prvalue, the lifetime of the temporary gets extended to the lifetime of the reference.
The function getBar creates an object of type Bar and immediately destroys returning a copy of it.
Bar getBar()
{
return Bar();//the lifetime of Bar() is only on this line;
}
Edit:
For the question in the source code if const Foo & f is valid; yes it is because getBar returns an object copy.
Also after checking the code i see that it first returns a copy of Bar and then casts it to Foo
Also i must mention the RVO (from the comments section) which is an optimization from the compiler. The lifetime of the object is still defined by it's scope {} however in this case the construction is done inside the function and the destruction is outside the function. This optimization will not work of you give a name to the variable like such:
Bar getBar()
{
Bar tmp_value;
return tmp_value;
}
Razvan.