Re: Overhead of subscript operator for STL maps
On Oct 18, 1:09 pm, Stephen Horne <sh006d3...@blueyonder.co.uk> wrote:
On Sat, 18 Oct 2008 00:03:47 -0700 (PDT), James Kanze
<james.ka...@gmail.com> wrote:
In C++, logically, you should be able to use [] on a const
map (if you're just reading).
I still don't get this. It doesn't fit with the normal pattern
of expression evaluation for C++. operator[] doesn't know
whether you're reading or not - in fact you're *always*
reading in a sense - just reading a reference rather than a
value.
If you're providing user defined operators, it's up to you to
define them in a "logical" fashion---logical, in this case,
being largely determined by what the built-in operators do.
Logically, if a class supports [], you should be able to use it
on a const object, or the designer of the class has abused
operator overloading. (The language doesn't prevent you from
designing a class in which [] meant the same as /=. But I
wouldn't call that "logical".)
In sum, my "logical" applies at the design level: is this a
logical design for a class.
[...]
And you should be able to use [] even if the mapped type
doesn't have a default constructor.
This is a very good point. I have plenty of types that don't
support default constructors because there is no obvious
default value, and because I never saw a need to default
construct them.
You too:-). I recently modified my Fallible class to support
them, because they are so common.
Doesn't mean I never need to use them as keys.
Keys aren't a problem. Mapped to types are (but only if you
actually use operator[]).
Most of the time, you'll wrap std::map in a class which
provides a more convenient interface for what it's being used
for, which is application specific.
I disagree, though I'm not sure if I disagree enough to
contradict you, if you see what I mean.
Well, for starters: anything fundamental to the application
should be in an application specific class, with as narrow an
interface as posssible. This way, if you later have to change
it, you can. In the case of std::map, the "basic" interface
involves find, insert and erase, and is not really very usable
(or at least very convenient) to use directly. Which leads to
wrapping it into something convenient (and with the desired
semantics) for what is needed in the application.
I often write classes that have several associative containers
as members. I don't count this as "wrapping std::map" - I'm
simply using it within a class, the same as any other type.
OK. My point was just that all use should be well hidden from
the more general code of the application. As an extreme case, I
certainly don't think you'd need a special wrapper class for a
single std::map when implementing a bidirectional map.
The most "wrapping" I do for most container types is to use a
typedef.
It depends. At the lowest level, perhaps, but application code
never really sees any standard container; any use of one is well
hidden in a "wrapper". Of course, you might not consider it
"just a wrapper", because it often does a lot more, ensuring
persistency, or transactional integrity, for example.
Overdoing limited interface wrappers adds an overhead to
development. It's a bit like overdoing the layers of
inheritance. It means you're always having to refactor
everything before you can get the relative information where
it's needed to handle new requirements.
Obviously, you can overdo anything. But carefully isolating
things, and only providing narrow interfaces, pays off in the
long run.
I'm not saying that specialising a standard class is wrong -
just that it's overkill for a lot of common uses of a
container. If I use, for example,
std::map<thisclass,thatclass> in the implementation of class
subsys, I'm probably not going to need the same mapping
anywhere else. Why add another layer of abstraction between
the subsys class and the std::map?
It depends somewhat on what subsys is doing, but if you can't
consider subsys a wrapper, maybe it's doing too much.
That said, the scope in which that container is used is always
pretty limited. The word "wrapper" is taking it too far, IMO,
but your point is probably valid.
Yes. The point isn't meant to be an absolute. But good
decoupling is important, and that includes decoupling higher
level abstractions from the interfaces defined in the standard
library.
My classical example for this is that you shouldn't use
std::string (nor std::vector<char>) directly as your text buffer
in an editor. You should define a TextBuffer class, with
exactly the interface you need for your purposes. You *should*
use std::string to implement this class---in many ways, it
really won't be more than a wrapper. But the day you find that
in fact, for performance reasons, you have to change the
implementation, the use of std::string is well isolated, and you
shouldn't have to modify the application from a to z.
I think the point of something like std::map is that it is
reallly a low level building block
This to me is also a little surprising - or at least it
depends on what you mean by low level, and what you expect
from low level tools.
To me, a low level binary tree tool should provide
data-structure handling algorithms, but shouldn't act as a
container at all. If I feel like rearranging my binary tree
into a linked list simply by modifying all the link fields and
then remembering to treat it differently, that should be my
right. If I feel like partitioning all the nodes into several
classes, and building each of those node sets into a separate
tree, that should also be my right. I should have direct
access to all the internals, so I can do with them as I
please. With a low level library, the rights and the
responsibilities should be mine, with no unnecessary barriers
in the way.
To me, a container should be a high level wrapper around a low
level data structure. Two different libraries appropriate to
two different levels of abstraction.
Trouble is, I guess, that high vs. low level isn't a simple binary
choice but rather a matter of degree.
In the end, yes. All of the implementations of std::map I've
seen use an even lower level class which implements the binary
tree (and is also used in e.g. std::set). On the other hand, a
data dictionary or a keyed access method to a data base is *not*
a container, and you probably don't want to support all of the
classical container interfaces to it. And most applications
need a data dictionary, or a keyed access method, and not a
generic std::map.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34