Unusual behavior of a destructor when copying across stack variables
I wrote a test to check if the destructors were called before assigning the overwrite to a stack variable and I can't find a rational explanation for the results ...
This is my test (in Visual C ++ 2008 release mode):
#include <iostream>
class C {
public:
char* ptr;
C(char p) { ptr = new char[100]; ptr[0] = p;}
~C() { std::cout << ptr[0] << ' '; delete [] ptr; }
};
int _tmain(int argc, _TCHAR* argv[])
{
{
C s('a');
s = C('b');
s = C('c');
s = C('d');
}
std::cin.get();
return 0;
}
I expected to get either "abc d" if my hypothesis was true, or just "d" if false. Instead, I get "bcd x". The "X" changes depending on how much memory ptr is allocated, which indicates that it is reading random heap values.
What I believe is happening (correct me if I'm wrong) is that every call to the constructor creates a new stack value (lets you call them s1, s2, s3, s4), and then the assignments leave s1.ptr overwritten by s4.ptr. s4 is then destroyed immediately after the copy, but s1 (with a dangling ptr) is destroyed when it leaves the scope, causing s4.ptr to be deleted twice and not deleting the original s1.ptr file.
Is there a way to avoid this useless behavior that doesn't involve using shared_ptrs?
edit: replace 'delete' with 'delete []'
source to share
The behavior of your application is undefined as it is stated that multiple objects will share access to the shared pointer and try to read it ...
The rule of three states, which every time you define one of:
- copy constructor
- assignment operator
- destructor
Then you have to define something else, as your object has certain behavior that the generated methods don't know about by default.
EDIT a special exception :
sometimes you only define a destructor because you want it to be virtual or because it writes something, not because there is some special handling of your attributes;)
source to share
Add the methods defined by the othere compiler:
class C
{
public:
char* ptr;
C(char p) { ptr = new char[100]; ptr[0] = p;}
~C() { std::cout << ptr[0] << ' '; delete [] ptr; }
C(C const& c) { ptr = new char[100]; ptr[0] = c.ptr[0];}
C& operator=(C const& c) { ptr[0] = c.ptr[0]; return *this;}
};
int _tmain(int argc, _TCHAR* argv[])
{
{
C s('a');
s = C('b');
s = C('c');
s = C('d');
}
std::cin.get();
return 0;
}
It should now print:
bcdd
Each temporary object is destroyed at the end of the expression. Then s is destroyed last (after "d" is copied to ptr [0]). If you follow the print instructions in each method, it will be easier for you to see what is happening:
>> C s('a');
Construct 'a'
>> s = C('b');
Construct 'b'
Assign 'b' onto 'a'
Destroy 'b' (Temporary destroyed at ';')
>> s = C('c');
Construct 'c'
Assign 'c' onto 'b' (was 'a' but has already been assigned over)
Destroy 'c' (Temporary destroyed at ';')
>> s = C('d');
Construct 'd'
Assign 'd' onto 'c'
Destroy 'd' (Temporary destroyed at ';')
>> End of scope.
Destroy 'd' (Object s destroyed at '}')
Since there are 4 methods defined by the compiler, the "rule of four" applies.
If your class contains a RAW pointer that belongs to the class (owned means your object defines lifespan). Then you have to override all 4 compiler generated methods.
Since you are creating and destroying the 'ptr' member, this is owned by ptr. Thus, all four methods must be defined.
source to share
The s is only destroyed when it goes out of scope - and, as you say, is overwritten during the program, so the original distribution is leaked out and the last one is deleted twice.
The solution is to overload the assignment operator (and, as Pete suggests, provide a copy constructor as they go hand in hand), in which you clear the array you have and the copy you provided.
source to share
The problem is that you need copy constructors and assignment operators. Because of the line where you assign one class to another, a shallow copy is made. This will cause both classes to have the same ptr pointer. If then one of them is deleted, the other indicates the top freed memory
source to share
You haven't defined an assignment or copy operator. So something like:
C s('a');
An instance of 's' is created and initialized with 'a'.
s = C('b');
Creates a temporary object, initializes it with "b", and then executes a default assignment statement that makes a bitwise copy of all variables, overwriting ptr s. The temporary object is destroyed. Emitting 'b' and removing 'ptr' (rendering ptr to s is invalid).
s = C('c');
s = C('d');
Same. A temporary object is created, initialized with 'c', and the 'ptr' in s is overwritten by the ptr allocated in the temporary object. The temporary object is destroyed by emitting 'c' and leaving ptr in s invalid. Repeat for d.
return 0;
}
Finally, s leaves the region, its destructor tries to emit the first character ptr, but this is garbage because ptr was allocated and removed by the last ('d') temporary object. An attempt to delete ptr should fail because this memory has already been deleted.
To solve this problem? Define explicit copy constructors and assignment operators.
class C {
// other stuff
C(const C&rhs); // copy constructor
C& operator=(const c& rhs){ // assignment operator
a[0] = rhs.a[0];
return *this;
}
};
source to share