Handling class invariant violations

D

DaKoadMunky

Say I have the following...

class Foo
{
public:

Foo() : bar(0) {}

void SetBar(int);

private:

int bar;
};

Say that an invariant for my class is that Foo::bar always be an even number.

If somebody calls Foo::SetBar(int) with an odd number what is the best way to
handle it?

1) assertions while in debug mode (if release mode let caller suffer the
consequences)?
2) throw an exception?
3) return an error code?
4) other?

In all cases I would leave the value of Foo::bar unchanged. The question is
how and if to let the caller know the value was not changed.

Throwing an exception seems like the least likely solution. Is it valid to use
exceptions to invalidate input or is that an abuse of exception handling?

I am leaning towards solution #1.

Any help would be appreciated.

Brian
 
C

Cy Edmunds

DaKoadMunky said:
Say I have the following...

class Foo
{
public:

Foo() : bar(0) {}

void SetBar(int);

private:

int bar;
};

Say that an invariant for my class is that Foo::bar always be an even
number.

If somebody calls Foo::SetBar(int) with an odd number what is the best way
to
handle it?

Your example is so artificial that it doesn't give enough context to answer
this question.
1) assertions while in debug mode (if release mode let caller suffer the
consequences)?
2) throw an exception?
3) return an error code?
4) other?

In all cases I would leave the value of Foo::bar unchanged. The question
is
how and if to let the caller know the value was not changed.

Throwing an exception seems like the least likely solution. Is it valid
to use
exceptions to invalidate input or is that an abuse of exception handling?

If calling SetBar with an odd number is an error which could be caused by
user input I don't see why throwing an exception is wrong. Remember, the
caller may be able to fix the problem with the information you provide in
the class you throw. However, this cannot really be decided in the abstract.
I am leaning towards solution #1.

That could be right, particularly if the integer argument can only be the
programmer's fault, not the client's fault. Hopefully you can test the code
enough to flush out any such logic errors. However, if the user input can
lead to this condition this approach is probably wrong. I'll bet you are a
client for software yourself -- if you put in inconsistent input would you
like an explanation of how to fix it or watch helplessly as the program
crashes?
Any help would be appreciated.

I would add that exceptions are better than error codes in most cases.
Extensive use of error codes makes the calling software look like a sequence
of error checks, and it becomes easy to lose the thread of what is actually
going on when there are no errors. I use error codes sometimes, usually when
I am implementing a C interface to a DLL or something like that.
 
V

Victor Bazarov

DaKoadMunky said:
Say I have the following...

class Foo
{
public:

Foo() : bar(0) {}

void SetBar(int);

private:

int bar;
};

Say that an invariant for my class is that Foo::bar always be an even
number.

If somebody calls Foo::SetBar(int) with an odd number what is the best way
to
handle it?

1) assertions while in debug mode (if release mode let caller suffer the
consequences)?
2) throw an exception?
3) return an error code?
4) other?

In all cases I would leave the value of Foo::bar unchanged. The question
is
how and if to let the caller know the value was not changed.

Throwing an exception seems like the least likely solution. Is it valid
to use
exceptions to invalidate input or is that an abuse of exception handling?

I am leaning towards solution #1.

First, let me note that this is not a language problem. This is a software
engineering problem, or perhaps a library design problem. In any case, do
what you feel is the requirement of the users of the function.

Second, since you asked, I'll say where I lean. If you allow the user to
pass in _any_ value (as implied by the type and the name), throwing is not
the right thing, since no value is an _exception_. Debug-time assertions
are only good if you make sure your test runs cover _all_ cases, so you
only use assertions to find an error in the code that calls your function.
If you release your function to the user, assertions will not help. Error
codes are good if your user is going to actually verify them.

So, considering all that, none of your 1-4 suggestions would work. What to
do, you might ask. There is no fool-proof solution here, unfortunately.
I would probably implement the function as

void SetBarToEvenPartOf(int i) { bar = (i/2)*2; }

and document the hell out of the fact that calling that function with 3 will
actually set bar to 2.

V
 
A

Arijit

DaKoadMunky said:
Say I have the following...

class Foo
{
public:

Foo() : bar(0) {}

void SetBar(int);

private:

int bar;
};

Say that an invariant for my class is that Foo::bar always be an even number.

If somebody calls Foo::SetBar(int) with an odd number what is the best way to
handle it?
1) assertions while in debug mode (if release mode let caller suffer the
consequences)?
2) throw an exception?
3) return an error code?
4) other?

In all cases I would leave the value of Foo::bar unchanged. The question is
how and if to let the caller know the value was not changed.

