Question on this program with exception C++

  • C/C++
  • Thread starter yungman
  • Start date
  • #1
yungman
5,641
227
I am stepping through the program and I actually wrote down the steps in comment each step the debugger step through. I have a few question I still don't understand. Here is the program:
C++:
//Matching catch handler with exception
#include<iostream>
#include<string>
#include<string_view>
#include<cstring>
using namespace std;
class Trouble  {public:
    string message;
    Trouble(string_view str = " There is a problem")        // 1    7   16
    {message = str;}                                        // 2    8   17
    virtual ~Trouble()=default;//Base classes must have virtual des.
    virtual string_view getM() const { return message; }};    //                 26        42    53   61

class MoreTrouble : public Trouble
{public: MoreTrouble(string_view str=" Theres more trouble")// 5   14
    : Trouble(str) {}                                       // 6    9   15   18
      ~MoreTrouble() { cout<<" Destructor MoreTrouble.\n\n";}}; //                                    60    62

class BigTrouble : public MoreTrouble
{ public: BigTrouble(string_view str=" Really big trouble")//      12
    : MoreTrouble(str) {};                                //       13        19
      ~BigTrouble() { cout<<" Destructor BigTrouble.\n\n";}};//                                       59    63

int main()
{    Trouble trouble;    //                            Start  3    
    MoreTrouble moreTrouble;                               // 4    10
    BigTrouble bigTrouble;                                 //      11        20
    for (int i = 3; i < 7; ++i)                            //                21   30   36   46        57
    { try                                                  //                22   31   37   47
       {if (i == 3) throw trouble;                         //                23        38   48
        else if (i == 5) throw moreTrouble;                //                          32   39   49
        else if (i == 6) throw bigTrouble;                 //                          33        50
       }                                                   //                                    51
      catch (const BigTrouble& t)
       {cout<<" BigTrouble caught: "<<t.getM()<<"\n\n";}   //                               40   52   54
      catch (const MoreTrouble& t)
       {cout<<" MoreTrouble caught: "<<t.getM()<<"\n\n";}  //                24             41   43
       catch (const Trouble& t)
        {cout<<" Trouble caught: "<<t.getM()<<"\n\n";}    //                 25   27
      cout<<" End of for loop(after catch blocks), i = "<<i<<"\n\n";//            28   34        44   55
    }                                                        //                   29   35        45   56
}//                                                            //                                     58   64

Took me a while to line up the step numbers in straight column so it's easier for you guys to read.
This is the printout with steps labeled:
15.4 stepping.jpg

1) I understand in the first 20 steps, object trouble, moreTrouble and bigTrouble are created. Each inherited the member funtion getM() in line 12 and message in line 8. These are all in the objects already. How come step 25 has to jump to step 26 to access getM() in line 12? this is the same from step 41 jump to step 42. Does the objects have their own getM(), that the program doesn't have to jump to the Trouble class anymore?

2) If you look at the printout with the steps, notice Destructor is called between step 43 and 44. same as from step 54 to 55. I don't see it stepping through the destructor but it said they are executed.

3) Following from question 2 above, you can see destructors are being called again in steps 59, 60 and 61. The program calls the destructors for each object TWICE. Why?

Thanks
 

Answers and Replies

  • #2
yungman
5,641
227
I lined up the steps in column in VS program, but when copied over here, it's all out of alignments. I had to spend a lot of time working on the code to make it line up showing in post 1. In case you want to copy and run the program on VS, the one in post 1 will be all over the place in VS.

this is the program copied straight out of VS, I post it in this post so if you want to copy into VS, hopefully it will line up in VS
C++:
//Matching catch handler with exception
#include<iostream>
#include<string>
#include<string_view>
#include<cstring>
using namespace std;
class Trouble  {public:
    string message;
    Trouble(string_view str = " There is a problem")        // 1    7   16
    {message = str;}                                        // 2    8   17
    virtual ~Trouble()=default;//Base classes must have virtual des.
    virtual string_view getM() const { return message; }};    //                 26          42    53   61

class MoreTrouble : public Trouble
{public: MoreTrouble(string_view str=" Theres more trouble")// 5   14
    : Trouble(str) {}                                        // 6    9   15   18
      ~MoreTrouble() { cout<<" Destructor MoreTrouble.\n\n";}}; //                                 60   62

class BigTrouble : public MoreTrouble
{ public: BigTrouble(string_view str=" Really big trouble")    //       12
    : MoreTrouble(str) {};                                    //       13        19
      ~BigTrouble() { cout<<" Destructor BigTrouble.\n\n";}};//                                     59   63

