maps that don't insert default items

S

shea martin

I would like to use maps as a container type, but I don't like how the
[] operator returns a default value. Is there a way to make [] return
NULL if it can't find the value?

I know I can do this:

typedef numMap map<int,myClass*>;
numMap nums;

if( nums.find(99) != nums.end() )
{
cout << "nums[99] is: " << nums[99] << endl;
}

I don't know how efficient this is, as it looks up the key 99 twice. To
avoid this situation:

numMap::iterator itr = nums.find(99);
if( itr != nums.end() )
{
cout << "nums[99] is: " << *nums << endl;
}

I don't find all this very convenient. Perhaps I am just complaining
too much.

~S
 
V

Victor Bazarov

shea martin said:
I would like to use maps as a container type, but I don't like how the
[] operator returns a default value. Is there a way to make [] return
NULL if it can't find the value?

Don't use operator[].
I know I can do this:

typedef numMap map<int,myClass*>;
numMap nums;

if( nums.find(99) != nums.end() )
{
cout << "nums[99] is: " << nums[99] << endl;
}

I don't know how efficient this is, as it looks up the key 99 twice. To
avoid this situation:

numMap::iterator itr = nums.find(99);
if( itr != nums.end() )
{
cout << "nums[99] is: " << *nums << endl;
}

I don't find all this very convenient. Perhaps I am just complaining
too much.

Yep.

Victor
 
S

Shane Beasley

shea martin said:
I would like to use maps as a container type, but I don't like how the
[] operator returns a default value. Is there a way to make [] return
NULL if it can't find the value?

First, the default value for a pointer is NULL, so it *does* return
NULL if it can't find the value. However, you might be concerned that
operator[] inserts the element if it wasn't already present, and also
that, because of this, it can't be used on const maps.
I know I can do this:

typedef numMap map<int,myClass*>;
numMap nums;

if( nums.find(99) != nums.end() )
{
cout << "nums[99] is: " << nums[99] << endl;
}

I don't know how efficient this is, as it looks up the key 99 twice.

Yes, it is, isn't it...
To avoid this situation:

numMap::iterator itr = nums.find(99);
if( itr != nums.end() )
{
cout << "nums[99] is: " << *nums << endl;
}

That's how it is intended to be used.
I don't find all this very convenient. Perhaps I am just complaining
too much.

Sounds like it. However, don't forget that C++ actually allows you to
write your own functions:

template <typename K, typename V, typename P, typename A>
V *find (std::map<K, V *, P, A> &m, const K &key) {
typename std::map<K, V *, P, A>::iterator i = m.find(key);
return (i != m.end()) ? i->second : 0;
}

template <typename K, typename V, typename P, typename A>
const V *find (const std::map<K, V *, P, A> &m, const K &key) {
typename std::map<K, V *, P, A>::const_iterator i = m.find(key);
return (i != m.end()) ? i->second : 0;
}

class myClass;

int main () {
typedef std::map<int, myClass *> numMap;
numMap nums;

if (myClass *obj = find(nums, 99)) {
std::cout << "nums[99] is: " << obj << std::endl;
}
}

- Shane
 
E

Evan

shea martin said:
I would like to use maps as a container type, but I don't like how the
[] operator returns a default value. Is there a way to make [] return
NULL if it can't find the value?

Blah, it's annoying behavior in certain situations I'll agree, but
there's no good way for it to be implemented as you (and I) desire.
Returning null works if the map value is a pointer (and in fact this
is what happens, though it still adds a null pointer to the map which
is probably not what you want), but not otherwise. There are three
alternatives:

1. Raise an exception,
2. Use something like the boost::eek:ptional,
3. Act like set and return a pair with the second value a bool to
indicate if it was in the map.

The first option would rarely be ideal, as exceptions are somewhat
costly at runtime and such an error would usually not be of a nature
that would merit their use.

The second and third options are very close to the same solution, but
both destroy the niceness of the [] syntax.

Besides, doing so would then make it impossible to insert values into
the map using [], so you can only have it one way.

I know I can do this:

typedef numMap map<int,myClass*>;
numMap nums;

if( nums.find(99) != nums.end() )
{
cout << "nums[99] is: " << nums[99] << endl;
}

I don't know how efficient this is, as it looks up the key 99 twice. To
avoid this situation:

numMap::iterator itr = nums.find(99);
if( itr != nums.end() )
{
cout << "nums[99] is: " << *nums << endl;
}

I don't find all this very convenient. Perhaps I am just complaining
too much.

~S
 
M

Michiel Salters

shea martin <[email protected]> wrote in message news:<[email protected]>...
template <typename K, typename V, typename P, typename A>
V *find (std::map<K, V *, P, A> &m, const K &key) {
typename std::map<K, V *, P, A>::iterator i = m.find(key);
return (i != m.end()) ? i->second : 0;
}

This is non-portable, as std::map may have extra
implementation-defined arguments. You'd need something like

template < typename MAP, typename pV = typename MAP::value_type >
pV find ( MAP &m, typename MAP::key_type const& key )

( nested types could be wrong, but you get the idea )

Regards,
 
C

Cy Edmunds

In "The C++ Programming Language" Third edition on page 374 he says:
"Exception handling is a less structured mechanism than local control
structures such as if and for and is *often les efficient* when an
exception is actually thrown."
Looks like it is costly at run time, though I guess the cost is
implementation specific.

He is saying that a thrown exception may be somewhat less efficient than an
if/then/else! If it's even in the ballpark, that's still pretty efficient.
However, to select a design for a relatively rare event like not finding
something you expect to find in a map, run time efficiency would be WAY down
my list of criteria. Maintainability would probably be first, as it usually
is.

Exceptions are a great feature of the C++ language, and refusing to use them
based on antecdotal evidence of run time inefficiency is unsound IMHO.
 
C

Cy Edmunds

Evan said:
"Cy Edmunds" <[email protected]> wrote in message
Costly at run time? What makes you think so? Stroustrop says they are so
efficient at runtime that it is tempting to just use them as control
structures. (But then he says not to anyway.)

Hmmmm, I've recently read somewhere that they are somewhat
inefficient. I can't remember where (in fact, the delay in responding
is partially to see if I would think of it) or I'd give you a
reference. It's possible that it was wrong as I too have heard of
exceptions used for control structures.

Still, that wasn't the main reason I decided that they were
inappropriate. The main reason was that which I wrote later:
Besides, doing so would then make it impossible to insert values into
the map using [], so you can only have it one way.

I thought that it might be possible to optionally install and
uninstall exception handlers for [], but dismissed it. However, I have
since read in Josuttis about the standard streams using a similar
approach to dealing with input errors, so maybe that could be an
optional extension for library makers (I'm not sure if it conflicts
with any of the standard, though I suspect it might with an exception
gurantee).

-Evan

OK, you have shamed me into getting around to publish some code which in
fact lets you do it "both ways". You can go with [] if that suits you but
use some functions which throw exceptions otherwise. Just look under
"throwmap" in the link in my sig.
 

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

Forum statistics

Threads
474,127
Messages
2,570,757
Members
47,315
Latest member
robertsoker

Latest Threads

Top