I am a Java developer but out of curiousity I would like to find out why is it not necessary for a method to declare an exception when coding in C++? So does it make C++ a weaker language since it enforces a weaker rule?

Edited 3 Years Ago by solomon_13000

In C (and C++) part of the language design doctrine is that you shouldn't have to pay for what you don't use. If you don't want to use exceptions, you shouldn't have to. You, the programmer, are trusted to know what you're doing and the choice of using exceptions or not is on you.

Edited 3 Years Ago by Moschops

why is it not necessary for a method to declare an exception when coding in C++?

Because the designers of C++ chose to use unchecked exceptions rather than checked exceptions while the designers of Java chose the opposite. Attempts have been made to introduce checked exceptions into C++, with less than stellar success. Further, it seems like the prevailing opinion among experts is that while checked exceptions were originally accepted as the better option, the dependency costs of enfocing them unconditionally has more of a negative impact than they're worth.

I think those in the know these days will suggest that the best option is unchecked by default and opt-in for checked when needed (which is the approach that C++ has attempted to take). Consider this interview with Anders Hejlsberg on why C# doesn't use checked exceptions like Java.

So does it make C++ a weaker language since it enforces a weaker rule?

It's quite a leap to go from "C++ supports a 'weaker' form of XYZ feature than Java" to "C++ is a 'weaker' language than Java". I could reverse that and say something equally offensive for Java programmers: Java's checked exceptions are frustrating, so Java is a more frustrating language than C++. ;) Obviously that's a hasty generalization, and it's also unfair to Java given that checked exceptions are a relatively minor feature. Conversely, it's unfair to label unchecked exceptions as 'weaker' rather than 'different'. It's no harder to write robust software in the absence of checked exceptions, though in the forced presence of checked exceptions I'd argue that it is problematic in terms of maintenance given the (in my opinion) unnecessary exposure of the underlying implementation.

Edited 3 Years Ago by deceptikon

Comments
Good points!

I would like to find out why is it not necessary for a method to declare an exception when coding in C++?

Actually, now with C++11 (latest standard), it is more than just "not necessary", it's deprecated (compilers must still accept the code, but it's highly discouraged to use explicit exception specification in your code). This is essentially a case of "lots of pain but no gain".

Specifying the exceptions that can be thrown by a function can't really accomplish much beyond just causing a hard crash if an unspecified exception is ever thrown. And because the whole thing is a dynamic mechanism (run-time), there is a run-time overhead to it and no benefit in static code optimization (like in the case of noexcept where the exception-handling code can be removed entirely) which is where traditional "native" languages like C++ shine. So, that was the "no gain" part.

In Java, since it sits at a higher level, it can have checked exceptions and can actually optimize accordingly (although modern exception implementations don't benefit from this), and the run-time overhead is eliminated by resolving this at compile-time. If C++ wanted to do the same, it would have to get further removed from native linking formats (e.g., ELF) and would have to specify at least some, if not all, binary interfaces (ABI) which it currently does not (and that is a huge move to make, and a widely debated and argued topic too). One of the advantage of designing a higher level language like Java/C# is that you can more liberally cut the ties with the traditions engrained in the low-level "native code" mechanisms, a liberty that C++ does not have.

The "lots of pain" part is the fact that exception specifications often lead to lots of annoying bookkeeping related to exceptions, i.e., bookkeeping by the programmers (not the compilers). If you add a new throw statement within a function, you need to update its exception specification, but also, you need to update either the exception specification of every other function that calls it or update their definitions to catch that exception and deal with it (or throw one of the acceptable exceptions). Now, imagine that the code that calls your functions is not within your code-base but other people's code (clients who use your library, or other departments of your company). This becomes ugly really quickly. This is the reason why, in traditional error-code mechanisms, there is usually one big list of unique error-codes that any part of the library (e.g., Win32 API, or POSIX) could generate, because then all you have to do is add entries to that list. This is also why, in C++, it is usual to use the std::exception base class for all exception classes, such that any sort of "default" exception catcher can just catch the exception by reference (polymorphic) and print out the error message (the virtual what() function). When you are enforcing that all exceptions are specified or checked, you don't have this kind of pain-free way to write code that largely ignores exceptions (as you very often do, given the "exceptional" nature of throwing an exception, and given the inherently exception-safe coding style of RAII).

And of course, more philosophically, exception specification does have a tendency to unnecessarily expose details of the implementation, or at least, make the user of a library have to worry a bit too much about some of the implementation-related corner cases that can throw exceptions. But, at the same time, it is possible to use this mechanism better, i.e., not letting implementation-related exceptions leak out of the interface and only have user-oriented and meaningful exceptions coming out of the interfaces, but this requires a lot more diligence by the programmers, and it still doesn't solve the problem of the last paragraph. In any case, exception specification (in C++) doesn't necessarily help much with the problem of exposing only user-oriented exceptions through an interface. This is a problem that is solved with lots of diligent work by the programmers, the rather weak enforcement of a set list of specified exceptions just doesn't go very far to solve that problem. You can either do the required work to catch implementation-related exceptions and throw user-oriented exceptions, or you don't, and when you don't you either have to add the implementation-related exception to the list of specified exceptions, add the implementation-related exception to the documentation, or both, or none at all. Either way, it doesn't stop you from having poor exception coding practices, and in some other ways it encourages even worse practices such as having one-type-for-all exceptions so that you don't have to specify more than one type (and at this point, you're basically cheating your way out of the feature, and reverting to error-code style mechanisms).

