Created
January 26, 2023 11:31
-
-
Save Broxzier/35ef7f82988e8d7af962088a1bcdabc4 to your computer and use it in GitHub Desktop.
Using std::excepted to ensure all exceptions are caught
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <exception> | |
#include <expected> | |
#include <iostream> | |
#include <variant> | |
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; | |
namespace IO | |
{ | |
struct FileNotFound {}; | |
struct FileInUse {}; | |
enum class FileError { | |
FileNotFound, | |
FileInUse, | |
}; | |
struct FileHandle {}; | |
using ExpectedExceptions = std::variant<FileNotFound, FileInUse>; | |
FileHandle OpenFileForWriting1() | |
{ | |
// Just some volatile data to avoid optimization | |
static volatile int w = 0; | |
if (w == 0) | |
throw FileNotFound{}; | |
if (w == 1) | |
throw FileInUse{}; | |
return FileHandle{}; | |
} | |
std::expected<FileHandle, FileError> OpenFileForWriting2() | |
{ | |
// Just some volatile data to avoid optimization | |
static volatile int w = 0; | |
if (w == 0) | |
return std::unexpected{ FileError::FileNotFound }; | |
if (w == 1) | |
return std::unexpected{ FileError::FileInUse }; | |
return FileHandle{}; | |
} | |
std::expected<FileHandle, ExpectedExceptions> OpenFileForWriting3() | |
{ | |
// Just some volatile data to avoid optimization | |
static volatile int w = 0; | |
if (w == 0) | |
return std::unexpected{ IO::FileNotFound{} }; | |
if (w == 1) | |
return std::unexpected{ IO::FileInUse{} }; | |
return FileHandle{}; | |
} | |
} | |
int main() | |
{ | |
// Typical C++ exception handling with try/catch | |
// Pros: Expected and unexpected code handling is separated | |
// Cons: No way to ensure all possible exceptions are handled | |
try { | |
auto handle1 = IO::OpenFileForWriting1(); | |
// Use file handle | |
} | |
catch (const IO::FileNotFound& ex) { std::cerr << "IO::FileNotFound was thrown\n"; } | |
catch (const IO::FileInUse& ex) { std::cerr << "IO::FileInUse was thrown\n"; } | |
catch (...) { std::cerr << "Unknown exception.\n"; } | |
// Error handling with std::expected and an enum | |
// Pros: Expected and unexpected code handling is separated | |
// Cons: Requires compiled settings to ensure all exceptions are caught | |
auto handle2 = IO::OpenFileForWriting2(); | |
if (handle2) { | |
// Use file handle | |
} else { | |
switch (handle2.error()) | |
{ | |
case IO::FileError::FileNotFound: std::cerr << "IO::FileError::FileNotFound was returned\n"; break; | |
case IO::FileError::FileInUse: std::cerr << "IO::FileError::FileInUse was returned\n"; break; | |
default: std::cerr << "Unknown error returned\n"; break; | |
} | |
} | |
// Error handling with std::expected and a std::variant | |
// Pros: Expected and unexpected code handling is separated; ensures all exceptions are caught | |
// Cons: Looks more complicated than try/catch; requires a handler for each type | |
auto handle3 = IO::OpenFileForWriting3(); | |
if (handle3) { | |
// Use file handle | |
} else { | |
auto error = handle3.error(); | |
std::visit(overloaded { | |
[](const IO::FileNotFound& error){ std::cerr << "IO::FileNotFound was returned\n"; }, | |
[](const IO::FileInUse& error){ std::cerr << "IO::FileInUse was returned\n"; }, | |
// No need for catching unknown errors, because std::visit ensured we already catch each one | |
}, error); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment