Solution
First, let's think about the implications of the
given assumptions:
-
Different orders
of evaluating function parameters are ignored, and exceptions
thrown by destructors are ignored.
Follow-up question for the intrepid: How many more execution paths
are there if destructors are allowed to throw?
-
Called
functions are considered atomic. For example, the call
"e.Title()" could throw for several reasons (for example,
it could throw an exception itself, it could fail to catch an
exception thrown by another function it has called, or it could
return by value and the temporary object's constructor could
throw). All that matters to the function is whether performing the
operation e.Title() results in an exception being thrown
or not.
-
To count as a
different execution path, an execution path must be made up of a
unique sequence of function calls performed and exited in the same
way.
So, how many possible execution paths are there?
Answer: 23 (in just three lines of
code!).
3 |
Average |
4–14 |
Exception-aware |
15–23 |
Guru
material |
The 23 are made up of:
By nonexceptional code
paths I mean paths of execution that happen even if there
are no exceptions thrown. Nonexceptional code paths result from
normal C++ program flow. On the other hand, by exceptional code paths I mean those paths of
execution that arise as a result of an exception being thrown or
propagated, and I'll consider those paths separately.
Nonexceptional Code Paths
For the nonexceptional execution paths, the
trick was to know C/C++'s short-circuit evaluation rule.
if( e.Title() == "CEO" || e.Salary() > 100000 )
-
If e.Title()
== "CEO" evaluates to
true, then the second part of the condition doesn't need
to be evaluated (for example, e.Salary() will never be
called), but the cout will be performed.
With suitable overloads for ==,
||, and/or > in the if's condition,
the || could actually turn out to be a function call. If
it is a function call, the short-circuit evaluation rule would be
suppressed and both parts of the condition would be evaluated all
the time.
-
If
e.Title() != "CEO" but
e.Salary() > 100000, both parts of the condition will
be evaluated and the cout will be performed.
-
If
e.Title() != "CEO" and
e.Salary() <= 100000, the cout will not be
performed.
Exceptional Code Paths
This leaves the exceptional execution paths.
String EvaluateSalaryAndReturnName( Employee e )
^*^ ^4^
-
The argument is
passed by value, which invokes the Employee copy
constructor. This copy operation might throw an exception.
*. String's copy constructor might
throw while copying the temporary return value into the caller's
area. We'll ignore this one, however, because it happens outside
this function (and it turns out that we have enough execution paths
of our own to keep us busy anyway).
if( e.Title() == "CEO" || e.Salary() > 100000 )
^5^ ^7^ ^6^ ^11^ ^8^ ^10^ ^9^
-
The
Title() member function might itself throw, or it might
return an object of class type by value, and that copy operation
might throw.
-
To match a
valid operator==(), the string literal may need to be
converted to a temporary object of class type (probably the same as
e.Title()'s return type), and that construction of the
temporary might throw.
-
If
operator==() is a programmer-supplied function, it might
throw.
-
Similar to #5,
Salary() might itself throw, or it might return a
temporary object and this construction operation might
throw.
-
Similar to #6,
a temporary object may need to be constructed and this construction
might throw.
-
Similar to #7,
this might be a programmer-provided function and therefore might
throw.
-
Similar to #7
and #10, this might be a programmer-provided function and therefore
might throw.
cout << e.First() << " " << e.Last() << " is overpaid" << endl;
^12^ ^17^ ^13^ ^14^ ^18^ ^15^ ^16^
-
As documented
in the C++ Standard, any of the five calls to
operator<< might throw.
-
Similar to #5,
First() and/or Last() might throw, or each might
return a temporary object and those construction operations might
throw.
return e.First() + " " + e.Last();
^19^ ^22^ ^21^ ^23^ ^20^
-
Similar to #5,
First() and/or Last() might throw, or each might
return a temporary object and those construction operations might
throw.
-
Similar to #6,
a temporary object may need to be constructed and this construction
might throw.
-
Similar to #7,
this might be a programmer-provided function and therefore might
throw.
Guideline
|
Always be
exception-aware. Know what code might emit exceptions.
|
One purpose of this Item was to demonstrate just
how many invisible execution paths can exist in simple code in a
language that allows exceptions. Does this invisible complexity
affect the function's reliability and testability? See the
following Item for the answer.
|