Can someone tell why test(2) object is destroyed after test_method() call?
#include<iostream>
#include<string>
using namespace std;
class test
{
int n;
public:
test(int n) : n(n)
{
cout << "test: " << n << endl;
}
~test()
{
cout << "~test: " << n << endl;
}
test & test_method()
{
cout << "test_method: " << n << endl;
return *this;
}
};
int main(int argc, const char *argv[])
{
cout << "main start" << endl;
const test &test1 = test(1);
const test &test2 = test(2).test_method();
cout << "main end" << endl;
}
Output is:
main start
test: 1
test: 2
test_method: 2
~test: 2
main end
~test: 1
test(2).test_method() returns a reference, which is bound to test2, and then the object to which it refers is destroyed at the end of the full expression, since it is a temporary object. That should not be a surprise.
The real surprise is that test1 remains a valid reference, because it is directly bound to a temporary, and binding a temporary to a reference extends the lifetime of the temporary to that of the reference variable.
You only have to note that in the test(2) case, the temporary object isn't bound to anything. It's just used to invoke some member function, and then its job is done. It doesn't "babysit" member functions, or in other words, lifetime extension isn't transitive through all possible future references.
Here's a simple thought experiment why it would be impossible to actually have "arbitrary lifetime extension":
extern T & get_ref(T &);
{
T const & x = get_ref(T());
// stuff
// Is x still valid?
}
We have no idea if x remains valid beyond the first line. get_ref could be doing anything. If it's implemented as T & get_ref(T & x) { return x; }, we might hope for magic, but it could also be this:
namespace { T global; }
T & get_ref(T & unused) { return global; }
It's impossible to decide within the original translation unit whether anything needs to be extended or not. So the way the standard has it at present is that it's an entirely trivial, local decision, just made when looking at the reference declaration expression, what the lifetime of the temporary object in question should be.
Because the C++ standard requires this behavior. Give the object a name if you want it to persist. It will persist as long as the name.
Edit: You your example, test1 is the name that you gave to the first object, whereas the second object has obtained no name at all, and so it does not outlast evaluation of the expression.
Related
Object lifetime is usually tied to scope but when an object is created inside a function call things become a bit blurrier (at least to me). Check the code below:
#include <iostream>
#include <string>
using namespace std;
struct ins
{
ins() : _num(++num) { cout << "ctor " << _num << endl; }
~ins() { cout << "dtor " << _num << endl; }
int get() { return _num; }
static int num;
int _num;
};
int ins::num = 0;
ins geti() { return {}; }
void usei(int i) { cout << "using " << i << endl; }
int main()
{
usei(geti().get());
cout << endl;
usei(geti().get());
}
Output
ctor 1
using 1
dtor 1
ctor 2
using 2
dtor 2
Demo
Long story short, I want to know if there's a way to "calculate" the lifetime of objects in such cases. How would I know to what function each object is tied to? my current mental model of what's going on does not help me much.
After digging into cppreference, I found a section on object lifetime. Apparently my mental model on this was wrong.
Lifetime does not bind to functions in this case, but to expressions. My case pertains to temporary object lifetime, which is created by
returning a prvalue from a function
In that case the following rule applies:
All temporary objects are destroyed as the last step in evaluating the
full-expression that (lexically) contains the point where they were
created, and if multiple temporary objects were created, they are
destroyed in the order opposite to the order of creation. This is true
even if that evaluation ends in throwing an exception.
I'm testing my understanding of lvalue and rvalue references by intentionally trying to break things. So say there is this struct:
struct FooBar
{
FooBar(int&& number) : rNumber(number)
{
}
int& rNumber;
};
and I create an instance FooBar obj(5). Every attempt to read the reference variable returns the right result (5). The same happens if I use const int& instead of int&&.
I noticed that replacing int with std::string and reading the reference returns an empty string, so I suspect it gives undefined behaviour. Is this so? And if so, why does it work with integers?
Update: I'm creating the instance and reading it like this:
FooBar obj(5);
//FooBar obj("Hello"); // For strings...
std::cout << obj.rNumber << std::endl;
Update 2: It also works if you pass a user-defined type, like this:
struct GooBar
{
public:
GooBar(int number) : itsNumber(number)
{
std::cout << "In constructor..." << std::endl;
}
GooBar(const GooBar& rhs) = delete;
GooBar(GooBar&& rhs) = delete;
~GooBar()
{
std::cout << "In destructor..." << std::endl;
}
int itsNumber;
};
struct FooBar
{
FooBar(GooBar&& number) : rNumber(number)
{
}
GooBar& rNumber;
};
and then creating an instance and reading it like so:
FooBar obj(GooBar(5));
std::cout << obj.rNumber.itsNumber << std::endl;
I think this is interesting, because it gives the following output:
In constructor...
In destructor...
5
With an integer literal as actual argument the compiler may pass a reference to a statically allocated instance.
With a std::string formal argument and a string literal as actual argument, the instance is created in the call, and is destroyed at the end of the call.
In both cases it's Undefined Behavior.
It's not clear how you call this though: you forgot to include that crucial information (as the question is as at time I'm writing this).
#include <iostream>
using namespace std;
class ClassA {
int k;
public:
ClassA(int i) : k(i)
{
}
~ClassA()
{
cout << "A destroyed" << " k=" << k << endl;
}
ClassA copyAndModify()
{
ClassA a(k*2);
return a;
}
void taunt()
{
cout << k << endl;
}
};
int main (int argc, char * const argv[]) {
ClassA original(1)
ClassA modified = original.copyAndModify();
modified.taunt();
return 0;
}
I thought that the object 'a' (inside method copyAndModify) was deconstructed when the method returned, but it didn't. Does this mean all objects, created inside a method, that are being returned don't get deconstructed? Is this true for all compilers?
You have encountered the Return Value Optimization. No, it is not going to be the same on all compilers.
This depends on the compiler, but usually when a method returns an object instance by value, the compiler can use RVO (Return Value Optimization) to prevent a temporary from being created at all, by passing the destination object as a hidden reference parameter. In other words, the compiler tweaks the generated code to act like you had written the code as follows:
void copyAndModify(ClassA &result)
{
ClassA a(k*2);
result = a;
}
ClassA modified;
original.copyAndModify(modified);
In the case of VS if you build in debug mode no optimization is done (the object will be destroyed), but in release mode optimization kicks in (the object will not be destroyed).
That's correct. If it's returned, it's not destructed. That makes sense. It would not make sense to be returned an object that was already destroyed.
After working 15 years in C++ I found that I don't understand references completely.
class TestClass
{
public:
TestClass() : m_nData(0)
{
}
TestClass(int n) : m_nData(n)
{
}
~TestClass()
{
cout << "destructor" << endl;
}
void Dump()
{
cout << "data = " << m_nData << " ptr = 0x" << hex << this << dec << endl;
}
private:
int m_nData;
};
int main()
{
cout << "main started" << endl;
TestClass& c = TestClass();
c.Dump();
c = TestClass(10);
c.Dump();
cout << "main ended" << endl;
return 0;
}
// prints:
// main started
// data = 0 ptr = 0x0012FF54
// destructor
// data = 10 ptr = 0x0012FF54
// main ended
// destructor
I understand from this test that TestClass instance is created on the stack (is this correct?) and initialized by first TestClass constructor. When is this instance allocated: when the main function is loaded or when the reference assignment is executed? When it is destroyed?
After the second reference assignment, the object address is not changed. Does this mean that the destructor and constructor are applied to the same memory area? Or is the memory deallocated (dynamically? on the stack?) and allocated again?
I know everything about stack and heap-allocated objects lifetime, their constructors and destructors, but I cannot understand what exactly happens in this program.
Edit:
Thanks to all. I tried to reproduce in this test some other (more complicated) program behavior. Your comments helped me to understand both my mistake and another program I am fighting with...
Fixed code is:
int main()
{
cout << "main started" << endl;
TestClass t;
TestClass& c(t);
c.Dump();
c = TestClass(10);
c.Dump();
cout << "main ended" << endl;
return 0;
}
Note from 29.06.2022: After latest edition by Daniel Walker this question looks like complete crap. I am not responsible for this.
Your code suffers from multiple problems and ultimately won't make sense. However, let's hack through it.
1) You can only bind a temporary to a const reference, thus extending its lifetime:
const TestClass & c = TestClass();
2) Now we can't use dump, because you didn't declare it const:
void Dump() const
3) Saying c = TestClass() is an assignment. However, c is now a reference-to-const, which cannot be assigned to, since assignment is non-constant (for obvious reasons). Let's hack around this:
const_cast<TestClass&>(c) = TestClass(10);
Now we've assigned a new value to the temporary-but-extended object c, and all is as it should be:
main started
data = 0 ptr = 0x0xbfa8219c
destructor
data = 10 ptr = 0x0xbfa8219c
main ended
destructor
The pointers are the same because there's only one object, namely the (temporary) one referenced by c. Assigning to it is a hack that's undefined behaviour in general, but we get away with it for the purpose of this demonstration.
The intermediate destructor is that of the second temporary TestClass(10).
TestClass& c = TestClass(); // TestClass() temporary doesn't persist beyond this expression.
c.Dump();
TestClass() creates a temporary and you cannot take the reference of it.
const TestClass& c = TestClass();
const qualification extends the life time of the temporary being created until the scope of the object c.
TestClass& c = TestClass();
This wouldn't even compile!
Attempting to bind a temporary to non-const reference would result in compilation error.
However, you can bind a temporary to const reference:
{
const TestClass& c = TestClass();
//use c
//....
}//<-------- the temporary will be destroyed here.
In this case, the life of the temporary extends to the lifetime of the reference, i.e when the reference variable goes out of scope, the temporary will be destroyed as shown above.
1) you can't get not const reference to a temporary object
2) in the line c = TestClass(10); operator=(...) is called
A good way is to compare references to pointers... (references are usually implemented the same way in assembly generally by using the ebx register). The main difference is that reference is constant after initialization...
However, The line const TestClass& c = TestClass(); is parallel to const TestClass* const pc = &TestClass(); so the object will be create and destroyed on the stack, pc will still hold the same address.
Static variables exist outside of the function, in terms of their memory at least (not scope), right? But one thing that always concerned me, is what happens when I call the function a second time. For instance:
f(){
static char buffer[256*256];
stuff(buffer);
}
When I call this function a second time, wouldn't it technically be declaring the variable 'buffer' a second time? Or does it work differently with static variables (as opposed to normal ones) once everything is compiled?
... I sometimes wish there was a chart or something of what a c++ compiler usually turns code into (minus optimizations) so I wouldn't have to bother you fine folks with little questions like this, aha. Thank you in advance!
edit: I know it works like this, I just want to know why though. It's probably something mind numbingly simple...
Static storage duration objects in function scope.
These objects are created on first use.
Then destroyed in reverse order of creation (with other static storage duration objects).
#include <iostream>
class X
{
public:
X(int x): m(x) {std::cout << "X: " << m << " created\n"; }
~X() {std::cout << "X: " << m << " destroyed\n";}
private:
int m;
};
static X x1(1);
int test()
{
std::cout << "Test: Start\n";
static X x3(3);
std::cout << "Test: Finished\n";
return 5;
}
int main()
{
std::cout << "Main: Start\n";
X x2(2);
test();
X x4(4);
std::cout << "Main: Finished\n";
}
Now Try it: (comments added). SSDO => Static Storage Duration object.
g++ X.cpp
./a.out
X: 1 created // SSDO file scope.
Main: Start
X: 2 created
Test: Start
X: 3 created // SSDO created on first use (Notice not destroyed)
Test: Finished
Test: Start // Notice not created here.
Test: Finished
X: 4 created
Main: Finished
X: 4 destroyed
X: 2 destroyed // Main now really finished. after destroying local variables.
X: 3 destroyed // Destroy SSDO in reverse order of creation. (3 - 1)
X: 1 destroyed
No, you static means that it is outside the scope of your function. It has the same effect as writing:
static char buffer[256*256];
f(){
stuff(buffer);
}
Except that the buffer is only visible in the scope of your function, and the code is more readable.
(NOTE: My example doesn't apply when char is not a primitive type - in that case, it is constructed the first time it is "declared").
In this context, static means that the variable has application lifetime. It is allocated before main() the function is entered, and deallocated after main() has returned. Also, its value is preserved between function calls. Think of it as a global variable that is only visible from inside that function.
The variable exists before and after you call the function...it's static.
This example might illustrate it:
#include <iostream>
using namespace std;
void test() {
static int i = 123;
if (i == 123) {
i = 321;
}
cout << i << endl;
}
int main(int arg, char **argv) {
test();
test();
return 0;
}
The output is:
321
321
So "i" is only initialized the first time it is encountered, so to speak. But actually it's allocated at compile time for that function. Afterwards, it's just in the scope of the function test() as a variable, but it is static so changing it changes it in all future calls to test() as well.