So, that really sums up the issue from my perspective. There's just no perfect solution. With Java-style checked exceptions, you can easily misuse them and abuse them, and even if you use them correctly, often you will have to cheat your way out of using them. With C++'s exception specification, they tried to do more of a hybrid, opt-in system, but it has too many problems and not enough benefits. At the end of the day, if you document your exceptions properly and you are diligent about catching implementation-related exception and throwing user-oriented exceptions, then that's what matters most, and I don't know that Java-style or C++-style exception specifications do any good and ensuring that you do this.

By contrast, if you look at C++ const-correctness rules, you see a much more effective compiler-programmer cooperation at enforcing semantic-interface rules.

So does it make C++ a weaker language since it enforces a weaker rule?

That's an odd statement from a Java programmer. In many other aspects, C++ has much stronger rules than Java, and Java programmers are constantly whining about that, so it's just odd that for once a Java programmer is not arguing that "weaker is better". The truth is, you could call this rule weaker in C++, but what's the point of a strict rule if the only practical thing to do with it is to cheat your way around it? Some of the stricter rules in C++, like type safety, const-correctness, some overload lookup rules, etc., are generally helpful in a kind of "compiler-programmer cooperation" way, i.e., the strict rule allows the compiler to keep the programmer in check by the rules set by the programmer. If it is difficult for the programmer to set up and maintain the rules consistently, and difficult for the compiler to enforce them correctly, then, yes, "weaker is better".

And, of course, one rule being weaker doesn't generalize to "a weaker language". You could argue that Java is much weaker on the grounds of being weakly typed, having no const-correctness, allowing reflection, having "generics" which are much weaker than templates, having non-deterministic life-time for objects, and so on.. And you could also argue that Java is much stronger in its enforcement of OOP-style programming practices. And so it goes.. Generalizations don't really make sense here.

In C (and C++) part of the language design doctrine is that you shouldn't have to pay for what you don't use. If you don't want to use exceptions, you shouldn't have to.

Most modern implementations of exceptions have the following characteristics (so-called "zero-cost exception handling", a standard part of most compilers since many years, at least, since Intel made it part of the ):

  • Static overhead for each "catch" clause.
  • Static overhead for each "throw" statement.
  • Static overhead for each exception type (obviously).
  • No dynamic overhead for the "good path" (i.e., if no exception is thrown, the code is as fast as it would be if you didn't check error conditions at all).
  • Dynamic overhead more-or-less equivalent to a few dynamic dispatches when entering the exception-handling execution path (i.e., there are a few function pointer look-ups involved in getting onto the exception handling code).

(N.B.: "static overhead" means a bit more compiled code in the overall executable (or dynamic library) and a bit more static/global memory needed when loading the executable)
(N.B.2: the performance of "as if you didn't check error conditions" is achieved through most-likely branch-prediction hints, and it's pretty safe to assume that compilers will use that optimization, something that you rarely see programmers do in traditional error-code checking code).

So, exceptions generally do not break the golden rule of C++ as much as people think, but, for sure, there is some constant overhead (i.e., the overhead if there is one throw-statement or one try-block), but most of it is pay-per-use overhead, and not as much as you think.

And, the OP's question is about exception specifications, which is not necessary for using exceptions, but yeah, if you use exception specifications, there will be some additional code for checking the code. Basically, the overhead of exception specifications is like wrapping every function body inside a try-catch block that catches all the allowed exceptions (and immediately re-throwing them) and then has a final catch-all block that calls the std::unexpected() function. In other words, it's like this:

void foo() throw(std::bad_alloc) {
  /* some code */
};

// equivalent to (at run-time):

void foo() {
  try {
    /* some code */
  } catch(std::bad_alloc& e) {
    throw;
  } catch(...) {
    std::unexpected();
  };
};

So, in that sense, exception specification has the same kind of pay-per-use static overhead, in addition to a run-time tax, in the form of checking the allowable exceptions and re-throwing, for every function boundary being traversed during stack unwinding, and that's the worse part (and that also doesn't play well with inlining). So, exception specification doesn't come for free in C++, and most people (me included) would argue that that price (and its other issues mentioned above) is too high for the little (if any) benefits it actually gives.

This article has been dead for over six months. Start a new discussion instead.