When I capture an object by reference in a C++11 lambda, let the object go out of scope, and then execute the lambda, it still has access to the object. When I execute the following code, the lambda call can still access the object, although the destructor has already been called! Can someone explain why this works and why I don't get a runtime error?
#include <iostream>
class MyClass {
public:
int health = 5;
MyClass() {std::cout << "MyClass created!\n";}
~MyClass() {std::cout << "MyClass destroyed!\n";}
};
int main(int argc, const char * argv[])
{
std::function<bool (int)> checkHealth;
if(true) {
MyClass myVanishingObject;
checkHealth = [&myVanishingObject] (int minimumHealth) -> bool {
std::cout << myVanishingObject.health << std::endl;
return myVanishingObject.health >= minimumHealth;
};
} // myVanishingObject goes out of scope
// let's do something with the callback to test if myVanishingObject still exists.
if(checkHealth(4)) {
std::cout << "has enough health\n";
} else {
std::cout << "doesn't have enough health\n";
}
return 0;
}
Here's the output:
MyClass created!
MyClass destroyed!
5
has enough health
According to the cppreference.com website's documentation of lambda functions
Dangling references
If an entity is captured by reference, implicitly or explicitly, and the function call operator of the closure object is invoked after the entity's lifetime has ended, undefined behavior occurs. The C++ closures do not extend the lifetimes of the captured references.
In other words, the fact that you have captured the object by reference and then let the object's lifetime ends means that invoking the lambda causes undefined behavior. Since one possible way that UB might work is "the object appears to be alive and well even though the object is dead," I suspect that you are seeing undefined behavior manifesting itself as nothing appearing to have gone wrong.
I suspect this would be the case if the compiler allocated a unique stack location to the temporary variable. This would mean that after the lifetime of the object ends, before main returns, the memory wouldn't be touched by anything. Accordingly, you'd see the variable holding the value 5 just as before, since nothing else is writing on top of it.
Hope this helps!
Related
I was testing my own RAII pointer implementation that does some weird stuff (by design). To test it, I made a class that tracks constructors and destructors and makes sure everything is deleted and created exactly once.
But I was constantly getting an error that I'm deleting something twice. Weird, right. I found out why and you can see it by running the sample below.
This is not the code that originally produced it, but it demonstrates the issue.
struct ReportDelete
{
ReportDelete() = delete;
ReportDelete(const std::string& name) : name(name) { std::cout << "create " << name << "\n"; }
ReportDelete(ReportDelete&& moveHere) : name(std::move(moveHere.name)) {}
ReportDelete& operator=(ReportDelete&& moveHere)
{
name = std::move(moveHere.name);
return *this;
}
ReportDelete(const ReportDelete&) = delete;
void operator=(const ReportDelete&) = delete;
std::string name;
~ReportDelete()
{
std::cout << "delete " << name << "\n";
name = name + "-deleted";
}
};
int main(int argc, const char** argv)
{
std::vector<ReportDelete> moveTest;
moveTest.push_back(ReportDelete("test"));
}
This prints:
create test
delete
delete test
So the destructor is still called on the moved value. That does not do anything bad in particular, but it makes it impossible for me to test the create/delete ratio correctly.
My actual test class does this in the destructor:
virtual ~Value()
{
std::string error = name + " deleted twice!";
assertm(parent.values[fullname()] == false, error.c_str());
parent.values[fullname()] = true;
}
Where parent is the class that tracks whether the values are deleted at the end. But how can I know here that this is only being run for Value&& and should be disregarded?
I tried this:
Value~() &&
{
std::cout << "rvalue destructor ignored...\n";
}
But it seems you cannot have a special destructor like that.
How can I know that a destructor is being only called on an rvalue refference and that I can therefore ignore it and not track it as a real delete?
This has nothing to do with a destructor getting called for an r-value reference, or not. A destructor is a destructor. An object is getting destroyed. The End. The particular details of the object are immaterial.
ReportDelete& operator=(ReportDelete&& moveHere)
{
name = std::move(moveHere.name);
return *this;
}
This will move name from the moved-from instance of this object. However, the moved-from instance of the object is still a valid, existing object, and at some point it will be destroyed.
However you moved-from its name member. Your C++ implementation's result of that is that the moved-from std::string name is left to be an empty string.
And when that moved-from object gets destroyed its destructor prints an empty name. This is the output that you're seeing.
TLDR: you were not getting an error. What you overlooked is that a moved-from object is still a valid, existing object, and it is still subject to being destroyed. Which it will be, in a well-formed C++ program. In fact, because, in this case, the moved-from object is an rvalue reference that increases, quite significantly, the likelyhood that the moved-from object is about to get nuked from high orbit.
Why does this:
#include <string>
#include <iostream>
using namespace std;
class Sandbox
{
public:
Sandbox(const string& n) : member(n) {}
const string& member;
};
int main()
{
Sandbox sandbox(string("four"));
cout << "The answer is: " << sandbox.member << endl;
return 0;
}
Give output of:
The answer is:
Instead of:
The answer is: four
Only local const references prolong the lifespan.
The standard specifies such behavior in §8.5.3/5, [dcl.init.ref], the section on initializers of reference declarations. The reference in your example is bound to the constructor's argument n, and becomes invalid when the object n is bound to goes out of scope.
The lifetime extension is not transitive through a function argument. §12.2/5 [class.temporary]:
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits. A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.
Here's the simplest way to explain what happened:
In main() you created a string and passed it into the constructor. This string instance only existed within the constructor. Inside the constructor, you assigned member to point directly to this instance. When when scope left the constructor, the string instance was destroyed, and member then pointed to a string object that no longer existed. Having Sandbox.member point to a reference outside its scope will not hold those external instances in scope.
If you want to fix your program to display the behavior you desire, make the following changes:
int main()
{
string temp = string("four");
Sandbox sandbox(temp);
cout << sandbox.member << endl;
return 0;
}
Now temp will pass out of scope at the end of main() instead of at the end of the constructor. However, this is bad practice. Your member variable should never be a reference to a variable that exists outside of the instance. In practice, you never know when that variable will go out of scope.
What I recommend is to define Sandbox.member as a const string member; This will copy the temporary parameter's data into the member variable instead of assigning the member variable as the temporary parameter itself.
Technically speaking, this program isn't required to actually output anything to standard output (which is a buffered stream to begin with).
The cout << "The answer is: " bit will emit "The answer is: " into the buffer of stdout.
Then the << sandbox.member bit will supply the dangling reference into operator << (ostream &, const std::string &), which invokes undefined behavior.
Because of this, nothing is guaranteed to happen. The program may work seemingly fine or may crash without even flushing stdout -- meaning the text "The answer is: " would not get to appear on your screen.
It's clear from the other answers that class members don't prolong the life of a temporary beyond the constructor call. There are cases though were your API can "safely" assume that all const& objects passed to a class won't be temporaries, but references to well scoped objects.
If you don't want to create copies, what can you do to ensure UB doesn't creep into your code? The best tool you have is to safeguard the assumption that std::string const& passed to the constructor are not temporaries, by declaring as deleted the overload that accepts such temporaries:
#include <string>
#include <iostream>
using namespace std;
class Sandbox
{
public:
Sandbox(const string& n) : member(n) {}
Sandbox(string&&) = delete;
// ^^^ This guy ;)
const string& member;
};
int main()
{
Sandbox sandbox(string("four"));
// Detect you're trying ^^^ to bind a
// reference to a temporary and refuse to compile
return 0;
}
Demo
Because your temporary string went out of scope once the Sandbox constructor returned, and the stack occupied by it was reclaimed for some other purposes.
Generally, you should never retain references long-term. References are good for arguments or local variables, never class members.
you're referring to something which has vanished. The following will work
#include <string>
#include <iostream>
class Sandbox
{
public:
const string member = " "; //default to whatever is the requirement
Sandbox(const string& n) : member(n) {}//a copy is made
};
int main()
{
Sandbox sandbox(string("four"));
std::cout << "The answer is: " << sandbox.member << std::endl;
return 0;
}
struct A {
A(int) : i(new int(783)) {
std::cout << "a ctor" << std::endl;
}
A(const A& other) : i(new int(*(other.i))) {
std::cout << "a copy ctor" << std::endl;
}
~A() {
std::cout << "a dtor" << std::endl;
delete i;
}
void get() {
std::cout << *i << std::endl;
}
private:
int* i;
};
const A& foo() {
return A(32);
}
const A& foo_2() {
return 6;
}
int main()
{
A a = foo();
a.get();
}
I know, returning references to local values is bad. But, on the other hand, const reference should extend a temporary object lifetime.
This code produce an UB output. So no life extention.
Why? I mean can someone explain whats happening step by step?
Where is fault in my reasoning chain?
foo():
A(32) - ctor
return A(32) - a const reference to local object is created and is returned
A a = foo(); - a is initialized by foo() returned value, returned value goes out of scope(out of expression) and is destroyed, but a is already initialized;
(But actually destructor is called before copy constructor)
foo_2():
return 6 - temp object of type A is created implicitly,a const reference to this object is created(extending its life) and is returned
A a = foo(); - a is initialized by foo() returned value, returned value goes out of scope(out of expression) and is destroyed, but a is already initialized;
(But actually destructor is called before copy constructor)
Rules of temporary lifetime extension for each specific context are explicitly spelled out in the language specification. And it says that
12.2 Temporary objects
5 The second context is when a reference is bound to a temporary. [...] A temporary bound to the returned value in a function return statement
(6.6.3) persists until the function exits. [...]
Your temporary object is destroyed at the moment of function exit. That happens before the initialization of the recipient object begins.
You seem to assume that your temporary should somehow live longer than that. Apparently you are trying to apply the rule that says that the temporary should survive until the end of the full expression. But that rule does not apply to temporaries created inside functions. Such temporaries' lifetimes are governed by their own, dedicated rules.
Both your foo and your foo_2 produce undefined behavior, if someone attempts to use the returned reference.
You are misinterpeting "until function exit". If you really want to use a const reference to extend the life of an object beyond foo, use
A foo() {
return A(32);
}
int main() {
const A& a = foo();
}
You must return from foo by value, and then use a const reference to reference the return value, if you wish to extend things in the way you expect.
As #AndreyT has said, the object is destroyed in the function that has the const &. You want your object to survive beyond foo, and hence you should not have const &
(or &) anywhere in foo or in the return type of foo. The first mention of const & should be in main, as that is the function that should keep the object alive.
You might think this return-by-value code is slow as there appear to be copies of A made in the return, but this is incorrect. In most cases, the compiler can construct A only once, in its final location (i.e. on the stack of the calling function), and then set up the relevant reference.
If I have a unique pointer and I create an alias for it in a function, and that alias goes out of scope, why doesn't the original unique_ptr also get destroyed? After all, 'b' as defined in the function below is basically the same object in memory as 'x'. What is going on behind the scenes?
#include <iostream>
#include <memory>
void testfunc(std::unique_ptr<int>& x) {
std::unique_ptr<int>& b = x;
}
int main() {
std::unique_ptr<int> a(new int(5));
std::cout << *a << std::endl; // 5
testfunc(a);
std::cout << *a << std::endl; // 5
}
What you're using is a reference, and a reference in C++ is a distinct type from what it is referencing. You can interact with an object through a reference, but the reference itself and the object being referred to have separate lifetimes. When one is destroyed, the other doesn't automatically get destroyed. This means you can pass a reference into a function and then at the end of a function when the reference is destroyed the original object is still valid. This allows passing around large complex objects without needing to copy or even moving them. It's a implementation detail, but it's common for compilers to simply use a pointer "behind the scenes" as references.
As a side note, this aspect of references in C++ leads to the infamous dangling reference issue. If you hold a reference to some object and that object is destroyed the reference you have is now technically invalid, and you'll invoke undefined behavior if you use it. Unfortunately there is nothing built into the language to automatically detect or deal with this situation. You must architect your program to avoid it.
A reference is can be considered like an alias to an element, hence it references another variable by taking up its value and working just like it does, but it doesn't get destroyed until called by the destructor or forcibly destroyed by the programmer which will also destroy the variable it references... since a reference is just an editable alias... However their lifespan differs since a non-reference type can be moved and it becomes out of scope...
"What is going on behind the scenes?"
Inside the memory, the reference allows us to change the value of an element and if often used instead of pointers which were a common practice in C... But, its value cannot be moved unless passed... A reference's value won't change unless changed using an assignment operation directly or indirectly i.e, from the function parameter x which itself is an alias...
Like: x = std::make_unique<int>(6); will change the value of a to 6 instead... But what you have done here instead is...
auto& b = x;
Nothing actually happens except the value that x(references to a) is referencing to is copied and passed to b (which just acts like another alias)... So it is similar to doing: auto& b = a;, but since a is outside the scope, it references a's value indirectly...
#include <iostream>
#include <memory>
void testfunc(std::unique_ptr<int>& x)
{
auto& b(x); // 'b' is an alias of 'x' and 'x' is an alias of 'a'
b = std::make_unique<int>(6); // Setting 'b' to 6 meaning setting 'a' to 6...
/* Now you can't do 'x = b' since you cannot assign a value to an alias and it is
like a 'circular assignment operation'...*/
}
int main()
{
std::unique_ptr<int> a(new int(5));
std::cout << *a << std::endl; // 5 : Nothing happens, just initialization...
testfunc(a); // It does not affect the reference...
std::cout << *a << std::endl; /* 6 : Since reference is an 'alias', you
changed 'a' as well...*/
} // It is freed after use by the destructor...
So, a general advice from people would be that you should avoid references if you are unsure of what it does (It can change the real variable if you are unknown of its consequences)... and take some time to learn about them...
If you destroy the original however..., all the references themselves will become invalidated... In such a case, when trying to access the value of destroyed (nullified) object is undefined causing undefined behavior...
#include <iostream>
#include <memory>
void testfunc(std::unique_ptr<int>& x) { // you take a reference to a unique_ptr
std::unique_ptr<int>& b = x; // which will do nothing to the lifetime of
} // the unique_ptr you pass to the function,
// then you assign the passed parameter
// to another reference. again, that does
// nothing to the lifetime of the original.
int main() {
std::unique_ptr<int> a(new int(5));
std::cout << *a << std::endl; // 5
testfunc(a);
std::cout << *a << std::endl; // 5
}
After all, 'b' as defined in the function below is basically the same object in memory as 'x'.
Not at all. x is a reference. A reference is not an object, and no constructor or destructor is called for it. There are no "aliases" for variables. There are for types, also known as typedefs.
Consider the same code with pointers instead:
void testfunc(std::unique_ptr<int>* x) {
std::unique_ptr<int>* b = x;
}
int main() {
std::unique_ptr<int> a(new int(5));
std::cout << *a << std::endl; // 5
testfunc(&a);
std::cout << *a << std::endl; // 5
}
The only time a reference can affect the lifetime of an object is when a reference binds to a temporary, but even then, it extends the lifetime rather than reducing it:
struct A {};
int main() {
{
A(); // Constructed and destructed
}
{
A const& a = A(); // Constructed
// Other instructions
} // Destructed
}
Demo
My friend told me C++ allows us to call a member function even if the instance is destroyed from memory. So I write the code below to verify it, but why the value of a can be extracted even after the object was destroyed? I thought there would be a segment fault.
#include <iostream>
class Foo{
public:
Foo(int a = 0){
std::cout << "created" << std::endl;
this->a = a;
}
~Foo(){
std::cout << "destroyed" << std::endl;
}
Foo *f(){
std::cout << "a=" << a << std::endl;
return this;
}
private:
int a;
};
Foo *iwanttocallf(int i){
return ((Foo)i).f();
}
int main(){
for(int i = 0; i < 3; i++)
iwanttocallf(i)->f();
}
Output from my Macbook Air:
created
a=0
destroyed
a=0
created
a=1
destroyed
a=1
created
a=2
destroyed
a=2
Usually compilers are implementing the member function call as a call to a regular c function with the first argument a pointer to the object (gcc does it like that as far as I know). Now if your pointer is pointing to one destroyed object it doesn't mean that the memory where the a has been stored will be changed, it might be changed. So it is undefined behavior in general. In your case you got a value of a but maybe next time with a different compiler or different code you will crash. Try to use placement new operator then set a value of 'a' = 0 in destructor... and follow the memory where the object is stored.
"My friend told me C++ allows us to call a member function even if the member is destroyed from memory"?
I don't know what your friend is trying to say. But you call member function on some object of a class
unless it's a static member. So, if you delete that object from memory, how could you call any function of that class on that object. It's an undefined behavior.
This is covered in §12.7 [class.cdtor]:
[..] For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor
finishes execution results in undefined behavior.
As other people have told, this involves undefined behavior, and any result is possible. I'll try to explain why you encountered this particular result (stuff working normally).
Objects in C++ are represented by contents of memory. When an object is destroyed, its
destructor is executed, but the memory still contains the previous value. The output operation outputs the value taken from memory (which is now "free" - doesn't belong to any object) - if there is not much stuff going on between the destructor call and the output, the old value will remain.
However, if you change your code to add some calculations, the bug will be evident. For example, I added the following function that simulates some calculations:
int do_stuff()
{
int result = 0;
int x[3] = {0};
for (auto& n: x)
{
n = rand();
result ^= n;
}
return result;
}
I also added a call to this function:
Foo *f(){
std::cout << "foo1: a=" << a << std::endl;
do_stuff();
std::cout << "foo2: a=" << a << std::endl;
return this;
}
I got the output:
foo1: a=0
foo2: a=424238335
This clearly shows that it's not safe to expect anything consistent when dealing with deleted objects.
By the way, some debuggers overwrite the memory that deleted objects occupied with a special value like 0xcdcdcdcd - to make some sense out of this kind of unpredictable behavior. If you execute your code under such a debugger, you will see garbage printed, and will immediately know that your code is buggy.