int main()
{    Trouble trouble;    //                                Start  3     
    MoreTrouble moreTrouble;                                // 4   10
    BigTrouble bigTrouble;                                    //       11        20
    for (int i = 3; i < 7; ++i)                                //                 21   30   36   46   57
    { try                                                    //                 22   31   37   47
       {if (i == 3) throw trouble;                            //                 23        38   48
        else if (i == 5) throw moreTrouble;                    //                      32   39   49
        else if (i == 6) throw bigTrouble;                    //                      33        50
       }                                                    //                                51
      catch (const BigTrouble& t) 
       {cout<<" BigTrouble caught: "<<t.getM()<<"\n\n";}    //                           40   52   54
      catch (const MoreTrouble& t)
       {cout<<" MoreTrouble caught: "<<t.getM()<<"\n\n";}    //                 24           41   43
       catch (const Trouble& t) 
        {cout<<" Trouble caught: "<<t.getM()<<"\n\n";}    //                 25   27
      cout<<" End of for loop(after catch blocks), i = "<<i<<"\n\n";//              28   34   44   55
    }                                                        //                      29   35   45   56
}//
 
  • #3
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
One thing I noticed is that you are using MoreTrouble as a base class (BigTrouble inherits from it), but the MoreTrouble destructor is not virtual.
 
  • #4
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
1) I understand in the first 20 steps, object trouble, moreTrouble and bigTrouble are created. Each inherited the member funtion getM() in line 12 and message in line 8. These are all in the objects already. How come step 25 has to jump to step 26 to access getM() in line 12? this is the same from step 41 jump to step 42. Does the objects have their own getM(), that the program doesn't have to jump to the Trouble class anymore?
Trouble::getM() is virtual, and it is not overridden by MoreTrouble or BigTrouble. Therefore any call to MoreTrouble::getM() or BigTrouble::getM() is a call to Trouble::getM(). Note that there is no reason to make Trouble::getM() virtual if you are not going to override it in the inheriting classes.
2) If you look at the printout with the steps, notice Destructor is called between step 43 and 44. same as from step 54 to 55. I don't see it stepping through the destructor but it said they are executed.
This is because the throw statements are making copies of the objects they are throwing. In other words, throw trouble is making a copy of trouble and throwing the copy. The copy is destroyed after the corresponding catch is completed.

The reason for this is that objects thrown by throw are always destroyed at the end of the throw/catch. But local variables have a well-defined lifetime: the local variables trouble, moreTrouble, and bigTrouble go out of scope and are destroyed at the end of the scope where they are defined (in this case, at the end of main()). So the throw/catch is not allowed to destroy them and therefore must make copies!
3) Following from question 2 above, you can see destructors are being called again in steps 59, 60 and 61. The program calls the destructors for each object TWICE. Why?
See the explanation above. You are making copies and throwing them, therefore you are seeing the destruction of the copies and then of the originals.

To avoid making copies, don't define these:
C++:
Trouble trouble;
MoreTrouble moreTrouble;
BigTrouble bigTrouble;
Instead create them on demand as part of the throw statement:
C++:
throw Trouble();
throw MoreTrouble();
throw BigTrouble();
The reason this avoids a copy is that Trouble() creates a temporary unnamed object that goes out of scope at the end of the throw statement, so throw doesn't need to make a copy and therefore any reasonable compiler will not make a copy. (I'm not sure if this is guaranteed by the C++ standard.)

Simple example demonstrating the above:
C++:
class A {
public:
    A() { std::cout << "A constructor" << std::endl; }
    A(const A& a) { std::cout << "A copy constructor" << std::endl; }
    ~A() { std::cout << "A destructor" << std::endl; }
};

int main()
{
    {
        A a;
        try {
            throw a;
        }
        catch (const A& aRef) {
            std::cout << "catch1" << std::endl;
        }
    }
    std::cout << "----" << std::endl;
    {
        try {
            throw A();
        }
        catch (const A& aRef) {
            std::cout << "catch2" << std::endl;
        }
    }
    return 0;
}
Output:
Code:
A constructor
A copy constructor
catch1
A destructor
A destructor
----
A constructor
catch2
A destructor
 
Last edited:
  • #5
yungman
5,641
227
Thanks a million. I forgot the throw statement made a copy.

But I am stepping through the program, why didn't I see the program stepping to constructor to make the copy, matter of fact, stepping to destructor to destroy the copy from the throw.

You think that's VS problem that doesn't show the steps?

I am still reading your other explanations, I want to take my time and come back again.

Thanks
 
  • #6
