Explicit destructor call ==> program freeze

Hi all.

I am using Bucky's C++ tutorials on YouTube at:
(https://www.youtube.com/watch?v=mUQZ1qmKlLY&feature=youtu.be)

Although it recommends using the "newboston" forum I can't find that site. So I looked for and joined this forum.

I'm using CodeBlocks to write the projects.

The simple lesson on destructors worked just fine: When the program exits I see my message from the ~class function: "Entered Sally:: Destructor"

Curious, I strayed a bit off the bare lesson on destructors to see what happens when I explicitly call the destructor for an object, not to wait for it to be called automatically at the exit of the program. I deliberately added something that should have cause a run-time error. This is noted in the main() code.

OK, here are the modules:

Sally.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef SALLY_H
#define SALLY_H

class Sally
{
    public:
        Sally();    // Constructor
        ~Sally();   // Destructor
        void printStuff();  // Omit the Sally:: prefix; not needed in here

    protected:

    private:
};

#endif // SALLY_H 


Now the class definition, Sally.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <windows.h>
#include <stdlib.h>
#include "Sally.h"
using namespace std;

Sally::Sally()
{
  cout << "Entered Sally:: Constructor" << endl;
}

Sally::~Sally()
{
  char response[10];
  cout << "Entered Sally:: Destructor" << endl;
  cout << "Deleted Sally object. Press <Enter> to continue: ";
  cin  >> response;
}

void Sally::printStuff()
{
  cout << "Did someone offer me free food?" << endl;
}


And finally, main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include "Sally.h"
using namespace std;

int main()
{
  Sally so;
  so.printStuff();
  cout << "Some nonsense from my main" << endl;
  so.~Sally();      // Explicitly call destructor
  so.printStuff();  // This *should* get a run-time error
                    // But it did not.
  return 0;
}


My output looks like:

Entered Sally:: Constructor (Created the object)
Did someone offer me free food? (Called a method in the object)
Some nonsense from my main (Just a breake between constructor & destructor)
Entered Sally:: Destructor (I think: Deliberate call to destructor)
Deleted Sally object. Press <Enter> to continue:
....
I press enter but it stays frozen. I had expected that when I called so.printStuff() after calling the destructor, I would get a run-time error about no such object.

(As an aside: I am unable to get this pane to actually do WSYSIG formatting so my code and quoted output have no formatting. Nor am I am able to choose an icon or view a preview. Using Chrome browser on Windows-7)

Thanks for a leg up here. It's about time I started picking up C++. (Python next.)

-- JS
I press enter but it stays frozen.

Just pressing "enter" alone will not produce the desired effect, remember that by default the extraction operator>> skips leading whitespace when retrieving a C-string. By the way you should never use a method that doesn't limit the number of characters it will try to retrieve into a C-string, why are you using C-strings instead of the safer std::string?

Also remember that the compiler will automatically call the destructor when the class goes out of scope, even if you already called it implicitly. Two destrucors mean two calls to cin.

I had expected that when I called so.printStuff() after calling the destructor, I would get a run-time error about no such object.

Funny thing about undefined behavior, almost anything can happen.
it is really unusual to call the destructor manually.
you have triggered undefined behavior (by trying to use the destroyed item, or trying to destroy it twice, not sure), which is locking you up. this is a run-time error, its just perhaps not what you wanted. I can't replicate it... my tools don't lock up over this.

try this one:

1
2
3
4
5
6
7
8
int main()
{
  { //extra brackets define a scope
   Sally so;
   so.printStuff();
  }
   cout << "end of main" << endl;
}


this is the normal c++ way: the destructor is called automatically when the variable died (went out of scope). Calling them manually is generally a 'bad idea'. There may be special times and cases for it, I don't know of any right off, but my weird deep OOP stuff is weak.

your code, calling it manually, will call it TWICE. Once when you call it, and again when it goes out of scope. If you remove the printstuff call, you will see this happen -- it will print that it is in the destructor TWICE. If you had done something worthy of a dtor, this would blow up on you if not careful, for example careless freeing of dynamic memory twice without setting null and checking null explicitly would freak out. (More evidence that calling it explicitly isnt really useful). It also shows that calling it does not really 'destroy' it, it just runs the dtor code as if it were any normal function. I can't underline that enough. This may depend on how your compiler does things... mine lets me run your code even with the print, and it all just works, so MY compiler is not destroying the object until it goes out of scope.

in other news, in c++ you have to handle your own errors more often than some other languages. So you don't need a catch block on nearly every call like in java, but on the flipside, you have to learn to throw your own when necessary. If you have a similar language the C++ try/catch/throw stuff is more or less the same syntax. You can try a try around your original to give an error message and clean it up so it does not crash/lock if you want to see that. I would have to mess with it to get a try catch that worked right for your first scenario, its tricky, and its not something I would have had to do, since ... :P
Last edited on
I thought I'd chime in starting with this point:

it is really unusual to call the destructor manually.


While true, it is far from a problem by itself. It is a technique used for special storage circumstances.

The example code is not that case, however.

It is used, for example, in the std::shared_ptr collection of classes (there are nodes inside you don't generally see). More specifically, the "std::make_shared" method for creating instances for the shared_ptr storage uses both in place construction and explicit destructor calls in order to provide custom allocation for the user's instance.


I deliberately added something that should have cause a run-time error...

I press enter but it stays frozen.


While @jib's point the undefined behavior doesn't always mean a crash, this situation is predictable.

The destructor makes not use or reference to member information. Further, the class itself defines no data members. As such, there's nothing that could cause a crash.

Factually, there's nothing dangerous to be expected in this example, and it actually does work, though it is ill advised.

First, the explicit call of the destructor functions like any other function call, in that there is no inherent de-allocation of storage. The destructor function is typically associated with de-allocation, but it does not perform de-allocation, it is a function to be called before de-allocation.

Since the destructor does nothing to the instance, it will operate any number of times without issue. This is a fragile situation, though, because if the destructor had performed other work on the instance, say de-allocating some dynamically allocated object held via smart pointer, or a string (same thing, really), then you'd expect a crash due to performing those actions twice.

The reason the software seems to hang is the use of "cin" in the destructor. Depending on the operating system one must enter a kind of termination, or some kind of string information...merely hitting return on many operating systems merely "repeats" the cin as if it had done nothing (ignored the enter). When I entered a ctrl-Z on Windows, for example, I got the remaining output expected from the subsequent double destructor call, and the program otherwise terminated normally.

In this example there is only 1 deallocation of Sally (which does not happen at the explicit destructor call). Since there are no data members, and no action taken on the instance of Sally, the call to so.printStuff is still operating on an instance (as allocated) which has not be changed from before the explicit destructor was called. We should expect this will continue to operate, but as I pointed out, it is a fragile scenario and certainly not something one should write.

The explicit call to the destructor should be reserved for custom allocation situations. For example, if a raw block of RAM were held by a void * (or char *), and the object were positioned upon some block within that memory, where in-place construction were used to initialize the object (a kind of explicit call of a constructor where the object is already allocated using "in place new"), then and only then is it appropriate to call the destructor explicitly, and then only because that storage would presumably be returned for availability within the custom memory system represented by that block of RAM.

There is no correct way to use the explicit destructor call for an object instantiated on the stack as Sally is done here, because there's no way to suppress the destructor from being called when that falls from scope.



WOW!

This has been greatly (bigly? :-) ) helpful. Jib's short explanation and Niccolo's treatise gave the the long and short of it. :-) The most helpful comment that the destructor is only stuff to do before the object is deallocated by the inner-sanctum code. Also that I would have needed to enter something besides <enter. to satisfy the cin. And I will take jonnin's advice about that additional cout.

