1. 5

  2. 2

    From a soundness perspective, not only can you not throw exceptions from constructors, you cannot call any methods at all from a constructor. This is because all methods may assume that this is already initialized, but this is obviously unsound if you’re calling from a constructor, whose job is to initialize the object. This is precisely why Rust constructors don’t allow for anything beyond providing fields.

    One small debate on this approach: normally a benefit of constructor methods is that you can write directly to already-allocated memory, but Rust’s sound approach forces you to allocate the fields separately before passing them to the constructor via move or copy. The counter-argument is that Rust’s approach is much more amenable to optimization, and that the optimizer will give you code that’s just as good (if not better) compared to your hand-optimized constructor method.

    1. 2

      The second you ban exceptions, you aren’t coding in C++ anymore.

      Quoting Stroustrup:

      The origins of exception handling lie in the problems experienced managing a variety of error-handling approaches, such as C’s errno, error-states, callbacks, return codes, and return objects. In addition, it was observed that there were no really good approaches to reporting errors detected in constructors and in operators. I and others were very aware of the literature on exceptions and error handling stretching back to Goodenough’s pioneering work. Before exceptions were adopted into C++, many standards meetings had sessions devoted to the topic and industry experts from outside the committee were consulted. Implementation alternatives were examined, and ideals were articulated. A relatively brief description can be found in D&E. The integration of exception handling with constructor/destructor resource managements in the form of RAII is one of the major achievements of C++ – still not fully appreciated by people relying on error-code based error handling.

      The language was constructed in a way that exceptions and ctors/dtors go hand-in-hand. If you remove one, the others stop making sense.

      1. 1

        Counter-arguments can be found here.

        I know little about c++ and have no opinion on this topic, but after reading this article and this comment I wanted to know why companies like google do disable exceptions, and I thought others might want this info too.

        1. 1

          Quoting from your link:

          On their face, the benefits of using exceptions outweigh the costs, especially in new projects.

          The only reason Google does not have exceptions is legacy, and legacy is never a valid technical argument, it’s at best a social one.

          1. 3

            Pretty much every large C++ codebase I’ve worked on disables exceptions, for several reasons:

            • Exceptions make every call a conditional branch from the perspective of the compiler and so impedes some optimisations.
            • Exceptions require RTTI, which adds a lot to the binary size.
            • C++ has only unchecked exceptions, which make it impossible to statically verify whether errors are handled. Explicitly returning an option type does not have this limitation. LLVM has some nice templates for this that abort the program if you don’t check the error (which can include propagating the error to the caller via the move constructor).

            The last of these is the really important one. Getting error handling right is one of the most important things for reliability and in a C++ codebase with exceptions it is impossible to audit a function to ensure that every error condition is either handled or propagated to a caller that knows that it needs to handle it. If you disable exceptions then you can do this kind of local analysis: the function either checks the return values of things that can fail or returns the option type.

            The list of pros in the list are not really compelling to me:

            Exceptions allow higher levels of an application to decide how to handle “can’t happen” failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.

            Except that C++ has unchecked exceptions and so reasoning about what exceptions are thrown and making sure that they’re caught is very hard. The compiler doesn’t help and you end up needing catch(...) { /* no idea what do do here! */ } everywhere in your code if you want it to actually be exception safe.

            Prior to C++17, you could put throws() at the end of your function and the compiler would insert code that would prevent any exceptions propagating out of your function. This was unpopular because it made codegen worse and binaries bigger. In C++17 this was replaced entirely with noexcept that simply says it is UB if you throw an exception out of the function, so in C++17 exceptions introduce a new way of adding UB into your codebase.

            Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.

            Other languages typically have checked exceptions. In Java, for example, the exceptions that can be thrown are part of the function signature. If you don’t explicitly catch an exception in a function that you call or advertise it on the list that you throw, then your code doesn’t compile.

            C++ has a feature that is somewhat like a feature in other languages but with limitations that mean that it doesn’t have the advantages that it has in other languages is not a great reason.

            Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.

            Possibly an advantage, though it’s very rare for me to find a library that I want to use that requires exceptions (and when I do, the above problems make me sad and glad that I disable them everywhere else).

            Exceptions are the only way for a constructor to fail. We can simulate this with a factory function or an Init() method, but these require heap allocation or a new “invalid” state, respectively.

            This is true and is, in my mind, the only benefit of exceptions in C++. I see this more as a limitation of the language though. Allowing constructors to have explicit return types would fix this.

            Exceptions are really handy in testing frameworks.

            Uh, okay? I’ve never wanted to use exceptions in a testing framework.

            Oh, and using factory methods doesn’t preclude on-stack allocation (it does preclude subclassing, though you can also make your constructor protected if you want to allow subclassing). Here’s an example:

            class Example
                    int x;
                    Example(int x) : x(x) {}
                    static bool validate_constructor_args(int x)
                            return x < 42;
                    Example(const Example &other) : x(other.x) {}
                    static std::optional<Example> create(int x)
                            if (!validate_constructor_args(x))
                                    return std::nullopt;
                            return Example{x};

            You can create instances of this on the stack with:

            auto x = Example::create(12);

            You now have an on-stack option type that either contains an Example if you passed valid arguments to the constructor, or doesn’t if the arguments were invalid. No heap allocation here at all and it’s trivial for me to audit this use and see if you tested whether the constructor failed.