[Design] Should accessors throw exception ?

M

mathieu

Hi there,

I'd like to know if there is a general answer to the following
question: when making a public interface that access a container
should the function throw an exception when the element is not found ?
Otherwise I need two functions: a Find and a Get function which put
the burden on the application programmer to always call Find before
Get.
The other alternative would be to return some kind of sentinel (like
std::set<>::end() )...

Comments ?

Thanks
-Mathieu

eg.


#include <map>
#include <string.h>

struct Phone { int P; };
struct Name { const char *N;
bool operator <(Name const &n) const { return strcmp(N,n.N) < 0; }
};
struct YellowPage
{
void Insert(Name const &n,Phone const &p)
{ I.insert( std::map<Name,Phone>::value_type(n,p) ); }
bool Find(Name const & n) { return I.find(n) != I.end(); }
Phone const &Get(Name const&n) { return I.find(n)->second; }
private:
std::map<Name,Phone> I;
};
 
R

Ron AF Greve

Hi,

I think returning a special value like end() or 0 for a pointer is the best
solution.

I think exception should only be used when something exceptional occurs, for
instance you expect a conifiguration file is there yet when you try to open
it it is missing or you try to access a database but the DBA took it down
for maintenance. This way you (or the user of your library) can catch those
thiings somewhere 'high up' in the code knowing that everything in between
is released and handled nicely and continue (maybe waiting a few minutes and
try again.

And indeed a find/get combination is more work. Also with the end() you make
it work the same as the STL.

Regards, Ron AF Greve

http://www.InformationSuperHighway.eu
 
I

Ian Collins

mathieu said:
Hi there,

I'd like to know if there is a general answer to the following
question: when making a public interface that access a container
should the function throw an exception when the element is not found ?
Otherwise I need two functions: a Find and a Get function which put
the burden on the application programmer to always call Find before
Get.

I tend to favour returning NULL if the return type is a pointer and
throwing an exception if the return type is a reference or an object
without an obvious bad value.
 
J

James Kanze

I'd like to know if there is a general answer to the following
question: when making a public interface that access a
container should the function throw an exception when the
element is not found ? Otherwise I need two functions: a Find
and a Get function which put the burden on the application
programmer to always call Find before Get.

It depends on the application. I used two different solutions
in my pre-standard AssocArray (a hash table): the operator[] had
a pre-condition that the element was present, and asserted this
precondition, but there was a get function which returned a
pointer to the element, and returned a null pointer if the
element wasn't found. In other contexts, I've found the
behavior of std::map<>::eek:perator[] to be useful as well: just
insert the missing element. On the other hand, I can't think of
a context where throwing an exception would be the desired
behavior.
The other alternative would be to return some kind of sentinel
(like std::set<>::end() )...

Just return a pointer to the element, rather than a reference,
and you have a sentinel value defined by the standard, the null
pointer.
 
J

James Kanze

mathieu wrote:
I tend to favour returning NULL if the return type is a
pointer and throwing an exception if the return type is a
reference or an object without an obvious bad value.

Isn't this inversing cause and effect. I tend to choose to
return a pointer if I need a sentinel value, and a reference
otherwise.
 
I

Ian Collins

James said:
Isn't this inversing cause and effect. I tend to choose to
return a pointer if I need a sentinel value, and a reference
otherwise.
Not really, consider dynamic_cast. I think you have given the same
answer I did in a different way. NULL is a sentinel value.
 
J

James Kanze

Not really, consider dynamic_cast. I think you have given the same
answer I did in a different way.

Partially, but you seemed to be saying that you chose the means
of reporting errors according to the return value, where as I
would choose the type of the return value according to whether I
needed to report errors or not. (Supposing there is a choice,
of course. For constructors and overloaded operators, you often
don't have any choice but to throw an exception in case of
error.)
NULL is a sentinel value.

Which is why I choose to return a pointer if I need a sentinal
value. (In cases where I don't actually have something to
point to, for example if the function returns a calculated
value, I will use Fallible.)
 
G

Gennaro Prota

James said:
Which is why I choose to return a pointer if I need a sentinal
value. (In cases where I don't actually have something to
point to, for example if the function returns a calculated
value, I will use Fallible.)

Do you have particular reasons to avoid Fallible references?
 
J

James Kanze

[...]
One design I have found handy:
T GetByName(const std::string name) const; // throws if name not found
T GetByName(const std::string name, T defaultvalue) const; // returns
defaultvalue if name not found
So the client code has the option to choose if it wants a
hard or soft fallback.
Nifty pattern, but wouldn't it tend to expand the interface
and bloat implementation if there were lots of functions like
this?

How many getters do you have in one class? In cases where you
systematically have something like this (e.g. data base access,
with null values in some columns), you would, of course, use a
class to represent nullable values; the above function would be
in that class, and the class representing a row would simply
return instances of that class.
I'd think returning something like boost::eek:ptional<T> that
throws when attempting to dereference when empty would be
best:
template<typename T>
checked_optional {
T t;
bool exists;
public:
checked_optional() : exists( false ) { }
checked_optional( T t ) : t( t ), exists( true ) { }
operator bool () const { return exists; }
T& operator * () const
{
if ( !exists )
throw "Bla bla";
return t;
}
};
checked_optional<T> GetByName( std::string name );
void use_t( T );
// Just dereference result if you want exception
use_t( *GetByName( "foo" ) );

But that doesn't solve the problem. At the client level, if you
want a default value, you have to declare an instance of a
variable, then test it. The whole point is to be able to
combine everything into a simple functional expression. (Also,
the use of implicit conversion to bool is blatant overload
abuse. If you want to check for validity, provide an isValid()
function.)
// Check the result before dereferencing if you want to handle not found
checked_optional<T> t = GetByName( "foo" );
if ( t )
use_t( *t );
else
not_found();

Wordy and obfuscated. And the interesting example would call
use_t with a default value if the value wasn't found, e.g.:

use_t( container.get( key ).elseDefaultTo( defaultValue ) ) ;
 
J

James Kanze

Do you have particular reasons to avoid Fallible references?

Well, the original implementation of Fallible required that the
instantiation type be default constructable, so you couldn't
instantiate it with a reference. At the time, I considered
removing this limit, precisely in order to support references.
I decided against it on the grounds that the standard already
defined a Fallible for references---it's called a pointer.

I recently did modify my Fallible to support types without
default constructors. But that was because I needed it for an
actual class type which didn't have a default constructor. The
alternative would have been to add a default constructor to the
class, which would have required adding a zombie state to the
class. And which, of course, would have only solved the problem
in this one particular case, and not generally.
 
G

Gennaro Prota

James said:
Well, the original implementation of Fallible required that the
instantiation type be default constructable, so you couldn't
instantiate it with a reference. At the time, I considered
removing this limit, precisely in order to support references.
I decided against it on the grounds that the standard already
defined a Fallible for references---it's called a pointer.

Yes, that answers my question. Compared to a pointer, of course,
Fallible<> also provides a check (which may be or not what you want, but
is a feature :)).

