2

In what seems like a deliberate design decision, C++ does not have a null value for objects and references. This makes using objects and references very elegant since we don't have to perform null checks. It also means constructors must always return an object:

void main () {
    // o is guaranteed to be non-null by c++:
    Object o = new Object();         
}

// Allowing this hypothetical "failing" constructor would break the guarantee:
Object() { return void }

Sometimes object creation has to fail, an easy example being a Connection object. Since C++ does not allow this, programmers have developed alternatives: initialization methods (with various names), static builder methods, "Factories" that do not use any polymorphism, and null objects. One reason for adding exceptions may have been as a way to go around this limitation of C++ constructors, but either way, it is a tradeoff - we avoid null checks but sometimes have awkward construction.


That explains why C++ does not allow constructors to return null. But it is insufficient when we consider newer OOP languages like Java and C#. There, references are allowed to be null, yet constructors work like C++ and must not fail. So we get to write both null checks and alternative construction schemes (having to write factories, init methods etc). What benefits does this bring?

19
  • Is asking “why” on language specs still considered as “primary opinion-based” if it can have official answers? (from the author of one the languages you ask about)
    – gnat
    Aug 14, 2018 at 16:39
  • Can you explain a bit more about what you mean in the last paragraph of your question? C# doesn't compel you to do anything in or with a constructor, except that which makes the most sense for your specific application. You don't even have to write a constructor if you don't want to; C# provides a parameterless constructor free of charge. Aug 14, 2018 at 16:47
  • 6
    Constructors don't return anything in Java or C#. The best practice for constructors that cannot succeed in these two languages is to throw an exception. Exceptions are not equivalent to return void. Aug 14, 2018 at 17:00
  • 1
    Let us continue this discussion in chat.
    – Fadeway
    Aug 14, 2018 at 17:19
  • 2
    "in C++ you literally cannot have an object be null". This is misleading. C++ can indeed do: auto x = new Class(); x = nullptr; <- This is the equivalent to Java and C#. C++ additionally allows objects on a stack: auto y = Class(); In that case "y = nullptr;" is type error. Aug 14, 2018 at 18:30

4 Answers 4

7

The case of C++

In C++ constructors are expected to succeed. The only way for a constructor to fail is to raise an exception.

Bjarne Stroustrup confirms in his book "The design and evolution of C++" that this is one of the most important aspect of exceptions (Chapter 16.5.1: Errors in constructors), as you rightly pointed out in your question.

Now imagine for a second that a constructor could fail without exceptions...

The pointer initialisation Object *o = new Object; could work with this semantic without problem with a nullptr. In fact, it is possible to get this semantic if the failure is caused by an allocation issue with nothrow.

But how could you handle the following cases:

  1. Construction of local (value) object: Object o; invokes for example a default constructor. If failed construction would be possible, you or the compiler would have to check for a void object in all subsequent uses of o. In most cases this would be a performance overhead that is justified only by some exceptional cases, which is not the design philosophy of C++
  2. Construction of a class member : for example class X { public: Object o; };. If construction of o would fail, in this simple example you could simply imagine to say that the class X object construction fails as well. But how should it work if class X would have several different members, some of them with a successful construction, and some of them with a failed object ? And what if there would be some other pointer members where the compiler cannot determine if the pointed object should be destroyed or not in case of a partially failed construction of X ?
  3. Construction of a derived class: what to do if a base class object could be successfully constructed, but the derived class object not: should the bbase class object be returned ? Or should it be destroyed ?
  4. I don't mention multiple inheritance or virtual inheritance which have similar problems.

If C++ would allow constructors to fail, there would be a lot of unsolved semantic issues, or a lot of overhead that is useless most of the time. This explains the current design option: handle exceptional circumstances exceptionally.

By the way, Object o = new Object(); is not valid in C++.

Code design issue ?

If you feel you have the need for a constructor that is expected to fail as a normal behavior, there is certainly a design issue.

May be you should then consider to use an object with a special fail state. You would then consider again that construction is always successful, but that the state of the object might not be the one that is expected. You could then check whenever needed if the state of the object is ok or not.

Java and C# cases