Now, Jib asked why I'm not using the safer std::string. Well, that's 'cause I'm just picking up C++; this *is* the beginner forum after all. After I'm done with the YouTube course I'll be in a better position to use the books.

Thanks all!

-- JS
Now, Jib asked why I'm not using the safer std::string. Well, that's 'cause I'm just picking up C++; this *is* the beginner forum after all.

That's probably the best reason to be using std::string, it is after much more "beginner" friendly.

After I'm done with the YouTube course I'll be in a better position to use the books.


I'd really recommend you consider those books now, there are so many bad "tutorials" on you tube that you should consider getting a well reviewed book that teaches C++ instead of C with classes.
Now, Jib asked why I'm not using the safer std::string. Well, that's 'cause I'm just picking up C++; this *is* the beginner forum after all.

C's arrays are very low-level constructs whose behavior is inconsistent with everything else in the language. This is why the C++ community largely considers arrays (and C strings, by extension) relatively advanced features.

You did not derive the formal definition of addition before learning how to add. Nor did you learn how to construct a C++ compiler before using one.

Similarly, you should not learn how to correctly manipulate c-strings before learning to use std::strings.

You're learning stuff in the wrong order.

See the ISO C++ Foundation's FAQ for more info:
https://isocpp.org/wiki/faq/how-to-learn-cpp

