Well, the first thing to understand is that it's not actually "zero".
It's "small", even when no exceptions are thrown. And it can be
considerable overhead when exceptions do occur, but that's OK, because
it is in line with the C++ exception handling philosophy.

I've worked on three C++ compilers in the past four years, and these
three all use an algorithm at least roughly equivalent to the following:

The compiler generates an "exception table" data structure, where
each entry has a start address, an end address, the handler address,
and a code that tells what kind of entry this is, and some other
info (e.g., the catch type). Some operating systems can leave
these tables on disk, and never page it in unless an exception actually
occurs. Some operating systems can't easily do this optimization (*1).

The linker collects and sorts these exception tables, and maybe
performs relocation (*2) on them, if the code in question is going
to be in a shared library.

The compiler has to notice that any function call within a try
block can also be a jump to the corresponding handler, and
accordingly must preserve register values, and possibly inhibit
code motion / instruction scheduling -- optimizations that might
be possible when compiling with exceptions disabled (*3).

Normal code ignores the EH tables, and runs pretty much full speed.
But when an exception is thrown, the "throw" code has to interpret
the tables, walk back up the stack to see what "catch" is relevant,
then copy the thrown object into the catch variable, unwind the
stack, restoring the state of the function that caught the exception,
and then transfer control to the handler.

So the marked points:
(*1) the OS might be unable to avoid loading the EH table data;
(*2) the linker might need to perform relocation on the tables; and
(*3) some optimizations are not possible;
are the main contributors to the small overhead that is caused by the
use of exceptions. That small overhead == zero if your OS is smart, your
linker/relocator is smart or very stupid, and your optimizer is weak
(e.g., an optimizer that wouldn't do those optimizations anyway). :-)