Throwing an exception seems like the least likely solution. Is it valid to use
exceptions to invalidate input or is that an abuse of exception handling?

I believe exceptions is the only solution. If you document the fact the
SetBar should only be called with even number, calling it with odd
number is an exceptional situation. Asserts are generally used to catch
bugs in your code, not someone's else's code. Error codes is an option,
but exceptions look cleaner.

-Arijit
 
I

Ivan Vecerina

DaKoadMunky said:
Say I have the following...

class Foo
{
public:

Foo() : bar(0) {}

void SetBar(int);

private:

int bar;
};

Say that an invariant for my class is that Foo::bar always be an even
number.

If somebody calls Foo::SetBar(int) with an odd number what is the best way
to
handle it?

1) assertions while in debug mode (if release mode let caller suffer the
consequences)?
Is the risk worth taking?
What are the possible consequences of storing an invalid value?
Can you be sure that debug-mode testing will catch all the possible errors
in all programs that use your calss?
Can you exclude the possibility that the value is obtained from user
input, or from a source file that might be corrupted?
I'd much prefer to detect and report the error in such cases -- especially
if a broken class invariant could later trigger undefined behavior...
2) throw an exception?
Yes.
Indeed, today it is not uncommon to replace the classic 'assert' with
a call that throws an exception (derived from std::logic_error) even in
release mode - at least when checking preconditions/inputs of a function.
You could even define a new "macro" dedicated to this (e.g. PRECONDITION).
3) return an error code?
Knowing that most users tend to disregard retuned status codes?
No. This is what exceptions are for.
4) other?
Automatically "fix" the input, or choose a default behavior?
This can be an acceptable choice, if there is a reasonable choice for a fix.
This could then be combined with a warning check (which would act like
assert in debug mode, and either do nothing or log an event in release
mode).
I sometimes use a "WARN_IF" macro for such cases, defined as something like:
#define WARN_IF(b) ((b)&&(IMPL_show_warning(#b))

For example:
void SetBar(int i) { WARN_IF(i&1); bar = i&~1; };
or:
void SetBar(int i) { if(WARN_IF(i&1)) i=0; bar = i; }
In all cases I would leave the value of Foo::bar unchanged. The question
is
how and if to let the caller know the value was not changed.

Throwing an exception seems like the least likely solution. Is it valid
to use
exceptions to invalidate input or is that an abuse of exception handling?
Definitely not an abuse.
std::logic_error, as I understand it, is designed as a base class for
such exceptions.
I am leaning towards solution #1.
Based on past experience, I (personally) strongly recommend #2 or #4.


Regards,
Ivan
 
M

Matthias =?ISO-8859-1?Q?K=E4ppler?=

Throwing an exception seems like the least likely solution. Is it valid
to use exceptions to invalidate input or is that an abuse of exception
handling?

Why? Of course it is. There's even a pre-defined exception called
std::invalid_argument (which is of type std::logic_error).
It's used to throw on exactly those occasions, in fact if the client (be it
programmer or user) has passed a faulty value on which your code can't
operate.

Well, one might argue if this situation is "exceptional". That however
depends on your code and the overall context.

Regards,
Matthias
 
A

adbarnet

If the parameter you pass must be an even number, then it isn't really an
int anymore - so I'd say your function specification is incorrect - I'd
define a class EvenNumber, and use that as an input -

void SetBar(const EvenNumber & )...

Ok this just defers the question to whether to throw an exception if a user
attempts to construct / assign an odd number to an instance of EvenNumber (I
would), but it's more obvious to users what is intended.

ad
 
A

Alf P. Steinbach

* DaKoadMunky:
Please use at least a pronouncable nick (or your real name, best).

Say that an invariant for my class is that Foo::bar always be an even number.

If somebody calls Foo::SetBar(int) [which does nothing but assign the argument
value to Foo::bar] with an odd number what is the best way to handle it?

There is a mismatch between the interface you present to client code,
and the internal class invariant.

As it is, the caller of Foo::SetBar must use a redundant representation.

The simplest and best way to handle that problem is to decide whose job
it is to _reduce_ the redundancy to the required non-redundant
representation.

Is it your class? Then throw an exception (but contrary to advice given
elsewhere in this thread, don't use the standard's predefined exception
type for that, because it derives directly from std::exception; use
std::runtime_error or a derived exception class). Also then consider
adding a middleman argument type, e.g. EvenNumber, that can do this.

Is it the client code? Then change the interface. E.g. document that
SetBar will use twice the argument value (or whatever).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top