I stumbled upon the following code snippet:
#include <iostream> #include <string> using namespace std; class First { string *s; public: First() { s = new string("Text");} ~First() { delete s;} void Print(){ cout<<*s;} }; int main() { First FirstObject; FirstObject.Print(); FirstObject.~First(); }
The text said that this snippet should cause a runtime error. Now, I wasn't really sure about that, so I tried to compile and run it. It worked. The weird thing is, despite the simplicity of the data involved, the program stuttered after printing "Text" and only after one second it completed.
I added a string to be printed to the destructor as I was unsure if it was legal to explicitly call a destructor like that. The program printed twice the string. So my guess was that the destructor is called twice as the normal program termination is unaware of the explicit call and tries to destroy the object again.
A simple search confirmed that explicitly calling a destructor on an automated object is dangerous, as the second call (when the object goes out of scope) has undefined behaviour. So I was lucky with my compiler (VS 2017) or this specific program.
Is the text simply wrong about the runtime error? Or is it really common to have runtime error? Or maybe my compiler implemented some kind of warding mechanism against this kind of things?
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .
Explicit Constructor Chaining using this() or super() To call a non-args default constructor or an overloaded constructor from within the same class, use the this() keyword. To call a non-default superclass constructor from a subclass, use the super() keyword.
Use the obj. ~ClassName() Notation to Explicitly Call a Destructor Function. Destructors are special functions that get executed when an object goes out of scope automatically or is deleted by an explicit call by the user.
When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.
A simple search confirmed that explicitly calling a destructor on an automated object is dangerous, as the second call (when the object goes out of scope) has undefined behaviour.
That is true. Undefined Behavor is invoked if you explicitly destroy an object with automatic storage. Learn more about it.
So I was lucky with my compiler (VS 2017) or this specific program.
I'd say you were unlucky. The best (for you, the coder) that can happen with UB is a crash at first run. If it appears to work fine, the crash could happen in January 19, 2038 in production.
Is the text simply wrong about the runtime error? Or is it really common to have runtime error? Or maybe my compiler implemented some kind of warding mechanism against this kind of things?
Yes, the text's kinda wrong. Undefined behavior is undefined. A run-time error is only one of many possibilities (including nasal demons).
A good read about undefined behavor: What is undefined behavor?
No this is simply undefined behavior from the draft C++ standard [class.dtor]p16:
Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life]). [ Example: If the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example
and we can see from the defintion of undefined behavior:
behavior for which this document imposes no requirements
You can have no expectations as to the results. It may have behaved that way for the author on their specific compiler with specific options on a specific machine but we can't expect it to be a portable nor reliable result. Althought there are cases where the implementation does try to obtain a specific result but that is just another form of acceptable undefined behavior.
Additionally [class.dtor]p15 gives more context on the normative section I quote above:
[ Note: Explicit calls of destructors are rarely needed. One use of such calls is for objects placed at specific addresses using a placement new-expression. Such use of explicit placement and destruction of objects can be necessary to cope with dedicated hardware resources and for writing memory management facilities. For example,
void* operator new(std::size_t, void* p) { return p; } struct X { X(int); ~X(); }; void f(X* p); void g() { // rare, specialized use: char* buf = new char[sizeof(X)]; X* p = new(buf) X(222); // use buf[] and initialize f(p); p->X::~X(); // cleanup }
— end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With