In particular,
How do I start Learning C++? https://isocpp.org/wiki/faq/how-to-learn-cpp#start-learning ; and
What is the best book to learn C++ from? https://isocpp.org/wiki/faq/how-to-learn-cpp#best-book .
Last edited on
Well, that's 'cause I'm just picking up C++; this *is* the beginner forum after all.


At risk of repeating others, but for the sake of emphasis:

Char arrays are not for beginners.
If you're a beginner you should use std::string.
Char arrays are not for beginners.
Char arrays are not for beginners.

I get why people say this but an awful lot of people learned char arrays before strings existed, as one of the first things they picked up. It isn't that hard, and it teaches you early on about screwing up boundary access and tons of core understanding that is not difficult to absorb and is useful to know.

I mean you can still mess up the same things to a string...
1
2
3
4
5
int main()
{
    string hw = "hello world";
    hw[100000] = 'x';
}


I would argue learning strings first because you should be using strings in most code. Not difficulty, or babysitting features, or the like. Learning something you shouldn't be using is less productive, relative difficulty aside. If the c++ way of doing things were to use char arrays, but strings were much easier, we wouldn't be sitting here recommending learning strings, (or I would not be, at least). But it isn't. Strings are how c++ is done, and they are safer and easier to use, so its a win/win to start with string.

Last edited on
Hey guys, I'm a C and Perl programmer; some habits are hard to to shake off, especially if I don't know which ones need shaking off.

Some years ago an interviewer looked at my Perl code and correctly identified me as a C programmer picking up Perl. Same issue here. But I will now know to use std::string.

Thanks much.
-- JS
An alternative tutorial source for learning C++:

https://www.learncpp.com/

Already knowing C can put you at a severe disadvantage, similar languages that are almost entirely different in "the how" to solve a problem.

Bjarne Stroustrup talks about 5 popular myths about C++, number one is needing to learn C first.

https://isocpp.org/blog/2014/12/myths-1

If you are curious about all 5 myths, split into 3 parts:

https://isocpp.org/blog/2014/12/five-popular-myths-about-c-bjarne-stroustrup
Last edited on
Going off of what @jonnin said:
jonnin wrote:
I mean you can still mess up the same things to a string

std::string is generally safer than char arrays because they provide additional methods that allow for more safety. For example, jonnin's example could be rewritten as follows:
hw.at(10000)= 'x';
This will throw std::out_of_range. With regular char arrays you are forced to use standard array indexing which uses no bounds checking but in std::string, you have the option to check the bounds.
Topic archived. No new replies allowed.