A key component of C++ programming is exception safety, which is necessary to preserve the consistency and dependability of the code in the event of an exception. In this article, we will clarify various levels of exception safety and recommended practices and provide an exploration of exception safety.
Robust C++ programming requires exception safety, which makes sure that code behaves consistently and maintains its integrity even when exceptions arise. It includes the following three levels of guarantees:
- Basic Exception Safety
- Strong Exception Safety
- No-Throw Guarantee
1. Basic Exception Safety:
At this stage, there is no resource leakage, and the program stays operational, but there can be some side effects from incomplete operations.
Example:
Let us take an example to illustrate the basic exception safety in C++.
#include <iostream>
#include <fstream>
#include <string>
void openFileAndPrinting()
{
std::ofstream file("output.txt");
if (!file.is_open())
{
throw std::runtime_error("Failed to open the file.");
}
// Close the File even if an exception is raised by using RAII (Resource Acquisition Is Initialization).
struct FileCloser
{
std::ofstream& fileRef;
FileCloser(std::ofstream& file) : fileRef(file) {}
~FileCloser() { fileRef.close(); }
} fileCloser(file);
// RAII object created here.
// Writing data into the File.
file << "Hello, Exception Safety!" << std::endl;
// An exception might be thrown here.
}
int main()
{
try
{
openFileAndPrinting();
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
// Even in the event of an exception, the File is properly closed.
return 0;
}
Output:
Exception: Failed to open the File.
Explanation:
This code demonstrates fundamental C++ exception safety concepts. The "output.txt" file is attempted to be opened for writing using the openFileAndPrinting method. The File throws a std::runtime_error if it cannot be opened. After that, an inner struct FileCloser is constructed, which uses RAII to shut the File in its destruction in order to ensure proper resource management. The FileCloser object makes sure that the File is closed when the Function exits, whether it is because of success or an exception. After that, information is written to the File, perhaps causing an exception. Next, the main method is called inside a try-catch block to handle any exceptions. The error message is displayed to the standard error stream in the event that an exception arises. This method ensures correct file closing, maintaining the stability of the program and guarding against resource leaks.
2. Strong Exception Safety:
This level of exception safety provides more dependability. If an exception is raised, it ensures that resources are released appropriately and that the program's state is unchanged, as if the operation had never taken place.
Example:
Let us take an example to illustrate the strong exception safety in C++.
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
// for std::unique_ptr
//Function to open a file and write data to it.
void openFileAndPrint()
{
std::ofstream file("output.txt");
if (!file.is_open())
{
throw std::runtime_error("Failed to open the file.");
}
// Writing data into the File.
file << "Hello, Exception Safety!" << std::endl;
// An exception might be thrown here.
}
//Function to read the data from the File.
void readFile()
{
std::ifstream file("output.txt");
if (!file.is_open())
{
throw std::runtime_error("Failed to open the file for reading.");
}
// Reading data from the File.
std::string line;
while (std::getline(file, line))
{
std::cout << "Read from file: " << line << std::endl;
}
// An exception might be thrown here.
}
int main()
{
// Using try-catch blocks to handle exceptions separately.
try
{
openFileAndPrint();
readFile();
}
catch (const std::runtime_error& openError)
{
std::cerr << "Error opening file: " << openError.what() << std::endl;
}
catch (const std::exception& readError)
{
std::cerr << "Error reading from file: " << readError.what() << std::endl;
}
//File is automatically closed when the unique_ptr goes out of scope.
return 0;
}
Output:
Error opening file: Failed to open the File.
Explanation:
This code demonstrates C++'s basic exception safety. An attempt is made to open the file "output.txt" for writing through the openFileAndPrint method. The program throws a std::runtimeerror with the appropriate error message if the File cannot be opened. Afterwards, the text "Hello, Exception Safety!" is written to the File. Similar to this, the readFile method takes every line from the File and outputs it to the console after trying to open the same File for reading. A std::runtimeerror is thrown if the File cannot be opened for reading.
Both readFile and openFileAndPrint are called inside of a try-catch block in the main method. The exception is caught and handled properly if either Function throws it. There appears to be a problem opening the File if openFileAndPrint raises an exception. It indicates an issue with reading from the File if readFile produces an exception.
The code provides basic exception safety by utilizing distinct try-catch blocks for each function call, ensuring that exceptions are caught and handled properly. In addition, resource leaks are avoided because the File is immediately closed by RAII (Resource Acquisition Is Initialization) principles when the std::ofstream and std::ifstream objects leave their scope.
3. No-Throw Guarantee:
Functions are guaranteed not to throw exceptions at this highest level. It ensures that the program's resources and status continue to be preserved, even under exceptional circumstances.