yungman
5,641
227
One thing I noticed is that you are using MoreTrouble as a base class (BigTrouble inherits from it), but the MoreTrouble destructor is not virtual.
I did not see this first reply. No, Trouble is the base class.

BTW, after I posted the response to you, I went back and put the break on the ~MoreTrouble(), It DID stop at that step, but it doesn't display the message "Destructor MoreTrouble" that I put into the destructor. Strange. It's like executing HALF of the destructor!
 
  • #7
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
I did not see this first reply. No, Trouble is the base class.
MoreTrouble is also being used as a base class!
C++:
class BigTrouble : public MoreTrouble
BTW, after I posted the response to you, I went back and put the break on the ~MoreTrouble(), It DID stop at that step, but it doesn't display the message "Destructor MoreTrouble" that I put into the destructor. Strange. It's like executing HALF of the destructor!
I see the message Destructor MoreTrouble printed three times: see copy of your image below. How many times do you expect to see it?

1612998233024.png

Two possible reasons come to mind for unexpected step behavior using the debugger:

1. Are you compiling for Debug or for Release? If you compile for Release, the debugger will always behave weirdly because the compiler generates optimized assembly code that doesn't align well with the C++ source code.

2. If you're putting the function definition all on one line like this:
C++:
~MoreTrouble() { cout<<" Destructor MoreTrouble.\n\n";}
then the debugger might only stop on that line once, either before or after the message is printed, but maybe not both. Try putting the function body on a separate line and see if the debugger behavior changes:
C++:
~MoreTrouble()
{
    cout<<" Destructor MoreTrouble.\n\n";
}

Regarding the output message, you're using \n instead of std::endl. The latter flushes the stream, but the former does not. So you won't necessarily see the output if you put a breakpoint right after that line. If you change it to
C++:
~MoreTrouble()
{
    cout<<" Destructor MoreTrouble." << std::endl << std::endl;
}
then the message will be printed immediately (at the std::endl).
 
Last edited:
  • #8
yungman
5,641
227
One thing I noticed is that you are using MoreTrouble as a base class (BigTrouble inherits from it), but the MoreTrouble destructor is not virtual.
Yes, I looked back the original program in the book, it doesn't even have ~MoreTrouble(){}. I added it into output a message. I did not put virtual.

Why is this important to put virtual?

Yes, MoreTrouble is base class for BigTrouble.

thanks


EDIT: I tried putting "virtual" in front of ~MoreTrouble(){}, it makes no difference in the output.
 
Last edited:
  • #9
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
Yes, I looked back the original program in the book, it doesn't even have ~MoreTrouble(){}. I added it into output a message. I did not put virtual.
...
Why is this important to put virtual?
...
EDIT: I tried putting "virtual" in front of ~MoreTrouble(){}, it makes no difference in the output.
Yes, in your case it doesn't make a difference because you're not managing a BigTrouble object via a pointer to MoreTrouble. An example where it would matter is as follows:
C++:
// Perfectly legal to use BigTrouble
// via a pointer to its base class MoreTrouble
MoreTrouble* pMT = new BigTrouble;
...
// But now when we want to destroy the object, this will invoke
// ~MoreTrouble, not ~BigTrouble, even though the object is of type
// BigTrouble! This is because ~MoreTrouble is not virtual.
// If BigTrouble allocated any resources that need to be freed in
// ~BigTrouble, then they will not be freed and there will be
// a resource leak! (e.g. memory leak)
delete pMT;
By the way, the problem isn't limited to dumb pointers. The same situation occurs if you use a smart pointer:
C++:
int main()
{
    std::unique_ptr<MoreTrouble> pmt = std::make_unique<BigTrouble>();
    ...
    // use the BigTrouble object via a MoreTrouble pointer, perfectly OK
    ...
    // but now at the end of scope, the unique pointer is destroyed,
    // and this calls ~MoreTrouble instead of ~BigTrouble because
    // ~MoreTrouble is not virtual.
}
 
  • #10
36,854
8,887
2. If you're putting the function definition all on one line like this:
C++:
~MoreTrouble() { cout<<" Destructor MoreTrouble.\n\n";}
then the debugger might only stop on that line once, either before or after the message is printed, but maybe not both. Try putting the function body on a separate line and see if the debugger behavior changes:
C++:
~MoreTrouble()
{
    cout<<" Destructor MoreTrouble.\n\n";
}
This has been explained previously in other threads on why it isn't a good idea to compress the code...
The debugger doesn't step through multiple statements on a single line, including code where the function header and its body on a single line.
 
  • #11
yungman
5,641
227
Thanks for the replies
I change this:
C++:
~MoreTrouble()
    { cout<<" Destructor MoreTrouble.\n\n";}
