If a function could fail, the caller needs some way to find out what happened. One of the most ancient and simple methods is for the function to return an integer error code, which the caller must check before using any other result.
A function that opens a resource of the kind a file would open can return 0 for success and nonzero for failure.
#include <iostream>
int openResource(int id)
{
if (id < 0)
return 1;
return 0;
}
int main()
{
int status = openResource(5);
if (status != 0)
std::cout << "open failed\n";
else
std::cout << "open succeeded\n";
return 0;
}
The function openResource accepts a number identifier as input. It returns 1 if the identifier is negative, 0 otherwise. In main, the return value is saved in the variable status.
The following if statement makes use of that variable.
It outputs a message depending on success or failure. The call 5 is successful.
When given a negative identifier, the same function also fails.
#include <iostream>
int openResource(int id)
{
if (id < 0)
return 1;
return 0;
}
int main()
{
int status = openResource(-3);
if (status != 0)
std::cout << "open failed\n";
else
std::cout << "open succeeded\n";
return 0;
}
Now that the returned code is 1, the condition status != 0 is true, and the failure message is printed. The caller must look at the code before assuming that the call succeeded.
A plain integer makes the meaning of each number implicit. An enumeration makes the possible results explicit and allows the compiler to detect misspelled names.
#include <iostream>
enum class Status
{
Ok,
InvalidId,
NotFound
};
Status openResource(int id)
{
if (id < 0)
return Status::InvalidId;
if (id > 100)
return Status::NotFound;
return Status::Ok;
}
int main()
{
Status s = openResource(5);
if (s != Status::Ok)
std::cout << "open failed\n";
else
std::cout << "open succeeded\n";
return 0;
}
The scoped enumeration Status enumerates all possible results of the function. The function does return one of those enumerators. In main, the comparison is done to Status::Ok so a reader can see the success condition without having to remember what number corresponds to it. The call with 5 produces Status::Ok and the success message.
If several functions are called in succession, check the status of each one before proceeding to the next. If any one of them failed, then stop and abandon the rest.
#include <iostream>
enum class Status
{
Ok,
InvalidId,
NotFound,
PermissionDenied
};
Status openResource(int id)
{
if (id < 0)
return Status::InvalidId;
return Status::Ok;
}
Status readResource(int id)
{
if (id == 42)
return Status::PermissionDenied;
return Status::Ok;
}
int main()
{
Status s = openResource(5);
if (s != Status::Ok)
{
std::cout << "open failed\n";
return 0;
}
s = readResource(5);
if (s != Status::Ok)
{
std::cout << "read failed\n";
return 0;
}
std::cout << "all succeeded\n";
return 0;
}
The first call saves its result into s, and the second call is made only if that result was Status::Ok. The individual tests are written out, so the error paths are easy to see in ordinary if statements. If either of the functions fails, an error message is printed, and main returns immediately.
Error codes thus convert each potential failure into a value that the calling code must test for. The approach needs nothing special in the language other than return values and enums, and the flow of control remains entirely within standard sequential control. The drawback is that every call site must remember to test; if it doesn't, failure can go unnoticed.
0 comments
No comments yet. Be the first.