How to instantiate an object in a try block, but keep it in scope?

Pages: 12
closed account (Ezyq4iN6)
What I have is a class with some various if statements in the constructor that test conditions and throw exceptions if they are not met. In my main the object instantiation is inside of a try block, but the problem is that it is only in scope for that block and cannot be used later, which kind of defeats the point.

So my question is: how can I use the try block (or other simple solution) to test the conditions during object creation and still use that object later in the program? See my sample code (shortened for brevity) below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

  // Inside main.cpp (code fragment)
  try
  {
    My_Class my_object;
  }
  catch (int x)
  {
    if (x == -1)
    {
      std::cout << "You have an error!";
      return 1;
    }
  }
  my_object.my_method(); // Error: 'my_object' was not declared in this scope.

  // Inside My_Class.cpp (constructor only)
  My_Class::My_Class()
  {
    if (!condition_1)
      throw -1;
    else
    {
      if (!condition_2)
        throw -1;
      else
      {
        // ... other conditions and then finally ...
        if (!condition_n)
          throw -1;
      }
    }
  }


Based on what the program will be doing, these conditions must be true in order for the program to be able to function, so I want it to quit immediately if any of them are false. I have read that it is considered good form to quit from main, so that is why I have the catch statement instead of attempting to do it from within the constructor.
1
2
3
4
5
6
7
8
9
10
11
12
13
  try
  {
    My_Class my_object;
    my_object.my_method();
  }
  catch (int x)
  {
    if (x == -1)
    {
      std::cout << "You have an error!";
      return 1;
    }
  }
closed account (Ezyq4iN6)
Thank you for your quick reply. I sent a reply, but I believe it was lost when I lost my Internet connection temporarily, so my apologies if the other one shows up later.

I think that is a fine solution for one or two methods being used, but what if I need to use the object later in the program? I suppose I could have my entire main (except for the catch block) inside of the try block if you think that would be alright.

An example of what I may want to do with something like this is to create an object for using the SDL library. My idea is to have the constructor start up SDL and any related components, test them, and then have the program fail via exception if something goes wrong. Later in the program I would have a main loop that would call various methods such as graphics display, playing audio, or even processing input. I could do all of that if I keep the main loop within the try block, but is it possible to do it so that the object can exist outside of the try block too?

My only real alternative that I see is to reduce the constructor to either a blank or defining a few key variables (where failure is unlikely). I would then have a separate init() method for starting up SDL and its related parts and that call would exist inside the try block instead. I have gotten some negative feedback about this solution not being proper coding technique, but what do you think? Sample below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

My_Class my_object;

try
{
  my_object.init();
}
catch (int x)
{
  if (x == -1)
  {
    std::cout << "You have an error!";
    return 1;
  }
}

// Main program loop.
while (!quit)
{
  my_object.check_input();
  // Program logic and other code.
  my_object.display_screen();
}
Last edited on
I suppose I could have my entire main (except for the catch block) inside of the try block if you think that would be alright.

I think this is the right solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  // Inside main.cpp (code fragment)
  My_Class *my_object = nullptr;
  try
  {
    my_object = new My_Class;
  }
  catch (int x)
  {
    if (x == -1)
    {
      std::cout << "You have an error!";
      return 1;
    }
  }
  my_object->my_method(); // Error: 'my_object' was not declared in this scope.
  ...
  delete my_object;
You could also create your object inside another function and return it, if necessary by means of std::optional.

Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <optional>


static bool condition_1 { false };
static bool condition_2 { false };
static bool condition_n { false };


struct MyClass {
    MyClass();

    void myMethod();
};


MyClass::MyClass()
{
    if ( !condition_1 ) {
        throw -1;
    }

    if ( !condition_2 ) {
        throw -1;
    }

    // ... other conditions and then finally ...
    if ( !condition_n ) {
        throw -1;
    }
}


void MyClass::myMethod()
{
    std::cout << "MyClass::myMethod() has been invoked.\n";
}


std::optional<MyClass> createMyClassOrThrow();


