C++ Logo

std-proposals

Advanced search

Re: Stacktrace from exception

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 28 Apr 2021 23:43:59 -0400
On Wed, Apr 28, 2021 at 3:23 PM Andrey Semashev via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> On 4/28/21 8:28 PM, Edward Catmur wrote:
> > On Wed, 28 Apr 2021 at 17:42, Andrey Semashev via Std-Proposals
> > <std-proposals_at_[hidden] <mailto:std-proposals_at_[hidden]>>
> > wrote:
> >
> > On 4/28/21 6:41 PM, Edward Catmur wrote:
> > > Ah, but you only need to save the stacktrace up to the exception
> > > handler; you can collect the remaining pointers up to thread
> > entry at
> > > the time you save the stacktrace.
> >
> > If you're going to add new elements to the list of stacktrace entries
> > then you will probably have to reallocate memory to store the added
> > data. I'm not sure this would have optimal performance.
> >
> >
> > As in constructing a vector of pointers from iterator pair and then
> > appending to it? It's pretty much close to optimal.
>
> No. Optimal would be one allocation for the whole stacktrace, with no
> reallocations later. Yet more optimal would be one allocation for both
> the exception and the stacktrace, but that couples the exception and the
> stacktrace, so probably not feasible. But the most optimal is to not
> collect the stacktrace at all, unless explicitly requested by the user.
>
> > > You're using exceptions in situations where you don't care about the
> > > stacktrace? Does that mean you're using exceptions as flow control?
> >
> > Regarding exceptions, there are definitely places where I don't and
> > won't want stacktraces collected - because the other diagnostic
> > information I attach to exceptions is far more important for problem
> > investigation than a stacktrace would be. I'm talking about data like
> > input and current state information that led to the exceptional case.
> > Together with logging, this is more useful than a stacktrace would be.
> > In other cases, the exceptions I throw are not handled by my code (e.g.
> > are passed on to the calling code, for example, via RPC). In these
> > cases, the throw site is often close to the entry point, and the
> > stacktrace would be useless there. In case of RPC, the stacktrace would
> > be discarded anyway, and in case of a direct call the caller is able to
> > (and will likely) catch the exception near the call site and log
> > whatever relevant information it has for further investigation.
> >
> >
> > But those are exceptions that you're throwing. Where stack traces come
> > in useful is investigating exceptions thrown by (standard or
> > third-party) library code.
>
> IMO, exceptions thrown by the standard library are close to useless. Not
> in least part for the poor QoI of the standard libraries in this regard,
> as many of them don't produce an informative error message with any
> relevant information. Also because the standard library cannot provide
> any context-specific details about the error (i.e. std::vector::at
> cannot know what the vector is and where the index comes from, only the
> caller knows that and can provide that information in the log). For this
> reason, you should normally code so that you check the necessary
> preconditions in your code before calling the standard library, and then
> call standard library methods that don't throw.

They are very useless... if you're using them for logging. If you're
using them for *error handling*, then they're fine. `vector::at`
doesn't need to know what the vector is or what the index means. The
code catching the exception is the one who has that context.

If the only people who catch exceptions are far away from the source
and have no meaningful context to the source of that exception, then
yes these exceptions are not particularly useful. But that's a matter
of how you choose to use them.

For example, let's say you're using some algorithm like
`std::generate` to build some container from raw materials. And in the
lambda that's generating values, you're accessing a vector. And you
know that, if an index is out of bounds, then you want to terminate
the building of the container. You *could* have the lambda set some
flag on an out-of-bounds index and make dummy objects to fill out the
rest of the container.

Or you could just use `vector::at`, and have the code that invoked
`std::generate` catch the exception. There's no loss of context or
meaning; an exception was thrown and the receiver knows what to do
with it.

This situation only becomes a problem if you try to use exceptions
outside of this local context. Like if the above code didn't catch the
exception, but allowed it to bubble up to the outside process who has
no idea that there's even a `vector` involved, let alone that an index
was out of bounds.

And indeed, this is one of the biggest arguments against making
exceptions have stack traces. When you're using local exception
handling like this, a stack trace is utterly useless. You're not using
an exception to signal the failure of an entire operation (where the
only resolution is to log the failure and move on to some other
operation); you're using it to signal a failure of something tiny.

So having a specific exception type specifically to mean "whole
operation failure", and thereby include things like stack traces make
sense.

Received on 2021-04-28 22:44:12