Contents
- 1 Exception handling
- 1.1 Capture and storage of exception objects
- 1.2 Handling of failures in exception handling
- 1.3 Handling of exception specification violations (removed in C++17)
- 2 Exception categories
- 3 Error numbers
- 4 System error
- 5 Assertions
- 6 Stacktrace
- 7 See also
[edit] Exception handling
The header <exception>
provides several classes and functions related to exception handling in C++ programs.
Defined in header |
|
exception |
base class for exceptions thrown by the standard library components (class) [edit] |
Capture and storage of exception objects |
|
uncaught_exceptionuncaught_exceptions (removed in C++20)(C++17) |
checks if exception handling is currently in progress (function) [edit] |
exception_ptr (C++11) |
shared pointer type for handling exception objects (typedef) [edit] |
make_exception_ptr (C++11) |
creates an std::exception_ptr from an exception object (function template) [edit] |
current_exception (C++11) |
captures the current exception in a std::exception_ptr (function) [edit] |
rethrow_exception (C++11) |
throws the exception from an std::exception_ptr (function) [edit] |
nested_exception (C++11) |
a mixin type to capture and store current exceptions (class) [edit] |
throw_with_nested (C++11) |
throws its argument with std::nested_exception mixed in (function template) [edit] |
rethrow_if_nested (C++11) |
throws the exception from a std::nested_exception (function template) [edit] |
Handling of failures in exception handling |
|
Defined in header |
|
terminate |
function called when exception handling fails (function) [edit] |
terminate_handler |
the type of the function called by std::terminate (typedef) [edit] |
get_terminate (C++11) |
obtains the current terminate_handler (function) [edit] |
set_terminate |
changes the function to be called by std::terminate (function) [edit] |
bad_exception |
exception thrown when std::current_exception fails to copy the exception object (class) [edit] |
Handling of exception specification violations (removed in C++17) |
|
unexpected (removed in C++17) |
function called when dynamic exception specification is violated (function) [edit] |
unexpected_handler (removed in C++17) |
the type of the function called by std::unexpected (typedef) [edit] |
get_unexpected (C++11)(removed in C++17) |
obtains the current unexpected_handler (function) [edit] |
set_unexpected (removed in C++17) |
changes the function to be called by std::unexpected (function) [edit] |
[edit] Exception categories
Several convenience classes are predefined in the header <stdexcept>
to report particular error conditions. These classes can be divided into two categories: logic errors and runtime errors. Logic errors are a consequence of faulty logic within the program and may be preventable. Runtime errors are due to events beyond the scope of the program and can not be easily predicted.
Defined in header |
|
logic_error |
exception class to indicate violations of logical preconditions or class invariants (class) [edit] |
invalid_argument |
exception class to report invalid arguments (class) [edit] |
domain_error |
exception class to report domain errors (class) [edit] |
length_error |
exception class to report attempts to exceed maximum allowed size (class) [edit] |
out_of_range |
exception class to report arguments outside of expected range (class) [edit] |
runtime_error |
exception class to indicate conditions only detectable at run time (class) [edit] |
range_error |
exception class to report range errors in internal computations (class) [edit] |
overflow_error |
exception class to report arithmetic overflows (class) [edit] |
underflow_error |
exception class to report arithmetic underflows (class) [edit] |
tx_exception (TM TS) |
exception class to cancel atomic transactions (class template) |
[edit] Error numbers
[edit] System error
The header <system_error>
defines types and functions used to report error conditions originating from the operating system, streams I/O, std::future, or other low-level APIs.
Defined in header |
|
error_category (C++11) |
base class for error categories (class) [edit] |
generic_category (C++11) |
identifies the generic error category (function) [edit] |
system_category (C++11) |
identifies the operating system error category (function) [edit] |
error_condition (C++11) |
holds a portable error code (class) [edit] |
errc (C++11) |
the std::error_condition enumeration listing all standard <cerrno> macro constants (class) [edit] |
error_code (C++11) |
holds a platform-dependent error code (class) [edit] |
system_error (C++11) |
exception class used to report conditions that have an error_code (class) [edit] |
[edit] Assertions
Assertions help to implement checking of preconditions in programs.
aborts the program if the user-specified condition is not true. May be disabled for release builds (function macro) [edit] |
[edit] Stacktrace
[edit] See also
title | description | ms.date | ms.topic | ms.assetid |
---|---|---|---|---|
Modern C++ best practices for exceptions and error handling |
How Modern C++ supports exceptional programming styles over error codes. |
08/24/2020 |
conceptual |
a6c111d0-24f9-4bbb-997d-3db4569761b7 |
Modern C++ best practices for exceptions and error handling
In modern C++, in most scenarios, the preferred way to report and handle both logic errors and runtime errors is to use exceptions. It’s especially true when the stack might contain several function calls between the function that detects the error, and the function that has the context to handle the error. Exceptions provide a formal, well-defined way for code that detects errors to pass the information up the call stack.
Use exceptions for exceptional code
Program errors are often divided into two categories: Logic errors that are caused by programming mistakes, for example, an «index out of range» error. And, runtime errors that are beyond the control of programmer, for example, a «network service unavailable» error. In C-style programming and in COM, error reporting is managed either by returning a value that represents an error code or a status code for a particular function, or by setting a global variable that the caller may optionally retrieve after every function call to see whether errors were reported. For example, COM programming uses the HRESULT return value to communicate errors to the caller. And the Win32 API has the GetLastError
function to retrieve the last error that was reported by the call stack. In both of these cases, it’s up to the caller to recognize the code and respond to it appropriately. If the caller doesn’t explicitly handle the error code, the program might crash without warning. Or, it might continue to execute using bad data and produce incorrect results.
Exceptions are preferred in modern C++ for the following reasons:
-
An exception forces calling code to recognize an error condition and handle it. Unhandled exceptions stop program execution.
-
An exception jumps to the point in the call stack that can handle the error. Intermediate functions can let the exception propagate. They don’t have to coordinate with other layers.
-
The exception stack-unwinding mechanism destroys all objects in scope after an exception is thrown, according to well-defined rules.
-
An exception enables a clean separation between the code that detects the error and the code that handles the error.
The following simplified example shows the necessary syntax for throwing and catching exceptions in C++.
#include <stdexcept> #include <limits> #include <iostream> using namespace std; void MyFunc(int c) { if (c > numeric_limits< char> ::max()) throw invalid_argument("MyFunc argument too large."); //... } int main() { try { MyFunc(256); //cause an exception to throw } catch (invalid_argument& e) { cerr << e.what() << endl; return -1; } //... return 0; }
Exceptions in C++ resemble ones in languages such as C# and Java. In the try
block, if an exception is thrown it will be caught by the first associated catch
block whose type matches that of the exception. In other words, execution jumps from the throw
statement to the catch
statement. If no usable catch block is found, std::terminate
is invoked and the program exits. In C++, any type may be thrown; however, we recommend that you throw a type that derives directly or indirectly from std::exception
. In the previous example, the exception type, invalid_argument
, is defined in the standard library in the <stdexcept>
header file. C++ doesn’t provide or require a finally
block to make sure all resources are released if an exception is thrown. The resource acquisition is initialization (RAII) idiom, which uses smart pointers, provides the required functionality for resource cleanup. For more information, see How to: Design for exception safety. For information about the C++ stack-unwinding mechanism, see Exceptions and stack unwinding.
Basic guidelines
Robust error handling is challenging in any programming language. Although exceptions provide several features that support good error handling, they can’t do all the work for you. To realize the benefits of the exception mechanism, keep exceptions in mind as you design your code.
-
Use asserts to check for errors that should never occur. Use exceptions to check for errors that might occur, for example, errors in input validation on parameters of public functions. For more information, see the Exceptions versus assertions section.
-
Use exceptions when the code that handles the error is separated from the code that detects the error by one or more intervening function calls. Consider whether to use error codes instead in performance-critical loops, when code that handles the error is tightly coupled to the code that detects it.
-
For every function that might throw or propagate an exception, provide one of the three exception guarantees: the strong guarantee, the basic guarantee, or the nothrow (noexcept) guarantee. For more information, see How to: Design for exception safety.
-
Throw exceptions by value, catch them by reference. Don’t catch what you can’t handle.
-
Don’t use exception specifications, which are deprecated in C++11. For more information, see the Exception specifications and
noexcept
section. -
Use standard library exception types when they apply. Derive custom exception types from the
exception
Class hierarchy. -
Don’t allow exceptions to escape from destructors or memory-deallocation functions.
Exceptions and performance
The exception mechanism has a minimal performance cost if no exception is thrown. If an exception is thrown, the cost of the stack traversal and unwinding is roughly comparable to the cost of a function call. Additional data structures are required to track the call stack after a try
block is entered, and additional instructions are required to unwind the stack if an exception is thrown. However, in most scenarios, the cost in performance and memory footprint isn’t significant. The adverse effect of exceptions on performance is likely to be significant only on memory-constrained systems. Or, in performance-critical loops, where an error is likely to occur regularly and there’s tight coupling between the code to handle it and the code that reports it. In any case, it’s impossible to know the actual cost of exceptions without profiling and measuring. Even in those rare cases when the cost is significant, you can weigh it against the increased correctness, easier maintainability, and other advantages that are provided by a well-designed exception policy.
Exceptions versus assertions
Exceptions and asserts are two distinct mechanisms for detecting run-time errors in a program. Use assert
statements to test for conditions during development that should never be true if all your code is correct. There’s no point in handling such an error by using an exception, because the error indicates that something in the code has to be fixed. It doesn’t represent a condition that the program has to recover from at run time. An assert
stops execution at the statement so that you can inspect the program state in the debugger. An exception continues execution from the first appropriate catch handler. Use exceptions to check error conditions that might occur at run time even if your code is correct, for example, «file not found» or «out of memory.» Exceptions can handle these conditions, even if the recovery just outputs a message to a log and ends the program. Always check arguments to public functions by using exceptions. Even if your function is error-free, you might not have complete control over arguments that a user might pass to it.
C++ exceptions versus Windows SEH exceptions
Both C and C++ programs can use the structured exception handling (SEH) mechanism in the Windows operating system. The concepts in SEH resemble the ones in C++ exceptions, except that SEH uses the __try
, __except
, and __finally
constructs instead of try
and catch
. In the Microsoft C++ compiler (MSVC), C++ exceptions are implemented for SEH. However, when you write C++ code, use the C++ exception syntax.
For more information about SEH, see Structured Exception Handling (C/C++).
Exception specifications and noexcept
Exception specifications were introduced in C++ as a way to specify the exceptions that a function might throw. However, exception specifications proved problematic in practice, and are deprecated in the C++11 draft standard. We recommend that you don’t use throw
exception specifications except for throw()
, which indicates that the function allows no exceptions to escape. If you must use exception specifications of the deprecated form throw( type-name )
, MSVC support is limited. For more information, see Exception Specifications (throw). The noexcept
specifier is introduced in C++11 as the preferred alternative to throw()
.
See also
How to: Interface between exceptional and non-exceptional code
C++ language reference
C++ Standard Library
One of the advantages of C++ over C is Exception Handling. Exceptions are runtime anomalies or abnormal conditions that a program encounters during its execution. There are two types of exceptions: a)Synchronous, b)Asynchronous (i.e., exceptions which are beyond the program’s control, such as disc failure, keyboard interrupts etc.). C++ provides the following specialized keywords for this purpose:
try: Represents a block of code that can throw an exception.
catch: Represents a block of code that is executed when a particular exception is thrown.
throw: Used to throw an exception. Also used to list the exceptions that a function throws but doesn’t handle itself.
Why Exception Handling?
The following are the main advantages of exception handling over traditional error handling:
1) Separation of Error Handling code from Normal Code: In traditional error handling codes, there are always if-else conditions to handle errors. These conditions and the code to handle errors get mixed up with the normal flow. This makes the code less readable and maintainable. With try/catch blocks, the code for error handling becomes separate from the normal flow.
2) Functions/Methods can handle only the exceptions they choose: A function can throw many exceptions, but may choose to handle some of them. The other exceptions, which are thrown but not caught, can be handled by the caller. If the caller chooses not to catch them, then the exceptions are handled by the caller of the caller.
In C++, a function can specify the exceptions that it throws using the throw keyword. The caller of this function must handle the exception in some way (either by specifying it again or catching it).
3) Grouping of Error Types: In C++, both basic types and objects can be thrown as exceptions. We can create a hierarchy of exception objects, group exceptions in namespaces or classes and categorize them according to their types.
C++ Exceptions:
When executing C++ code, different errors can occur: coding errors made by the programmer, errors due to wrong input, or other unforeseeable things.
When an error occurs, C++ will normally stop and generate an error message. The technical term for this is: C++ will throw an exception (error).
C++ try and catch:
Exception handling in C++ consists of three keywords: try, throw and catch:
The try statement allows you to define a block of code to be tested for errors while it is being executed.
The throw keyword throws an exception when a problem is detected, which lets us create a custom error.
The catch statement allows you to define a block of code to be executed if an error occurs in the try block.
The try and catch keywords come in pairs:
We use the try block to test some code: If the value of a variable “age” is less than 18, we will throw an exception, and handle it in our catch block.
In the catch block, we catch the error if it occurs and do something about it. The catch statement takes a single parameter. So, if the value of age is 15 and that’s why we are throwing an exception of type int in the try block (age), we can pass “int myNum” as the parameter to the catch statement, where the variable “myNum” is used to output the value of age.
If no error occurs (e.g. if age is 20 instead of 15, meaning it will be greater than 18), the catch block is skipped.
Exception Handling in C++
1) The following is a simple example to show exception handling in C++. The output of the program explains the flow of execution of try/catch blocks.
CPP
#include <iostream>
using
namespace
std;
int
main()
{
int
x = -1;
cout <<
"Before try n"
;
try
{
cout <<
"Inside try n"
;
if
(x < 0)
{
throw
x;
cout <<
"After throw (Never executed) n"
;
}
}
catch
(
int
x ) {
cout <<
"Exception Caught n"
;
}
cout <<
"After catch (Will be executed) n"
;
return
0;
}
Output:
Before try Inside try Exception Caught After catch (Will be executed)
2) There is a special catch block called the ‘catch all’ block, written as catch(…), that can be used to catch all types of exceptions. For example, in the following program, an int is thrown as an exception, but there is no catch block for int, so the catch(…) block will be executed.
CPP
#include <iostream>
using
namespace
std;
int
main()
{
try
{
throw
10;
}
catch
(
char
*excp) {
cout <<
"Caught "
<< excp;
}
catch
(...) {
cout <<
"Default Exceptionn"
;
}
return
0;
}
Output:
Default Exception
3) Implicit type conversion doesn’t happen for primitive types. For example, in the following program, ‘a’ is not implicitly converted to int.
CPP
#include <iostream>
using
namespace
std;
int
main()
{
try
{
throw
'a'
;
}
catch
(
int
x) {
cout <<
"Caught "
<< x;
}
catch
(...) {
cout <<
"Default Exceptionn"
;
}
return
0;
}
Output:
Default Exception
4) If an exception is thrown and not caught anywhere, the program terminates abnormally. For example, in the following program, a char is thrown, but there is no catch block to catch the char.
CPP
#include <iostream>
using
namespace
std;
int
main()
{
try
{
throw
'a'
;
}
catch
(
int
x) {
cout <<
"Caught "
;
}
return
0;
}
Output:
terminate called after throwing an instance of 'char' This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.
We can change this abnormal termination behavior by writing our own unexpected function.
5) A derived class exception should be caught before a base class exception. See this for more details.
6) Like Java, the C++ library has a standard exception class which is the base class for all standard exceptions. All objects thrown by the components of the standard library are derived from this class. Therefore, all standard exceptions can be caught by catching this type
7) Unlike Java, in C++, all exceptions are unchecked, i.e., the compiler doesn’t check whether an exception is caught or not (See this for details). So, it is not necessary to specify all uncaught exceptions in a function declaration. Although it’s a recommended practice to do so. For example, the following program compiles fine, but ideally the signature of fun() should list the unchecked exceptions.
CPP
#include <iostream>
using
namespace
std;
void
fun(
int
*ptr,
int
x)
{
if
(ptr == NULL)
throw
ptr;
if
(x == 0)
throw
x;
}
int
main()
{
try
{
fun(NULL, 0);
}
catch
(...) {
cout <<
"Caught exception from fun()"
;
}
return
0;
}
Output:
Caught exception from fun()
A better way to write the above code:
CPP
#include <iostream>
using
namespace
std;
void
fun(
int
*ptr,
int
x)
throw
(
int
*,
int
)
{
if
(ptr == NULL)
throw
ptr;
if
(x == 0)
throw
x;
}
int
main()
{
try
{
fun(NULL, 0);
}
catch
(...) {
cout <<
"Caught exception from fun()"
;
}
return
0;
}
Note : The use of Dynamic Exception Specification has been deprecated since C++11. One of the reasons for it may be that it can randomly abort your program. This can happen when you throw an exception of another type which is not mentioned in the dynamic exception specification. Your program will abort itself because in that scenario, it calls (indirectly) terminate(), which by default calls abort().
Output:
Caught exception from fun()
In C++, try/catch blocks can be nested. Also, an exception can be re-thrown using “throw; “.
CPP
#include <iostream>
using
namespace
std;
int
main()
{
try
{
try
{
throw
20;
}
catch
(
int
n) {
cout <<
"Handle Partially "
;
throw
;
}
}
catch
(
int
n) {
cout <<
"Handle remaining "
;
}
return
0;
}
Output:
Handle Partially Handle remaining
A function can also re-throw a function using the same “throw; ” syntax. A function can handle a part and ask the caller to handle the remaining.
9) When an exception is thrown, all objects created inside the enclosing try block are destroyed before the control is transferred to the catch block.
CPP
#include <iostream>
using
namespace
std;
class
Test {
public
:
Test() { cout <<
"Constructor of Test "
<< endl; }
~Test() { cout <<
"Destructor of Test "
<< endl; }
};
int
main()
{
try
{
Test t1;
throw
10;
}
catch
(
int
i) {
cout <<
"Caught "
<< i << endl;
}
}
Output:
Constructor of Test Destructor of Test Caught 10
10) You may like to try Quiz on Exception Handling in C++.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
In lesson 7.14 — Common semantic errors in C++, we covered many types of common C++ semantic errors that new C++ programmers run into with the language. If an error is the result of a misused language feature or logic error, the error can simply be corrected.
But most errors in a program don’t occur as the result of inadvertently misusing language features — rather, most errors occur due to faulty assumptions made by the programmer and/or a lack of proper error detection/handling.
For example, in a function designed to look up a grade for a student, you might have assumed:
- The student being looked up will exist.
- All student names will be unique.
- The class uses letter grading (instead of pass/fail).
What if any of these assumptions aren’t true? If the programmer didn’t anticipate these cases, the program will likely malfunction or crash when such cases arise (usually at some point in the future, well after the function has been written).
There are three key places where assumption errors typically occur:
- When a function returns, the programmer may have assumed the called function was successful when it was not.
- When a program receives input (either from the user, or a file), the programmer may have assumed the input was in the correct format and semantically valid when it was not.
- When a function has been called, the programmer may have assumed the arguments would be semantically valid when they were not.
Many new programmers write code and then only test the happy path: only the cases where there are no errors. But you should also be planning for and testing your sad paths, where things can and will go wrong. In lesson 3.10 — Finding issues before they become problems, we defined defensive programming as the practice of trying to anticipate all of the ways software can be misused, either by end-users, or by developers (either the programmer themselves, or others). Once you’ve anticipated (or discovered) some misuse, the next thing to do is handle it.
In this lesson, we’ll talk about error handling strategies (what to do when things go wrong) inside a function. In the subsequent lessons, we’ll talk about validating user input, and then introduce a useful tool to help document and validate assumptions.
Handling errors in functions
Functions may fail for any number of reasons — the caller may have passed in an argument with an invalid value, or something may fail within the body of the function. For example, a function that opens a file for reading might fail if the file cannot be found.
When this happens, you have quite a few options at your disposal. There is no best way to handle an error — it really depends on the nature of the problem and whether the problem can be fixed or not.
There are 4 general strategies that can be used:
- Handle the error within the function
- Pass the error back to the caller to deal with
- Halt the program
- Throw an exception
Handling the error within the function
If possible, the best strategy is to recover from the error in the same function in which the error occurred, so that the error can be contained and corrected without impacting any code outside the function. There are two options here: retry until successful, or cancel the operation being executed.
If the error has occurred due to something outside of the program’s control, the program can retry until success is achieved. For example, if the program requires an internet connection, and the user has lost their connection, the program may be able to display a warning and then use a loop to periodically recheck for internet connectivity. Alternatively, if the user has entered invalid input, the program can ask the user to try again, and loop until the user is successful at entering valid input. We’ll show examples of handling invalid input and using loops to retry in the next lesson (7.16 — std::cin and handling invalid input).
An alternate strategy is just to ignore the error and/or cancel the operation. For example:
void printDivision(int x, int y)
{
if (y != 0)
std::cout << static_cast<double>(x) / y;
}
In the above example, if the user passed in an invalid value for y
, we just ignore the request to print the result of the division operation. The primary challenge with doing this is that the caller or user have no way to identify that something went wrong. In such case, printing an error message can be helpful:
void printDivision(int x, int y)
{
if (y != 0)
std::cout << static_cast<double>(x) / y;
else
std::cerr << "Error: Could not divide by zeron";
}
However, if the calling function is expecting the called function to produce a return value or some useful side-effect, then just ignoring the error may not be an option.
Passing errors back to the caller
In many cases, the error can’t reasonably be handled in the function that detects the error. For example, consider the following function:
double doDivision(int x, int y)
{
return static_cast<double>(x) / y;
}
If y
is 0
, what should we do? We can’t just skip the program logic, because the function needs to return some value. We shouldn’t ask the user to enter a new value for y
because this is a calculation function, and introducing input routines into it may or may not be appropriate for the program calling this function.
In such cases, the best option can be to pass the error back to the caller in hopes that the caller will be able to deal with it.
How might we do that?
If the function has a void return type, it can be changed to return a Boolean that indicates success or failure. For example, instead of:
void printDivision(int x, int y)
{
if (y != 0)
std::cout << static_cast<double>(x) / y;
else
std::cerr << "Error: Could not divide by zeron";
}
We can do this:
bool printDivision(int x, int y)
{
if (y == 0)
{
std::cerr << "Error: could not divide by zeron";
return false;
}
std::cout << static_cast<double>(x) / y;
return true;
}
That way, the caller can check the return value to see if the function failed for some reason.
If the function returns a normal value, things are a little more complicated. In some cases, the full range of return values isn’t used. In such cases, we can use a return value that wouldn’t otherwise be possible to occur normally to indicate an error. For example, consider the following function:
// The reciprocal of x is 1/x
double reciprocal(double x)
{
return 1.0 / x;
}
The reciprocal of some number x
is defined as 1/x
, and a number multiplied by its reciprocal equals 1.
However, what happens if the user calls this function as reciprocal(0.0)
? We get a divide by zero
error and a program crash, so clearly we should protect against this case. But this function must return a double value, so what value should we return? It turns out that this function will never produce 0.0
as a legitimate result, so we can return 0.0
to indicate an error case.
// The reciprocal of x is 1/x, returns 0.0 if x=0
double reciprocal(double x)
{
if (x == 0.0)
return 0.0;
return 1.0 / x;
}
However, if the full range of return values are needed, then using the return value to indicate an error will not be possible (because the caller would not be able to tell whether the return value is a valid value or an error value). In such a case, an out parameter
(covered in lesson 9.5 — Pass by lvalue reference) might be a viable choice.
Fatal errors
If the error is so bad that the program can not continue to operate properly, this is called a non-recoverable error (also called a fatal error). In such cases, the best thing to do is terminate the program. If your code is in main()
or a function called directly from main()
, the best thing to do is let main()
return a non-zero status code. However, if you’re deep in some nested subfunction, it may not be convenient or possible to propagate the error all the way back to main()
. In such a case, a halt statement
(such as std::exit()
) can be used.
For example:
double doDivision(int x, int y)
{
if (y == 0)
{
std::cerr << "Error: Could not divide by zeron";
std::exit(1);
}
return static_cast<double>(x) / y;
}
Exceptions
Because returning an error from a function back to the caller is complicated (and the many different ways to do so leads to inconsistency, and inconsistency leads to mistakes), C++ offers an entirely separate way to pass errors back to the caller: exceptions
.
The basic idea is that when an error occurs, an exception is “thrown”. If the current function does not “catch” the error, the caller of the function has a chance to catch the error. If the caller does not catch the error, the caller’s caller has a chance to catch the error. The error progressively moves up the call stack until it is either caught and handled (at which point execution continues normally), or until main() fails to handle the error (at which point the program is terminated with an exception error).
We cover exception handling in chapter 20 of this tutorial series.