Throw exception, error handling

I have a question regarding throwing errors inside a constructor.

So lets say I have a class called Item, which holds three data:
string name, code;
double price;

In my constructor, I pass in three parameters which will assign code, name, price with what the user wants.

I want to have an error handling in my constructor, so if the user enters a price that is negative, an error will be thrown.
If the user enters an invalid code format, the error will be thrown.

My question is, what is the proper format for this?

1
2
3
4
5
6
7
8
9
10
11
12
Item (string &name, string &code, double &price)
{
try
{
name = name;
code = code;
price = price;
}
if (price < 0)
{
throw error
}


Is this the correct way to do it? So if price is less than 0, then the object shouldnt get created.
Is this the correct way to do it? So if price is less than 0, then the object shouldnt get created.


The way to do this is not inside the constructor. The constructor is what happens when the decision has already been made that the object is being created. You know the value of price beforehand. Look at it, and if you don't like it, don't create the object.
The constructor is exactly where such errors are supposed to be detected and reported as exceptions, see e.g. core guidelines https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-throw

As for proper style, look up C++ constructor syntax and do throw something derived from std::exception:

1
2
3
4
Item (const std::string &name, const std::string &code, double price)
: name(name), code(code), price(price) {
    if (price < 0) throw std::invalid_argument("price cannot be negative");
}


(you could also check price before initializing it, although it's not a big deal when it's just a double:
1
2
3
Item (const std::string &name, const std::string &code, double price)
: name(name), code(code), price(price >= 0 ? price : throw std::invalid_argument("price cannot be negative"))
{}
Last edited on
The constructor is exactly where such errors are supposed to be detected and reported as exceptions


I would suggest that this be modified to "the constructor is your last chance to spot that you're about to create an invalid object, and your only choice is to throw an exception because you've backed yourself into a corner".

Commence flamewar.
Commence flamewar.

fine, you can also do this:
1
2
3
// The behavior is undefined if price < 0
Item (const std::string &name, const std::string &code, double price)
: name(name), code(code), price(price) {}

(I actually worked in a company that had a strict code guideline to document all undefined behavior on any narrow-interface function, in a similar manner, as a comment)

although if that matters, I'd be using something like
Item (const std::string &name, const std::string &code, mycorp::nonnegative<double> price)
Last edited on
A good question is: When an exception occurs within the initializer list -> What happens to the members which are supposed to be intitalized after that exception?

Is the destructor called with half initialized members?
If no -> What happens to the members which are actually initialized [with dynamic memory]?
If yes -> What happens to the uninitialized members?
@Cubbi,

Not for the purpose of extending this discussion, because I agree that the constructor is the place for the error checking, but I'm curious about the mycorp::nonnegative<> template class.

Did the constructor for this class throw an exception on a negative value? Would the need for a comment documenting undefined behavior merely move from the (theoretical) Item class to the mycorp::nonnegative<> class?

When an exception occurs within the initializer list -> What happens to the members which are supposed to be intitalized after that exception?

nothing, they never began initializing. Stack unwinding will call destructors of anything that completed its constructor: the members and bases that appear before the member whose initializer or constructor threw.

I'm curious about the mycorp::nonnegative<> template class. Did the constructor for this class throw an exception on a negative value?

Yes, it's the usual C++ practice using invariant-preserving wrapper classes to represent preconditions: the predicate ("non-negative" in this case) is true by construction, so consumers of that wrapper never need to re-check the predicate. The most popular of these are non-null wrappers and indeed it's always a major design question: should the wrapper allow a narrow-contract ("undefined behavior unless") constructor overload? Dropbox's not-null wrapper has one: https://github.com/dropbox/nn/blob/master/nn.hpp#L105-L107 , Microsoft GSL chooses by a #define switch https://github.com/Microsoft/GSL/blob/master/gsl/gsl_assert#L61-L83 the behavior of not_null's https://github.com/Microsoft/GSL/blob/master/gsl/gsl#L81
Last edited on
@Cubbi

1
2
3
4
Item (const std::string &name, const std::string &code, double price)
: name(name), code(code), price(price) {
    if (price < 0) throw std::invalid_argument("price cannot be negative");
}


Does this constructor assign name to name, code to code, price to price, but if price is less than 0, it throws in the error and cancels the previous assignment of name = name, code = code, price = price?
yes: opening brace of the constructor is only entered after every base and member is fully initialized, so if the body of the constructor exits by exception, the destructor of every base and member is called (in reverse order of initialization).
Topic archived. No new replies allowed.