I have been reading a book where the object oriented implementation for exceptions is as follows:
Rectangle.hpp
class Rectangle
{
private:
// code
public:
class NegativeSize
{
// empty class declaration
// for exception
};
// code
};
Whenever a specific error occurs, the NegativeSize() is thrown from the constructor as follows:
Rectangle.cpp
void Rectangle::setX(int _x)
{
if (_x < 0)
{
// this instantiates an object
throw NegativeSize();
}
x = _x;
}
main.cpp
try
{
// code
}
catch (Rectangle::NegativeSize)
{
cout << "Negative value entered" << endl;
}
But I can do the same thing by declaring a public variable in Rectangle class and throw it instead:
Rectangle.hpp
class Rectangle
{
private:
// code
public:
string NegativeVal;
// code
};
Rectangle.cpp
void Rectangle::setX(int _x)
{
if (_x < 0)
{
throw NegativeSize;
}
x = _x;
}
main.cpp
try
{
// code
}
catch (string NegativeSize)
{
cout << "Negative value entered" << endl;
}
Can the second implementation be considered a good practice?
CodePudding user response:
The actual exception object that the catch
clause will catch is not the object that you name or refer to in the throw
expression. Instead the exception object is an object of the same (decayed) type as the throw
operand and initialized from the same.
So in your first example you throw an object of type Rectangle::NegativeSize
which is initialized from NegativeSize()
and in the second example you throw a std::string
which is a copy of the NegativeSize
member, whatever value it holds at that time.
In the second example catch (Rectangle::NegativeSize)
will not work, because NegativeSize
is not a type anymore. You will need to catch std::string
instead, which of course is a problem, since it doesn't tell you at all what the kind of exception this is (plus some other deeper issues).
Throwing a std::string
instead of a specific exception class is therefore not a good practice. You wouldn't be able to differentiate different exceptions and catch only those you can handle in a given catch
clause.
Throwing a data member also seems pointless. What value will you set it to? Probably just the "Negative value entered"
string? If so, that could be thrown directly as throw std::string("Negative value entered");
as well. As I said above, the exception object is going to be a copy anyway. At the catch
clause you can't identify anymore that the throw
expression used the NegativeSize
member.
Also, in practice exception classes are usually derived from std::exception
or a class derived from it and are not empty. They commonly store a string which can be given at the point of the throw
expression, so that detailed information from the source of the exception can be provided to the user, e.g.:
class Rectangle
{
private:
// code
public:
struct NegativeSize : std::invalid_argument {};
// code
};
//...
void Rectangle::setX(int _x)
{
if (_x < 0)
{
throw NegativeSize("Negative value entered");
}
x = _x;
}
//...
try
{
// code
}
catch (const Rectangle::NegativeSize& e)
{
cout << e.what() << endl;
}
Also, as I do above, exceptions should usually be caught by-reference.