James said:
Matthias Buelow wrote:
Jeff Schwab wrote:
[...]
If I wanted to store objects in an MFC container, I had to
inherit them from a particular class, override virtual
methods, and generally jump through a bunch of artificial
hoops.
A technique which went out of favor even before I learned C++,
around 1990.
I'm talking mid to late nineties, 10-12 years ago.
Microsoft aways was a little behind the times.
Thanks, I'll take a look at those. Are they still worth using?
For the most part, they aren't even still around. I don't know
what happened with USL (which was the half standard library for
a long time), but Unix System Laboraties has been gone for ages.
Booch components was bought up by Rogue Wave, so that they
wouldn't have to compete with it. (The Booch components were
particularly well designed.)
You might want to take a look at OSE
(
http://ose.sourceforge.net). IMHO, a lot better designed and
easier to use than the STL. Above all, a different approach.
It tend to use STL mainly as the low level tools, over which I
build the library I actually use. OSE is usable directly.
I don't know about "most people," but there was a relatively advanced
technice that I have used in C called XInclude:
#define ELEM_T int
# include "list.h"
#endif
It's a far cry from what C++ templates give you. Googling XInclude just
turns up something related to XML-related processing. Googling XInclude
-XML also fails to turn up the XInclude pattern. It's still not an
especially well-known practice.
Try googling for <generic.h>
.
Some people today claim that templates are too complicated.
They've never used <generic.h>. (Actually, this is a good
example of where language complexity makes life easier for the
No. It wasn't available on the machines I worked on.
Things have gotten better, but even today, most of the
container types in 3rd-party libraries I've seen are nothing
like as sophisticated as the STL.
There are different ways of being sophisticated. There are some
things that are very sophisticated, and above all, very complete
in STL. There are also some serious design flaws, which make it
difficult to do even the simplest operations (e.g. a filtering
iterator, for example).
Take the Qt container types,
I'm afraid I don't know Qt. As soon as I heard that they needed
to preprocess, rather than using straight C++, I dropped it from
consideration. (Not that it makes much difference---the
machines I target typically don't have any terminal attached,
graphic or otherwise, so a GUI isn't really an issue.)
[...]
This is what happens when you try to work
outside the language, rather than within it.
Agreed. That's why I didn't bother looking any further when I
heard you needed a special pre-processor.
If I were going to write something with a GUI, I'd probably give
wxWidgets a trial. On the other hand, GUI's are something that
Java actually does quite well. (More because Swing is well
designed, that because of anything in the language itself.)
It may have been in the air, but I didn't smell it.
ADT's have a long, long history. I first heard about them in
the late 1970's. They were common practice in languages that
supported (Lisp dialects, Ada) them by the mid 1980's.
The only other "iterators" I was using at the time were
hateful little C-style things that were intended to work like
this:
some_lib_iter* iter = some_lib_create_iter(some_lib_some_container);
while (!some_lib_iter_done(iter)) {
some_item* item = (some_item)some_lib_iter_next(iter);
// ...
}
By the way, I'm currently using a recently written,
professional, industry-specific C++ library that supports
almost the same idiom, and I still don't like it.
It's very close to the USL idiom
. And the Java one. And
yes, combining advancing and accessing in a single function is
NOT a good idea. But one iterator still beats two, when you
have one function providing the range for another, or when you
want a filtering iterator. (Look at all the hoops
boost::iterator has to jump through to make their stuff work.)
It hasn't been a problem for me. Maybe I've just been spoiled
by being a client, rather than an implementer, of the STL.
So how to you write a function which returns a range, and use
the return value of that function as the argument to a function
which takes a range? Or how do you use the decorator pattern on
an iterator, to provide a filtering iterator?
My usual policy today is to write my iterators according to the
GoF pattern, with three functions: isDone(), next() and
element(), adding isEqual() if at all possible. And to have
them inherit from IteratorOperators<>, which uses the Barton and
Nackman trick to provide the STL interface. When it makes no
difference otherwise, I'll use the STL interface, on the grounds
that that's what most C++ programmers will be most familiar
with. But the clean interface is there if I need it, e.g. for
chaining, filtering, etc.
That depends whom you ask. I always considered it a part of
the standard library that was "retrofitted" to be STL-like.
Not really. The STL was a library developped by Stepanov while
he was at HP, and it didn't contain a string type. But when a
large part of the STL was adopted into the standard, the string
class that was there was retrofitted to sort of conform.
Scott Meyers includes it as part of the STL in his book,
Effective STL, even though he does not include (for example)
the iostreams. SGI seems to claim std::basic_string as part
of the STL:
SGI has extended the STL to include hash tables, strings, and
probably a few other things.
The fact that those aren't member functions does not mean
they're difficult; in fact, they're trivial to write.
But you have to write them
.
Seriously, the problem with std::string is that it is sort of a
bastard---it's too close to an STL container of charT to be an
effective abstraction of text, and it adds a bit too much which
is text oriented to be truly an STL container.
In its defense: even today, I'm not sure what a good abstraction
of text should support.
Case-insensitive compare is covered in plenty of introductory
C++ texts, because it's one of the easiest things to show
people.
Case insensitive compare is one of the most difficult problems I
know. Just specifying it is extremely difficult.
Then your code is probably wrong. (Note that you're certainly
not alone in this. The toupper and tolower functions in C and
in C++ all suppose a one to one mapping, which doesn't
correspond to the real world, and every time I integrated my
pre-standard string class into a project, I had to add a
non-const []---although the class supported an lvalue substring
replace:
s.substring( 3, 5 ) = "abcd" ;
was the equivalent of
s = s.replace( 3, 5, "abcd" ) ;
.)
(And of course, the [] operator of std::string gives you
access to the underlying bytes, not the characters.)
That's a sometimes-true but fundamentally misleading
statement. If you have a character type that serves better
than char or wchar_t, you're free to instantiate basic_string
with it, specialize char_traits for it, and generally define
your own character type.
Are you kidding. Have you ever tried this?
But that's not the problem. I usually use UTF-8, which fits
nicely in a char. But [] won't return a character.
The lack of a real Unicode character type in the standard
library is a valid weakness, but not a fundamental limitation
of the std::basic_string.
Even char32_t will sometimes require two char32_t for a single
character: say a q with a hacek.
And by the way, I was relating my own experience. At the time
I first used std::string, the characters I needed to represent
fit very comfortably into bytes, and the [] operator did
provide correct access to them.
Take a look at my .sig. I should be obvious that this is not
the case for me.
But even in English, if you're dealing with text, how often do
you replace a single letter, rather than a word?
I suggested / (in C++, & has the wrong precedance), but that
went over like a lead ballon.
I like operator+ as a concatenator. What I think is more
confusing (until you get used to it) is that the same operator
is valid for individual chars, but with completely different
meaning.
That's because char isn't a character type, but a small integral
type. That's because we don't have a character type. (As
mentionned above, I'm not sure that we have enough practice,
even today, to know what we would really need in a character
type, so perhaps the current situation is the best we can do.
or would be, if char were required to be unsigned.)
int main() {
char c = 'c', d = 'd';
/* "199" on ASCII platforms. */
std::cout << (c + d) << '\n';
}
The standard library types behave so much like primitive types
most of the time, that I find it jarring when they are
different.
That difference is correct, but I find it a natural extension
of iteration. Anyway, what the STL calls iterators are really
more like "pointer-like objects that may be used for
iterating, but may sometimes also be used for other stuff."
Exactly. Which is both their force and their weakness.
[...]
No, I mean acquiring and releasing with each dereference. You first
create a type that acquires the lock in its constructor and releases in
its destructor. The smart pointer creates a temporary of that type on
each dereference. This implements the extreme case of fine granularity,
acquiring the locking the mutex for an absolute minimum amount of time,
but with potentially frequent calls to the locking code. Accessing an
object only through such a locking smart-pointer is similar to using a
Java object with only "synchronized" methods, where every time you call
a method, you first have to get a lock on the object.
That much I understood. One of the things that I found out
quickly in Java is that synchronized methods were useless, and
could only be considered a misfeature. (But I guess it's nice
to say that in C++, you can emulate even the misfeatures of
another language.)
Of course, I'd thought about the technique you describe above,
and rejected it because it doesn't work in some frequent cases,
e.g.:
f( smartPtr->get1(), smartPtr->get2() ) ;
[...]
On the whole, lambda expressions/classes are something that
needs real language support. Boost::lambda does as much as is
possible without directly language support, but it still isn't
enough to make lambda truely effective.
So you're saying, point blank, flat out, that nobody will ever
be able to write a C++ library that supports lambdas to your
satisfaction? That seems like a pretty sweeping statement.
When folks say "you can't do that without special language
support," C++ seems to prove them wrong a lot; see D&E. I'm
not saying you're wrong; I just don't buy into blanket "that
can't be done" statements without some real proof.
Well, where lambda is concerned, a lot of really intelligent
people have been trying. Boost::lambda is amazing in what it
does, but it still encounters limits.
[...]
Huh? Do you really think you know every nook and cranny of
the standard off the top of your head, including the standard
libraries?
Not every nook and cranny. But I do expect anyone using C++ to
have at least an awareness of what it can do. You don't have to
know the details, but you do have to know that the possibility
exists, and where you should start looking in the documentation.
My point is just that if your goal is to just learn a minimum,
and start hacking code, C++ probably isn't the language for you.
The minimum necessary is a good deal more than for a lot of
other languages. But... the effort you invest won't be wasted,
because once you do have a good grasp of the language, your
productivity will be considerably higher than in any other
language. (At least, that's been my experience. But of course,
I've not tried every other possible language---maybe there's one
out there in which I'd be even more productive. If so, however,
I'm willing to bet that it will be just as complicated as C++,
if not more so.)
It's enough to have a fundamental grasp of the items you use
regularly, and know where to look to get more information. I
do not ever expect to have the whole thing memorized. Even if
I were intimately familiar with the current standard, I'd
still have to update my knowledge every 5 years or so, which
seems to go against your "learn it once" philosophy.
It's a fact of life that in this profession, we can't afford to
stop learning. And unlearning. (About six months ago, I was
cleaninig out some old directories, and came accross some code
I'd written almost 20 years ago. I wouldn't consider such code
acceptable today, but back then, my employers thought very
highly of it.)