int main()
{
    auto my_instance { createMyClassOrThrow() };
    if( my_instance ) {
        std::cout << "An instance of MyClass has been created.\n";
    } else {
        std::cout << "No instances of MyClass have been created.\n";
    }
}


std::optional<MyClass> createMyClassOrThrow()
{
    try {
        MyClass my_object;
        my_object.myMethod();
        return my_object;
    } catch (int x) {
        if (x == -1) {
            std::cout << "Error creating an instance of MyClass!\n";
        }
    }
    return {};
}


Please, do not ask about theoretical code: provide a compilable example, instead.
@opisop, @Peter87 has a good point in agreeing. If your exception can't be fixed (that is, your only real option is to log/print a message and exit), you can surround a large portion or even your entire application with a try.

@dhayden has a good and typical solution, though we might have a chat about raw vs smart pointers, that's a common solution.

I realize these are just experiments, throwing integers, but I hope you know most exceptions say more about what's gone wrong, and usually (or should likely) derive from std::exception.

Another related point is that there is an exception that COULD happen (and is very unlikely in such example code)....the allocation could fail and you might get a bad allocation exception, which isn't going to be caught by catch of an integer.

Exceptions have some performance cost, and they are intended for "exceptional" (seriously bad) circumstances. Most coding standards discourage overuse when the exception represents a simple error which could be represented without an exception. This coding "rule" means most exceptions do tend to exit the program.

Yet, if you are going to be able to fix the problem and continue, you would require a plan. That may be where you head next in your exploration.

What if an exception could be fixed and you could try again. How would that be formed in your example?

Might you "try/catch" inside the constructor so all recoverable errors are dealt with there, and throw only if they can't be fixed?

If you could fix the problem but let that "fix" happen in the catch you have now, how would you try again?

@dhayden's suggestion leaves that possibility open, but that depends on whether it is best to fix outside the constructor or inside the constructor.

These kinds of things come up as design patterns in the use of exceptions, and I recall at least one or two chapters in good texts spending some time on it.

Then, you have to cover "exception safe code". There are issues to consider about the state of objects under the situation of a stack unwinding when an exception has fired. It is a separate chapter on exception safety in some texts, and is a worthy study. Without it, the only exceptions you can handle are those that exit, and even then you can occasionally end up with exceptions causing exceptions (causing exceptions....) if the objects involved are not exception safe.

Not to worry, though, it is all comprehensible after a few reads, and is in your near future in your study of the subject. A good read is found through an Internet search for Exception Safety, where you'll find Stroustrup's paper on the points involved.


Last edited on
C++ makes life easy for you here. Even for throwaway code, I often write main() as:

1
2
3
4
5
6
7
8
9
int main(...) try
{
  // do stuff
}
catch (const std::exception& e)
{
  std::cerr << e.message << "\n";
  return 1;
}

This is much nicer than a hard crash from the unhandled exception handler.

