-1

I have been thinking for some time now about this question, but I have not found an answer online satisfactory enough, yet. So here I am.

Assumptions

For the sake of clarity let us restrict ourselves to C++11 and a custom object. By custom object, I mean a user-defined class that the developer has full control of. All the code snippets below are not meant to be compilable or even syntactically correct. They just illustrate a concept.

Boundary conditions

Our object has a nontrivial constructor that can throw exceptions if an error is encountered. When constructing this object I would like to catch and deal with the exceptions as close as possible to the object creation point, to make the code more readable and catching only the constructor exceptions and nothing else.

Example 1

This example is not ideal because this is exactly what I am trying to avoid: dealing with constructor exceptions far away from the constructor.

class MyClass {
public:
  MyClass() {
    throw 1;
  }
}

int main() {

  try {

    MyClass my_obj;

    try {
      // Do something with my_obj that may throw
    } catch (...) {
      // deal with exceptions
    }

  } catch(...) {
    // deal with constructor exceptions
  }
}

Example 2

Here I use a std::unique_ptr to separate the object declaration and initialization. The downside is that I now create the object on the heap instead of the stack even if I have no strong reason to do that.

class MyClass {
public:
  MyClass() {
    throw 1;
  }
}

int main() {

  std::unique_ptr<MyClass> my_obj_ptr;

  try {
    my_obj_ptr = boost::make_unique<MyClass>();  
  } catch (...) {
    // deal with constructor exceptions
  }

  // continue to use my_obj
}

Example 3

Modify the object internal state and check that.

class MyClass {
private:
  good_internal_state;

public:
  MyClass() : good_internal_state(true) {
    try {
      throw 1;
    } catch(...) {
       good_internal_state = false;
    }
  }

  bool IsInternalStateGood() {
    return good_internal_state;
  }
}

int main() {

  MyClass my_obj;
  if (!my_obj.IsInternalStateGood()) {
    // Do something
  }

  // continue to use my_obj
}

Right now I am leaning towards the Example 2 case but I would like to know what is the most syntactically correct way to accomplish what I want.

  • Can/does the class have a cheap move-constructor? – Lukas-T Mar 22 '20 at 07:59
  • Let us assume that, if the class is managing a resource (heap memory, file, socket, etc), it has a move constructor implemented using the [copy-and-swap idiom](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). Otherwise, it has the default move-constructor. – LastStarDust Mar 22 '20 at 08:05
  • Let's say construction of the `MyClass` fails. In all of the cases, what do you expect the code labelled like `// continue to use my_obj` (or equivalent) to do when there is visibility of an object named `my_obj`, that has not been properly constructed? If a constructor throws an exception, then the object being constructed effectively has never existed. The point of throwing an exception in a constructor is to indicate that the object cannot be constructed. – Peter Mar 22 '20 at 09:43
  • @Peter Exactly as you say, I cannot use an object that has not been created. I would probably report the error and return or something like that. Please, do not overthink the examples that I have written. They are not meant to be logically correct. They just show different ways of catching constructor exceptions. – LastStarDust Mar 23 '20 at 02:01
  • My point is that an aim to catch an exception "as close as possible to the object construction point" is based on a faulty premise. The purpose of an exception is to signal an exceptional error condition when the code that detects the error can do nothing to correct the cause. If an exception handler can be "close" to the throw point, there is no point in catching an exception AT ALL, since it means it IS possible to correct the cause at that point and, logically, means the exception should not be thrown at all, let alone caught. – Peter Mar 23 '20 at 08:58
  • I understand your point and completely agree with you. BUT I am not asking how to catch an exception near the point it is thrown. I am only asking how to catch an exception near the constructor when the exception is thrown inside the constructor. What is happening inside the constructor is not specified. The object might have a very complex and large constructor and so on so forth. – LastStarDust Mar 23 '20 at 14:22

1 Answers1

1

I wouldn't say any of those versions is more or less correct (except for some typos). Example 2 looked like the one I would use. Here scoping seems to be the largest problem. The variable needs to declared outside of the try but must be initialized inside. If you don't like to use the heap std::optional could be usefull:

int main() {

  std::optional<MyClass> maybeMyClass;

  try {
    maybeMyClass.emplace(/*constructor parameters*/);
  } catch (...) {
    // deal with constructor exceptions
  }

  // continue to use my_obj
  maybeMyClass->foo();
}

Allthough the syntax could imply otherwise, the value managed by std::optional is allocated as the footprint of this std::optional (on the stack in this case).


You could also use a factory function createMyClass that would return a std::optional, this would require MyClass to have a move constructor, which shouldn't be to expensive.

Lukas-T
  • 11,133
  • 3
  • 20
  • 30