FWIW, my implementation of Fallible allows references, because it holds
the T indirectly, via a holder< T > which is specialized for reference
types, but I haven't had so far a concrete chance to use them. This was
part of the reason for my question.
I recently did modify my Fallible to support types without
default constructors. But that was because I needed it for an
actual class type which didn't have a default constructor. The
alternative would have been to add a default constructor to the
class, which would have required adding a zombie state to the
class. And which, of course, would have only solved the problem
in this one particular case, and not generally.

Yes, that certainly makes sense. I've encountered the same issue and,
for the moment, I just did without Fallible, as I'm right in the middle
of other interesting stuff :)
 
J

James Kanze

Yes, that answers my question. Compared to a pointer, of
course, Fallible<> also provides a check (which may be or not
what you want, but is a feature :)).

It provides an earlier check, but on all of the implementations
I use, dereferencing a null pointer causes a core dump, exactly
like using the value of Fallible<> when it's not valid. (Well,
almost exactly: the check for accessing through a null pointer
has no runtime cost, and cannot be disabled; the check for using
an invalid Fallible said:
FWIW, my implementation of Fallible allows references, because
it holds the T indirectly, via a holder< T > which is
specialized for reference types, but I haven't had so far a
concrete chance to use them. This was part of the reason for
my question.

Well, I'd still use a pointer rather than a Fallible reference,
simply because it is standard. On the other hand, as I said, I
did need a Fallible once for a class which didn't have a
reasonable default constructor. I don't have any Holder<>
class; I just implemented the usual separation of allocation and
initialization directly in Fallible<> (using an unsigned char[]
in the Fallible<> for allocation).
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top