4

I have a class whose constructor may throw an exception. Here’s some code that will catch the exception:

try {
    MyClass instance(3, 4, 5);
}
catch (MyClassException& ex) {
    cerr << "There was an error creating the MyClass." << endl;
    return 1;
}

But of course no code after the try/catch can see instance because it’s now out of scope. One way to resolve this would be to declare and define instance separately:

MyClass instance;
try {
    MyClass instance(3, 4, 5);
}
...

except that my class doesn’t have the appropriate zero-argument constructor. In fact, this case right here is the only one in which such a constructor would even make sense: the MyClass object is intended to be immutable, in the sense that none of its data members change after construction. If I were to add a zero-argument constructor I’d need to introduce some instance variable like is_initialized_ and then have every method check to make sure that that variable is true before proceeding. That seems like far too much verbosity for such a simple pattern.

What is the idiomatic way to deal with this kind of thing? Do I need to suck it up and allow instances of my class to be declared before they’re initialized?

M.M
  • 138,810
  • 21
  • 208
  • 365
bdesham
  • 15,430
  • 13
  • 79
  • 123
  • 1
    Put all the code inside a function, call the function in the try block. – juanchopanza Jul 04 '14 at 13:54
  • 8
    That doesn't make sense. If there's an exception, there's no object, so what possible need could you have with `instance`? – Kerrek SB Jul 04 '14 at 13:55
  • 1
    "What is the idiomatic way to deal with this kind of thing?" - don't use `try`/`catch` at all, unless you can handle the failure locally. – Mike Seymour Jul 04 '14 at 13:56
  • Consider redesigning your class so that the constructor can no longer possibly encounter the error situation which makes him throw the `MyClassException` in the first place. – Christian Hackl Jul 04 '14 at 15:08
  • What are you going to do with the "failed-to-create" object? If you need to store info why it failed to create, store it in the exception. – milleniumbug Jul 04 '14 at 15:11
  • @ChristianHackl With respect, I don't think that's particularly useful advice. Throwing an exception from a constructor is the idiomatic RAII way to do things if there was a a problem creating the object, right? My object is created by deserializing a file; that the file might not be valid is a possibility I can't avoid. – bdesham Jul 04 '14 at 16:57
  • @juanchopanza I think that may be the way for me to go in this situation. – bdesham Jul 04 '14 at 16:58
  • @KerrekSB I'm coming to C++ from Objective-C, so I'm used to initializing an object, bailing if it's nil (which indicates a construction failure), or continuing with my function otherwise. I guess I'm inappropriately trying to apply that idiom here. – bdesham Jul 04 '14 at 17:03
  • @bdesham: An object cannot magically be "nil". That makes no sense (in C++, but I believe it just doesn't make sense, period). An object has to have one of the values that its type permits. If you're shopping for cars, you'd expect a Ford, a Toyota and a Mercedes, but not nil. If you suddenly find yourself unable to create a value of the desired type (e.g. because you ran out of wheels), you simply don't *have* a car; it's *not* the case that you have a car but it's some kind of "special car value". – Kerrek SB Jul 04 '14 at 17:09
  • @KerrekSB I agree. Objective-C's type system is not expressive enough to represent that; instead, all objects are pointers, and nil pointers represent uninitialized objects or the like. – bdesham Jul 04 '14 at 17:16
  • @bdesham: Well, Obj-C has a *different* type system and object model, e.g. variables in Obj-C are never objects, and the "declared type" of a variable isn't the domain from which the variable takes values, unlike in C++. That's why it's a really bad idea to transliterate code between languages rather than re-expressing the ideas *in* the new language. – Kerrek SB Jul 04 '14 at 17:32
  • @bdesham: I only said he should *consider* it. If his current design somehow needs the object later on yet allows for a situation in which the object may never be created due to some error situation, then the elimination of that error situation would be a good thing. This has nothing to do with RAII. – Christian Hackl Jul 04 '14 at 17:39

4 Answers4

13

You should be doing everything you need to do inside the try block:

try {
    MyClass instance(3, 4, 5);

    // Use instance here
}
catch (MyClassException& ex) {
    cerr << "There was an error creating the MyClass." << endl;
    return 1;
}

After all, it is only within the try block that instance has been successfully created and so can be used.

I do wonder whether your catch block is really handling the exception. If you can't do anything to resolve the situation, you should be letting it propagate.

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • 2
    What if the code that uses `instance` could also throw an exception? Would I just add additional `catch` blocks for those? My instinct was to put as little code as possible in the `try`. – bdesham Jul 04 '14 at 14:01
  • But if using instance can also throw many exceptions, is it good style to have one try-catch for all exceptions from different parts of code? It will looks better if there will be try-catch for each group of exceptions from each group of code, isn't it? – Arkady Jul 04 '14 at 14:02
  • This is for a simple command-line app. If creation of the object fails, my options are to show the user some libc++ error about an unhandled exception or to show them my more informative error message. I don't think just letting the error propagate is really appropriate in this case. – bdesham Jul 04 '14 at 14:04
  • 1
    @bdesham Regardless, if you have a `try` block, it needs to contain everything that depends on the exception not being thrown. That might even mean that it contains the entire content of the function (in which case you *could* use a [function try block](http://stackoverflow.com/q/5612486/150634)). – Joseph Mansfield Jul 04 '14 at 14:09
  • 1
    @JosephMansfield, Could, but then your code would fail a review when people don't know what it is :p – chris Jul 04 '14 at 14:13
  • 1
    @bdesham: For the code that uses `instance`, you have two options. If they throw, can other code continue to use `instance`, or other objects created in the try block? If so, then you nest another try/catch block within this try block (or better, factor it out into a function with the try/catch block in there). If you *can't* do anything with `instance` after those other operations throw, then either add more catchers to the outer try block, or let the exception fall through. – Benjamin Lindley Jul 04 '14 at 14:29
  • @JosephMansfield Yes, this code is in main, so returning from within the `catch` block exits the application. – bdesham Jul 04 '14 at 16:49
  • It makes the most sense for me to factor out the code dealing with `instance` into a separate function and then to call that function from within the `try`. – bdesham Jul 04 '14 at 17:05
  • This doesn't scale well; what if the code needs 4 such objects? It'd be an unreadable mess. – M.M Aug 02 '18 at 12:18
3

Dynamically allocate the instance using new:

std::unique_ptr<MyClass> instance;
try
{
    instance.reset(new MyClass(3, 4, 5));
}
catch (const MyClassException& ex)
{
    std::cerr << "There was an error creating the MyClass." << std::endl;
    return 1;
}
// use instance as needed...
M.M
  • 138,810
  • 21
  • 208
  • 365
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Note that you could write `MyClass& inst = *instance;` after the `catch`, in order to not need a dereference operator for future accesses of the object – M.M Aug 02 '18 at 12:22
  • Using `optional` as in my answer saves the heap allocation here, although that's not a big deal. – Eric Feb 16 '19 at 20:06
2

You could use a generic helper function that catches exceptions and the future std::optional (or boost::optional) to signal the successful or failed creation of the instance:

template< typename T, typename... Args >
std::optional< T > try_make( Args&&... args )
{
    try {
        return T{ std::forward< Args >( args )... };
    }
    catch( ... ) {
        return {};
    }
}

Using basically this:

auto instance = try_make< MyClass >(3, 4, 5);

Where instance is now an optional<MyClass>. To test the result and separate availablity of the instance from the error case is also simple:

if( auto instance = try_make< MyClass >( 3, 4, 5 ) ) {
    // use *instance, but this code is *not* in the try/catch block!
}
else {
    // creating the instance failed
}

Of course the exception information will be lost this way, but you could go for a less generic function and add some logging in the catch-block depending on your needs.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • @LightnessRacesinOrbit I can't judge the validity of OPs use-case. *If* it is valid, the answer might help. Others already pointed out that OP should check if this is really needed in the first place. – Daniel Frey Jul 04 '14 at 15:11
  • The last code block is a bit confusing; the OP's goal was that `instance` be in scope for the remainder of the function, but in that code it is scoped to the if...else block. Recommend just not having that last bit. – M.M Aug 02 '18 at 12:17
  • @M.M I don't think that was the goal. The goal was to work with the instance but *outside* of the try/catch block, so you don't accidentally catch exceptions from that code, only from the ctor. This can be achieved with my solution (as the comment in the code suggests). – Daniel Frey Aug 08 '18 at 21:25
1

A variant of Remy's answer, but saving a dynamic allocation using std::optional:

std::optional<MyClass> instance_opt;
try {
    // could use `instance = MyClass(3, 4, 5)`, but that requires you to write a move constructor
    instance_opt.emplace(3, 4, 5);
}
catch (const MyClassException& ex) {
    std::cerr << "There was an error creating the MyClass." << std::endl;
    return 1;
}
MyClass& instance = *instance_opt;
// use instance as needed...
Eric
  • 95,302
  • 53
  • 242
  • 374