Still jump right pass it.
 
  • #12
yungman
5,641
227
I tried again, this time I did it like this.
C++:
{public: MoreTrouble(string_view str=" Theres more trouble")// 5   14
    : Trouble(str) {}                                        // 6    9   15   18
      ~MoreTrouble()
      {
          cout<<" Destructor MoreTrouble.\n\n";
      }

Exactly the same thing. Doesn't make a dent of a difference.

debugger in VS just doesn't step through it. This is really my question.

Seems like it's VS.
 
  • #13
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
What if you put a breakpoint on the line
C++:
cout<<" Destructor MoreTrouble.\n\n";
Does the debugger stop there if you do that?
 
  • #14
yungman
5,641
227
What if you put a breakpoint on the line
C++:
cout<<" Destructor MoreTrouble.\n\n";
Does the debugger stop there if you do that?
Yes.

I did try to break on the destructor, it will break. I am talking about if I step through the program, it will not stop at the destructor, it just skip pass it like I described in the first post.

Like I said, it will print the message in the destructor, but it will not stop at the destructor. I am starting to think it's VS at this point.

What you responded in post 3 makes sense, maybe I should let it go at this point. It obviously did invoked the destructors like you said that the throw made a copy and it will be destroyed as soon as the throw and catch is done.

I don't know why MoreTrouble was destroyed 3 times. That's a good question.

Thanks
 
  • #15
36,854
8,887
If you're stepping through a program in VS with F10 or the Step Over menu item under Debug, VS won't go into function bodies. To see better what's happening use F11 or Step Into menu item to get VS to show you what happens inside functions.
 
  • #16
yungman
5,641
227
If you're stepping through a program in VS with F10 or the Step Over menu item under Debug, VS won't go into function bodies. To see better what's happening use F11 or Step Into menu item to get VS to show you what happens inside functions.
I always use F11, I don't even know about F10.

Thanks
 
  • #17
36,854
8,887
The best way I know of to see if a certain line of code is executing is to put a breakpoint on that line. Also, for very small programs, I might step through the code line by line, but for longer ones I'll put a breakpoint at the place where I want to start looking more closely. Then I hit F5 to get to that breakpoint and then use either F10 or F11, depending on whether I need to trace into function calls or not.
 
  • #18
yungman
5,641
227
The best way I know of to see if a certain line of code is executing is to put a breakpoint on that line. Also, for very small programs, I might step through the code line by line, but for longer ones I'll put a breakpoint at the place where I want to start looking more closely. Then I hit F5 to get to that breakpoint and then use either F10 or F11, depending on whether I need to trace into function calls or not.
I did mention in post 6, if I put a break point on the destructor, it WILL stop at the destructor.

My question is when I step through the program ( break anywhere before the destructor and step with F11), then it will not stop at the destructor. BUT it will still printout the message of the detructor(meaning it did execute the destructor). This is really for my information, why the stepping won't stop at the destructor that was clearly executed.


Thanks
 
Last edited:
  • #19
36,854
8,887
No idea, but possibly the compiler is inlining the destructor code. What I do know is that in any of your catch blocks, after the output statement, there is a whole lot of code that executes (looking at the disassembly code). Maybe someone here knows the answer to your question, or maybe not. My advice is that if the debugger doesn't seem to be stopping at a line, but it executes anyway, put a breakpoint there.
 
  • #20
yungman
5,641
227
No idea, but possibly the compiler is inlining the destructor code. What I do know is that in any of your catch blocks, after the output statement, there is a whole lot of code that executes (looking at the disassembly code). Maybe someone here knows the answer to your question, or maybe not. My advice is that if the debugger doesn't seem to be stopping at a line, but it executes anyway, put a breakpoint there.
Thanks Mark

If you don't know, that's good enough for me as you are about the most knowledgeable person.( Now I don't mean to put down anyone here, I just say Mark is very very knowledgeable)


That's why I start to wonder maybe I should let this go. Maybe I am looking too close, having too much fun learning from Jtbell that time and keep looking at where the program goes exactly. I sometimes even go further and look at the addresses also to verify. Over all, the program is doing exactly what it supposed to do.

Thanks
 

Suggested for: Question on this program with exception C++

  • Last Post
3
Replies
73
Views
3K
  • Last Post
3
Replies
89
Views
3K
Replies
35
Views
2K
Replies
1
Views
413
Replies
12
Views
255
Replies
1
Views
626
Replies
2
Views
457
Replies
9
Views
835
  • Last Post
Replies
2
Views
872
Top