It is also worth your time to take a look through the exception types, but std::runtime_error should be your knee-jerk goto (#include <stdexcept> ).

Remember, an exception is meant for exceptional problems — those that cannot be handled gracefully / those that you cannot recover from. You should endeavor to avoid using them normally.

For example, a text editor attempting to write a file should not crash when the OS says “no”. Hence, opening a std::ofstream does not throw an exception when it fails.*

Based on what the program will be doing, these conditions must be true in order for the program to be able to function, so I want it to quit immediately if any of them are false.

Fair enough. Still, knowing absolutely nothing about your program, I will add that you should, as a general rule of thumb, avoid die-on-failure constructs in library code. That is, let the class’ user decide whether or not to throw if the object is not valid.

There is no fixed rule for this, though. You know your code’s requirements best, so choose what is most correct.

Hope this helps.


* (by default; you can change the behavior if needed)
Last edited on
Duthomhas wrote:
This is much nicer than a hard crash from the unhandled exception handler.

Nicer for the end user perhaps but when debugging I find an unhandled exception much easier to debug because it gives me a full stack trace.

Remember, an exception is meant for exceptional problems — those that cannot be handled gracefully / those that you cannot recover from.

I disagree. Exceptions are for recoverable errors. If there is no sensible way to recover you could just as well abort the program right away. This is what happens when standard assert fails, when C++20 contracts fails (by default), or when trying to throw from a noexcept function. Integer division by zero is technically undefined behaviour but will also normally just abort the program.

For example, a text editor attempting to write a file should not crash when the OS says “no”. Hence, opening a std::ofstream does not throw an exception when it fails.*

It is possible to enable exceptions for std::ofstream so that it throws on failure. That doesn't necessarily mean the program has to crash when it happens. The program could catch the exception, show an error message to the user, and then continue running the program.
Last edited on
This is the problem about theoretical code: every answer raises more questions.

Do we agree it is better to throw an exception in the constructor than having an object in an invalid state?
Even if we agree, how many examples of invalid states have we met apart from memory allocation?
If the object cannot acquire a resource different from memory (cannot open a file, access a database…), usually it can be solved asking the user or by further investigation (is the file read only? Is the server running? …).
But if there’s no free memory, there’s normally very little the user can do.

To sum up, I think in general we don’t need to throw in the constructor. The user is usually expected to read the documentation and use the object the right way (like checking if the file is opened before using it).

In this case, it seems the object ‘valid state’ depends on a large range of conditions. To my eyes, it simply looks bad design. If I were in the OP’s shoes, I’d reconsider what I’m doing rather then searching convoluted workarounds.

I disagree. Exceptions are for recoverable errors. If there is no sensible way to recover you could just as well abort the program right away.


@Peter87,

This somewhat disagrees with much of the literature.

I must grant that the subject has a range of opinions, but I focus here what I read from the literature on the subject, and of course I prefer Stroustrup where others might not.

I also want to to be clear that I'm not trying to start a debate or that I'm attempting some kind of correction to your point, but to expand on the fundamentals of the subject for readers finding this thread interesting.

In most literature there is no tendency to focus on recoverable errors for exceptions, but of all errors that are not best handled by return values logically and conveniently.

Stroustrup opens the discussion by stressing that the fundamental idea of exceptions is the separation of detection of an error from the handling of an error. That's a far cry from focusing on recoverable errors, though it certainly doesn't discourage recovery.

Stroustrup further stresses that throwing an exception is implied when a function can't handle an error, because when that's true the function can't return normally. The reverse of that logic is that if the function can handle the error and can return normally, exceptions are not implied to solve the problem. This is what implies exceptions are for exceptional situations implied by the inability to return normally from a function.

Uncaught exceptions terminate anyway. That's nearly automatic, but it means either the programmer simply didn't catch that particular error or that it is of such severity that there's no recovery. One might hope that layers of try (from callers of code higher up) may catch exceptions not caught more locally, but the further out from the error's occurrence a catch is located the less likely it can fix the problem (usually).

The implied objective in the context of severe errors is to attempt to log and describe the unrecoverable error so it can be reported back to engineering, leading to a maintenance cycle. Feedback is an implied purpose of these types of exceptions even where recovery from the error isn't possible. There were older mechanisms supporting this notion, but C++ exceptions offer leverage.

In the mix is also the implication of saving application data upon such an unrecoverable exit. If the coding standard for the application provides "the basic guarantee" of the application's data, it may be possible to save work in "recovery storage" on the way toward termination. This is better if "the strong guarantee" can be employed, especially for those objects/storage representing user data. This is partial recovery, but not of the running application - that may be a lost cause. This is an attempt to save user's work.

For those reading not yet familiar with exceptions, there's an entire subject of writing exception safe code you'll want to search and read about, which are reflected in the patterns of the basic and strong guarantees (leaving data intact in the presence of exceptions), or the no-throw guarantee (of functions like swap). Without attention to the detail of this part of the subject, recovery from exceptions becomes less likely. It is part of exceptions one considers even when NOT writing try/catch in the code, but of everything else we write when exceptions are imposed upon that code.

That's what complicates the subject relative to @Peter87's point. Stroustrup stresses that when mixing older libraries (especially C libraries), the ability to offer such guarantees is limited or impossible. This makes it more likely exceptions can't recover, but must terminate.



there is some aweomeness in this thread!
that said, is this a good place to just C it and move on? Its crude, its simple, but it may just get it done with minimal fuss. Granted it moves the pointer out of the block, which may be a deal breaker?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    My_Class *my_object = nullptr;
  // Inside main.cpp (code fragment)
  try
  {
    my_object = new My_Class;
  }
  catch (int x)
  {
    if (x == -1)
    {
      std::cout << "You have an error!";
      delete my_object;
      my object = nullptr;
      return 1;
    }
  }
   if(my_object)
  *my_object.my_method(); // Error: 'my_object' was not declared in this scope.

Last edited on
some things I don't like about that:
- line 5: unnecessary dynamic memory allocation
- line 12--13: not needed, but perhaps feels wrong without them...
- line 17: «tell, don't ask» having to check for null every time is error prone
- line 20: memory leak
- line 7--16: quoting Niccolo quoting Stroustrup «the fundamental idea of exceptions is the separation of detection of an error from the handling of an error», so that fails.

it ends up being similar to using an .init() function and check its return value as an error code,
except that you use exceptions (expensive) and dynamic memory allocation (expensive and error prone)
@Niccolo

My opinion is a lot inspired by Herb Sutter's P0709 paper ("Zero-overhead deterministic exceptions"):
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r2.pdf

On page 30 (4.3.1) he has the following table:

                                           What to use           Report-to handler  Handler species

1. Corruption of the abstract  machine     Terminate             User               Human
(e.g., stack overflow)

2. Programming bug                         Contract,             Programmer         Human
(e.g., precondition violation)             default to terminate  

3. Heap exhaustion (OOM)                   ?                     ?                  ?

4. Recoverable error that can be handed    Throw exception, or   Calling code       Code
programmatically (e.g., host not found)    return error code     

5. Alternate success (e.g., used smaller   Return success, this  Calling code       Code
buffer, call me again for the next chunk)  is not an error       
Last edited on
One thing bothers me about this whole thread. It seems to me that the original issue is exactly what's required by RAII, and the fact that we're having such a long discussion on how to deal with it is a good argument against RAII in the first place.

Perhaps what I'm really asking is for an example of how RAII is supposed to be used.
In my mind the original question has already been answered, but perhaps it was not motivated enough? To make good use of RAII you simply put the declaration of the variable and all the code using it inside the try block. You, dhayden, suggested another non-RAII approach so maybe you want to tell us what is wrong with the first approach?
Last edited on
The point of RAII is that the object does not exist, never had existed, if its constructor couldn't acquire resources/establish invariants. So the premise of the original question "it is only in scope for that block and cannot be used later, which kind of defeats the point" is wrong: it cannot be used later because it is exactly the point.

RAII is supposed to be used without any tests:
1
2
    My_Class my_object;
    my_object.my_method();
.
RAII is supposed to be used without any tests:

There's still a test, but it's in the form of a try/catch block. So your (Cubbi's) example should really be:
1
2
3
4
5
6
try {
    My_Class my_object;
    my_object.my_method();
} catch ( whatever ) {
    deal_with_error();
}


There's still a test, but it's in the form of a try/catch block.

Unnecessary.

RAII simply means that an object cleans up after itself when it goes out of scope, whether that scope be a try..catch block or a normal procedure body, or even just a spare block surrounded by curly braces. Cleanup happens whether there is an error or not.
Hi opisop


Are you sure there isn't boiler plate code in the initialisation of SDL that deals with these problems? I would be surprised if there isn't.


In another framework such as Qt, the main.cpp has a call to Application.exec() and very little else. You could do the same thing, have a try block in the member init list, and other tests in the ctr body. If any of them fail the program would terminate.


I guess this is something similar to what Cubbi and Duthomas are saying.
Pages: 12