Java and C# also expect the constructor to succeed or to raise an exception. The reasons are very similar to those mentioned for C++. Although case 1 would not be applicable because objects are managed by reference, and case 4 not relevant because these languages do not support MI, the case 2 and 3 would cause semantic issues in case of partially created objects.

If the language was to allow constructor failure without exception, it would have to decide how to handle partially successful construction (either keep the correct member objects and let the other be nil, or destroy constructed members and return nil). But the chosen semantic might not be the correct one in every case. Exception handling allows more flexibility in this regard.

As a solution to the design smell, in addition to the special fail state already mentioned for C++, you could also easily use a factory or a builder that could return nil, for example if the construction parameter are invalid.

4
  • If your typesystem allows you to express nullability (i.e. Optional in Java, Kotlin and Swift, Nullable in C#, Maybe in Haskell, etc.), then you could have the both worlds. Straight forward happy cases, with constructors that return T, and type-safe, visible and easy to handle errors in failable constructors, that could return T?
    – Alexander
    Aug 16, 2018 at 19:18
  • 1
    @Alexander At least in Java, you'd get the worst of both worlds. With a constructor throwing, you get an exception and know exactly what went wrong. With a constructor returning Optional.empty(), you'd know nothing. With a constructor returning Optional.of(something), you could choose between silently ignoring problems using map or throwing NoSuchElementException using get(). That's all worse than a throwing constructor.
    – maaartinus
    Aug 18, 2018 at 4:28
  • @Alexander to @maaartinus' comment, I'd like to add that when you use optionals in Swift, you need to unwrap them with ! at every use (or declare the variables as implicitly unwrapped). This is the kind of overhead I was thinking of (in the first para after the numbered list). Of course, there are some use cases where this could be practical. But isn't there a difference between using optionals in the cases where they are really useful and imposing optionals everywhere ?
    – Christophe
    Aug 18, 2018 at 10:45
  • @Christophe Optionals are appropriate when there is only one failure mode, or an unknown failure mode, where an "UnknownWidgetException" might have been used otherwise. Optionals provide you many easy ways to handle the empty case. You can map them, provide a default value, force unwrap when you're sure they're not nil, etc., in a much mode convenient way than using try/catch, forward declaring a variable, assigning to it once in the happy case, again in the catch, etc.
    – Alexander
    Aug 18, 2018 at 19:13
6

The standard method of handling a constructor that fails is to throw an exception. Constructors do not typically fail, and it's usually easier to handle such failures in a separate place rather than to clutter the main logic.

If a Connection constructor fails, whatever was supposed to happen with that Connection isn't going to happen, so throwing rather than executing the rest of the function or try...catch block is appropriate.

If you want an object that might or might not exist, consider std::optional (or boost::optional if your C++ compiler is older).

1
  • 2
    Since the invention of exceptions, the general guideline is to throw an exception whenever a piece of code can't fulfill its contract. And the contract of a constructor is to create a usable instance of the class, e.g. a connection that can transfer data. Aug 14, 2018 at 21:26
3

Your example using C++ won't work because C++ has stack objects in addition to pointers.

int main
{ 
  Object x;
  x = nullptr; // x is not a pointer, this makes no sense.
}

Also, void is not a value, so you can't return or assign void. The very existence of stack variables makes the concept of a void-returning constructor nonsense. In another language that didn't allow stack objects, you could do imagine doing it:

Class OPQ
{
  Constructor(bool b) { if (b == false) return null; else return this; }
}

and then perhaps garbage collection would clean up your OPQ when the constructor exits. This means your caller would have to handle null results, but on the other hand this means such a construction would work in an exception-free programming language. (perhaps one where null-object operations are no-ops.)

So I think bottom line, there's no compulsion either way in general. C++ automatic variables preclude the notion, but a reference-based language can do as it likes. Likely C# and Java chose as they did because the language makers liked exceptions and wanted to imitate C++ to some extent.

1

Swift has no problems at all with this. Instances of classes are reference objects, you can have optional classes, so you can have initialisers that can’t fail, or failable initialisers. The implementation of a failable initialiser is somehow tricky since the language must clean up any half initialised object correctly, but that’s invisible to the programmer.

The good thing in Swift is that you can’t just ignore the fact that an optional object could be nil.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.