|
|
A safer/better C++?
Hello everybody,
I would like to get some opinions about whether it would make sense to
change the C++ design principles, so it would become safer for beginner?
I'm assuming there are two kind of beginner:
1) A beginner who, through reading and education, will eventually become an
advanced developer and maybe even an expert developer. For this discussion
I simple call them "novice" developer.
2) A beginner who doesn't read or educate himself and simply muddles
through. I would like to call this kind of developer "ignorant" developer.
Now both kind of developers start to program and sooner or later they start
to use arrays. Both will probably at some point start to use a vector. I'm
also assuming that both developers have difficulties with the index and the
designer of vector obviously also assumed this, why else would there an
at()?
The novice developer reads more about vector and learns, that there are two
ways to access the elements. A fast but unsafe way via the operator[] and a
version which is little slower but safer way via at(). He starts to use at()
because it is a lot safer and he doesn't need that last bit of speed.
The ignorant developer also starts to use vector but doesn't learn that
there a two interface, so he still uses operator[] and still has problems
and bugs with the index access.
So my question is now, would it not have been better to reverse the semantic
of the two versions, meaning operator[] checks the index and throws an
exception and at() would allow an unchecked access? I'm assuming of course
that either beginner starts to use the vector via the operator[]. If this
assumption would be correct, then a beginner would start with the safe
version and maybe, if he needs that additional speed, later use the at()
version.
This is of course easiest done in libraries, because then the language
doesn't have to be changed. But I wonder whether it would make sense also
in a new language design. It wouldn't be C++ anymore, because it would break
backwards compatibility, but maybe a worthy C++ successor?
To give some background on this thoughts:
I'm developing in C++ for about 15 years and in recent years I got the
feeling, that C++ is loosing ground to other languages like Java and C#. I
simply love the sheer power I have with C++ and if I shoot myself in the
foot, so to speak, I simply learn to aim better, but I also learned, that
for beginners it is often to difficult to learn. There are simply to many
mistakes a beginner can make and only trough education does he learn to
avoid those beginner mistakes. Mind you, I'm only referring to unsafe
constructs which led to crashes. I'm not talking about supposedly complex
features like operator overloading or multiple inheritance. If a beginner
doesn't understand these features he probably won't use them. But because
of the forementioned ignorant developers, the language is getting a bad
reputation for it's "unsafety" which also influences decision makers to
abandon C++ projects. I read of course Bjarne Stroustrup's excellent
posting (http://www.research.att.com/~bs/blast.html) where he addresses
some of the issues. But the questions remains:
Should C++ changed to be a more beginner friendly language?
I'm very interested in hearing your opinions.
Kind regards
Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/6/2005 6:17:34 PM |
|
On 6 Dec 2005 13:17:34 -0500, Peter Most <Peter_Most@gmx.de> wrote:
>Hello everybody,
>
>I would like to get some opinions about whether it would make sense to
>change the C++ design principles, so it would become safer for beginner?
>
>I'm assuming there are two kind of beginner:
>1) A beginner who, through reading and education, will eventually become an
>advanced developer and maybe even an expert developer. For this discussion
>I simple call them "novice" developer.
>2) A beginner who doesn't read or educate himself and simply muddles
>through. I would like to call this kind of developer "ignorant" developer.
>
>Now both kind of developers start to program and sooner or later they start
>to use arrays. Both will probably at some point start to use a vector. I'm
>also assuming that both developers have difficulties with the index and the
>designer of vector obviously also assumed this, why else would there an
>at()?
>
>The novice developer reads more about vector and learns, that there are two
>ways to access the elements. A fast but unsafe way via the operator[] and a
>version which is little slower but safer way via at(). He starts to use at()
>because it is a lot safer and he doesn't need that last bit of speed.
>
>The ignorant developer also starts to use vector but doesn't learn that
>there a two interface, so he still uses operator[] and still has problems
>and bugs with the index access.
>
>So my question is now, would it not have been better to reverse the semantic
>of the two versions, meaning operator[] checks the index and throws an
>exception and at() would allow an unchecked access? I'm assuming of course
>that either beginner starts to use the vector via the operator[]. If this
>assumption would be correct, then a beginner would start with the safe
>version and maybe, if he needs that additional speed, later use the at()
>version.
It's an interesting idea, but I think the way it is now is more
intuitive. Arrays do no checking when using [], so why should
vector::operator[]? The fact that at() doesn't exist for arrays
implies (to me) that it is more special and has enhanced functionality
(i.e. does checking of the index).
>This is of course easiest done in libraries, because then the language
>doesn't have to be changed. But I wonder whether it would make sense also
>in a new language design. It wouldn't be C++ anymore, because it would break
>backwards compatibility, but maybe a worthy C++ successor?
>
>To give some background on this thoughts:
>
>I'm developing in C++ for about 15 years and in recent years I got the
>feeling, that C++ is loosing ground to other languages like Java and C#. I
>simply love the sheer power I have with C++ and if I shoot myself in the
>foot, so to speak, I simply learn to aim better, but I also learned, that
>for beginners it is often to difficult to learn. There are simply to many
>mistakes a beginner can make and only trough education does he learn to
>avoid those beginner mistakes. Mind you, I'm only referring to unsafe
>constructs which led to crashes. I'm not talking about supposedly complex
>features like operator overloading or multiple inheritance. If a beginner
>doesn't understand these features he probably won't use them. But because
>of the forementioned ignorant developers, the language is getting a bad
>reputation for it's "unsafety" which also influences decision makers to
>abandon C++ projects. I read of course Bjarne Stroustrup's excellent
>posting (http://www.research.att.com/~bs/blast.html) where he addresses
>some of the issues. But the questions remains:
>
>Should C++ changed to be a more beginner friendly language?
No ... isn't that why we have Java, C# and Visual Basic? ;)
One might argue that knives should be outlawed because you can cut
yourself with them.
--
Bob Hairgrove
NoSpamPlease@Home.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/7/2005 2:02:43 PM
|
|
Peter Most <Peter_Most@gmx.de> writes:
> Hello everybody,
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
>
> I'm assuming there are two kind of beginner:
> 1) A beginner who, through reading and education, will eventually become an
> advanced developer and maybe even an expert developer. For this discussion
> I simple call them "novice" developer.
> 2) A beginner who doesn't read or educate himself and simply muddles
> through. I would like to call this kind of developer "ignorant" developer.
>
> Now both kind of developers start to program and sooner or later they start
> to use arrays. Both will probably at some point start to use a vector. I'm
> also assuming that both developers have difficulties with the index and the
> designer of vector obviously also assumed this, why else would there an
> at()?
>
> The novice developer reads more about vector and learns, that there are two
> ways to access the elements. A fast but unsafe way via the operator[] and a
> version which is little slower but safer way via at(). He starts to use at()
> because it is a lot safer and he doesn't need that last bit of speed.
Safer how? It won't make an unintentional out-of-bounds access any
more correct. Incorrect code can always have unintended (i.e. unsafe)
behaviors.
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
12/7/2005 2:05:56 PM
|
|
* Peter Most:
> I read of course Bjarne Stroustrup's excellent
> posting (http://www.research.att.com/~bs/blast.html) where he addresses
> some of the issues.
The Internet Gods refuse to honor my request to see that page,
teleporting me instead to ...
Argh, on the third try, to copy the URL I get redirected to, they
finally let me through to that page, but, there is evidently something
that doesn't quite work as it should.
I meant to write,
"did you mean <url:
http://www.research.att.com/~bs/new_learning.pdf>?",
but now, well, I still think that's relevant! ;-)
PS: Regarding Bjarne's comments on the statement "C++ sucks", I think he
may have misunderstood. All those who state that "C++ sucks" simply
mean that they're drawn inexorably towards C++. That's how good it is!
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/7/2005 2:07:48 PM
|
|
Dnia 6 Dec 2005 13:17:34 -0500, Peter Most skrobie:
> Hello everybody,
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
Regarding what you mean in the "beginner" term, I think it would, but with
your proposition it wouln't either.
The problem of safety in C++ is not only in case of indexing vector out of
bounds, but also other things, like accessing objects in freed memory. I'd
rather see the solution in making appropriate high-level environment in which
the program in C++ runs so that every invalid access will end up with an
exception. Cheching bounds in [] can be added by an implementation as well, on
a direct request.
This change in Standard terms would change also existing programs, which is
not what "non-beginners" want.
> To give some background on this thoughts:
> I'm developing in C++ for about 15 years and in recent years I got the
> feeling, that C++ is loosing ground to other languages like Java and C#. I
> simply love the sheer power I have with C++ and if I shoot myself in the
> foot, so to speak, I simply learn to aim better, but I also learned, that
> for beginners it is often to difficult to learn. There are simply to many
> mistakes a beginner can make and only trough education does he learn to
> avoid those beginner mistakes. Mind you, I'm only referring to unsafe
> constructs which led to crashes. I'm not talking about supposedly complex
> features like operator overloading or multiple inheritance. If a beginner
> doesn't understand these features he probably won't use them. But because
> of the forementioned ignorant developers, the language is getting a bad
> reputation for it's "unsafety" which also influences decision makers to
> abandon C++ projects. I read of course Bjarne Stroustrup's excellent
> posting (http://www.research.att.com/~bs/blast.html) where he addresses
> some of the issues. But the questions remains:
> Should C++ changed to be a more beginner friendly language?
This thought is still worth taking into consideration. But still the only way
that leads to a safe usage of C++ is learning. So if a developer is assigned
to a work in a production environment, there must be someone to look at this
code and detect such problematic usages. I heard, for example, that inability
to overload operators in Java is its advantage. Why? Because it prevents
immature developers from doing stupid things. However a language, which is
suitable for beginners is rather not suitable for professionals. C++ can be
made safer, but only by cost of removing features, which professionals would
require in this language. That's why it depends, which developers the project
needs, which of them can be allocated for a project.
When I started learning C++ a couple years ago, I just accepted it as it is
and I never had problems with its safety. Of course, I did mistakes many times
and there was also a lot of problems very hard to detect, but I never thought
that hardening it is a way the C++ should walk. For "ignorant" developers
maybe Java would be more suitable.
For a complicated language (that is, a language that provides a lot of
capabilities and programming paradigms) there is only one way to use it
correctly: learning. And for "ignorant" developers there's no hope. Hardening
C++ is not a help for that.
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethourhs(O)wp.pl>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"I am allergic to Java because programming in Java reminds me casting spells"
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sektor
|
12/7/2005 2:10:56 PM
|
|
Peter Most wrote:
> Hello everybody,
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for
> beginner?
>
> I'm assuming there are two kind of beginner:
> 1) A beginner who, through reading and education, will eventually
> become an advanced developer and maybe even an expert developer. For
> this discussion I simple call them "novice" developer.
> 2) A beginner who doesn't read or educate himself and simply muddles
> through. I would like to call this kind of developer "ignorant"
> developer.
>
> Now both kind of developers start to program and sooner or later they
> start to use arrays. Both will probably at some point start to use a
> vector. I'm also assuming that both developers have difficulties with
> the index and the designer of vector obviously also assumed this, why
> else would there an at()?
I'll leave that one for someone who knows something about the design
rationale of the STL. :-)
>
> The novice developer reads more about vector and learns, that there
> are two ways to access the elements. A fast but unsafe way via the
> operator[] and a version which is little slower but safer way via
> at(). He starts to use at() because it is a lot safer and he doesn't
> need that last bit of speed.
No. The novice developer is unlikely to pick up on existance of at(),
but will be more careful in use of operator[]. This isn't a fault of
the novice: it's because most examples (at least those that don't
involve using iterators) in introductory texts use operator[]
>
> The ignorant developer also starts to use vector but doesn't learn
> that there a two interface, so he still uses operator[] and still has
> problems and bugs with the index access.
In my experience, vector is a step up for the ignorant developer. They
are more likely to do this (for an array of ints);
int *array;
array[some_index] = 42;
The only way you could prevent this would be to remove pointers from
the language.
>
> So my question is now, would it not have been better to reverse the
> semantic of the two versions, meaning operator[] checks the index and
> throws an exception and at() would allow an unchecked access? I'm
> assuming of course that either beginner starts to use the vector via
> the operator[]. If this assumption would be correct, then a beginner
> would start with the safe version and maybe, if he needs that
> additional speed, later use the at() version.
>
> This is of course easiest done in libraries, because then the language
> doesn't have to be changed. But I wonder whether it would make sense
> also in a new language design. It wouldn't be C++ anymore, because it
> would break backwards compatibility, but maybe a worthy C++ successor?
What you are describing is the same sort of thinking that resulted in
Java: they removed features from the language they din't like, and
added others that they claimed were safer.
The problem with that is that such a language is offputting to someone
with any experience. With a disciplined approach, the benefits of a
"safe" language or library are relatively limited. And can actually be
limiting, by eliminating some powerful techniques.
>
> To give some background on this thoughts:
>
> I'm developing in C++ for about 15 years and in recent years I got the
> feeling, that C++ is loosing ground to other languages like Java and
> C#. I simply love the sheer power I have with C++ and if I shoot
> myself in the foot, so to speak, I simply learn to aim better, but I
> also learned, that for beginners it is often to difficult to learn.
C++ is losing ground to languages like Java and C# in problems areas
that are better suited to using Java or C#. That's the way it should
be. If you are doing a job that Java is designed to do well, you are
better off using Java than C++. But if you are doing a job that is
outside the realm of what Java does well, you will be better off
choosing another language (and C++ may well be the better choice).
> There are simply to many mistakes a beginner can make and only trough
> education does he learn to avoid those beginner mistakes. Mind you,
> I'm only referring to unsafe constructs which led to crashes. I'm not
> talking about supposedly complex features like operator overloading
> or multiple inheritance. If a beginner doesn't understand these
> features he probably won't use them. But because of the forementioned
> ignorant developers, the language is getting a bad reputation for
> it's "unsafety" which also influences decision makers to abandon C++
> projects. I read of course Bjarne Stroustrup's excellent posting
> (http://www.research.att.com/~bs/blast.html) where he addresses some
> of the issues. But the questions remains:
>
> Should C++ changed to be a more beginner friendly language?
>
My experience with decision makers (at least the rational ones) who
abandon C++ in favour of a language like Java is that they don't do it
because of safety. They do it because they find they are doing things
that are better suited to Java. One company I know of uses Java for
initial rapid prototyping (eg convince potential users that particular
features are worthwhile) but then hand the specification over to
another team that uses C++ to implement the "production version" in
which things like runtime performance are more critical.
There is some element of Java advocacy in a lot of new recruits (in
part because universities have gotten onto the bandwagon of teaching
Java). That is actually a self-licking icecream: it is easier to find
developers who know Java, but it is just as difficult to find a *good*
developer who knows Java as it is to find a good developer in C++. So
going back to Java simply because it is "safer" has a side-effect of
encouraging mediocre development standards because it is easier to find
programmers who can program with that safety net.
That's not a fault of Java (or any other language), per se. It is a
problem with the expectations that some managers have because they are
using a "safe" language --- they lower the standards when selecting
developers. The reality is that a good developer will be a good
developer regardless of programming language (as long as they are given
time and mentoring to learn a new language effectively)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Rob
|
12/7/2005 2:23:13 PM
|
|
"Peter Most" <Peter_Most@gmx.de> wrote in message
news:4395bb80$0$27884$9b4e6d93@newsread4.arcor-online.net...
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
C++ has been around for over 20 years now, an eternity in the software
business. I've been working on C++ compilers since 1986 or so. It certainly
makes sense every once in a while to take a step back, and see if the
feature set is the best fit for current thinking and coding practice. For
example, C++'s notion of strings is based on past ideas about character sets
and is out of step with current UTF technology.
I, however, am less concerned with making C++ a safe language for beginners
than looking at it from the standpoint of, for professional programmers:
1) improving programmer productivity
2) improving program reliability
3) improving program portability
4) improving program performance
5) improving documentation
6) improving managability
7) reducing the effort necessary to master it
8) making internationalization of apps much easier
So what might such a reengineering look like? One example is the D
programming language, from www.digitalmars.com/d/
-Walter Bright, www.digitalmars.com
Digital Mars - C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/7/2005 2:24:40 PM
|
|
Peter Most wrote:
> I would like to get some opinions about whether it would make
> sense to change the C++ design principles, so it would become
> safer for beginner?
Making the language safer is in general a good idea, even for
non-beginners. The question is rather how, and at what price.
(Note that some languages which claim "safety" are actually less
safe than C++, at least in the hands of someone who knows what
he is doing.)
> I'm assuming there are two kind of beginner:
> 1) A beginner who, through reading and education, will
> eventually become an advanced developer and maybe even an
> expert developer. For this discussion I simple call them
> "novice" developer.
> 2) A beginner who doesn't read or educate himself and simply
> muddles through. I would like to call this kind of developer
> "ignorant" developer.
I'd hesitate to call that one a developer.
> Now both kind of developers start to program and sooner or
> later they start to use arrays. Both will probably at some
> point start to use a vector. I'm also assuming that both
> developers have difficulties with the index and the designer
> of vector obviously also assumed this, why else would there an
> at()?
Why there is a function at() is a good question? There are
probably specific cases where it is reasonable to wait for the
vector itself to verify the validity of an index, and to report
the error with an exception, but the are certainly rare.
> The novice developer reads more about vector and learns, that
> there are two ways to access the elements. A fast but unsafe
> way via the operator[] and a version which is little slower
> but safer way via at(). He starts to use at() because it is a
> lot safer and he doesn't need that last bit of speed.
What makes you say that there is any difference in speed. I
would expect that in the default mode, operator[] and at() have
about the same performance. The only difference is that at()
raises an exception, so represents an error which is, in some
way, expected (or expectable), albeit exceptional, where as
operator[] will provoke an immediate core dump -- an assertion
failure.
Like other assertions, of course, this can be turned of in
production code, if the profiler shows it necessary.
Regretfully, I fear that in most library implementations,
turning off checking is an all or nothing proposition, and some
of the other checks are expensive enough that one will have to
turn them off in production code. This is a weakness of the
current implementations, however, and not of the standard.
> The ignorant developer also starts to use vector but doesn't
> learn that there a two interface, so he still uses operator[]
> and still has problems and bugs with the index access.
A developer who has problems with indexes has problems.
Regardless of what the library does. Using operator[], and
crashing immediately, is probably the best solution. But note
that if he has problems with indexes, it won't always be
apparent -- accessing the index 2 when the data needed is at
index 1 will not cause an immediate error if the vector has a
size of 10, but will result in something worse than a program
crash -- a silently incorrect result.
> So my question is now, would it not have been better to
> reverse the semantic of the two versions, meaning operator[]
> checks the index and throws an exception and at() would allow
> an unchecked access?
That sounds like a good way of increasing the danger.
The C++ standard has no concept of "must crash" (except in the
case of the library functions assert() and abort()). Maybe this
is what is needed. And a requirement that operator[] act as if
an assertion failure had occured in the case of an illegal
index. (If this is done, of course, one should add an unsafe_at
function which could be used in cases where the profiler
indicates that bounds checking is a bottle neck.)
[...]
> To give some background on this thoughts:
> I'm developing in C++ for about 15 years and in recent years I
> got the feeling, that C++ is loosing ground to other languages
> like Java and C#.
For some things. The reasons have nothing to do with the
relative safety of the languages, however, but with the
available infrastructure -- it's possible to write server side
dynamic web pages in C++, but the available infrastructures make
it far easier in Java.
It's interesting to note that Java hasn't replaced C++ in
critical applications, because it isn't safe enough. In the
hands of an organization which knows what it is doing, C++ is in
fact far safer than Java.
> I simply love the sheer power I have with C++ and if I shoot
> myself in the foot, so to speak, I simply learn to aim better,
> but I also learned, that for beginners it is often to
> difficult to learn. There are simply to many mistakes a
> beginner can make and only trough education does he learn to
> avoid those beginner mistakes. Mind you, I'm only referring to
> unsafe constructs which led to crashes. I'm not talking about
> supposedly complex features like operator overloading or
> multiple inheritance. If a beginner doesn't understand these
> features he probably won't use them. But because of the
> forementioned ignorant developers, the language is getting a
> bad reputation for it's "unsafety" which also influences
> decision makers to abandon C++ projects. I read of course
> Bjarne Stroustrup's excellent posting
> (http://www.research.att.com/~bs/blast.html) where he
> addresses some of the issues. But the questions remains:
> Should C++ changed to be a more beginner friendly language?
I think that there are some things that do need improvement.
Not just for beginners -- I've over fifteen years experience
with the language, and I still occasionally end up declaring a
function when I mean to define a variable. I'm not convinced
that safety, per se, is the problem, however.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/7/2005 2:29:22 PM
|
|
Peter Most wrote:
> Hello everybody,
Hello Peter!
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
>
> [snipped]
>
> The ignorant developer also starts to use vector but doesn't learn that
> there a two interface, so he still uses operator[] and still has problems
> and bugs with the index access.
Actually, I don't see how this happens. If you are accessing an element
out of range both operator[] and at() will fail. The only difference is
that operator[] almost always crash the program immediately, whereas the
at() will throw an exception. Now to handle the exception gracefully is
a much more challenging task than knowing at() and operator[] in itself.
It is very unlikely that the Ignorant will care to handle the exception
properly. Therefore the program crashes all the same.
>
> So my question is now, would it not have been better to reverse the semantic
> of the two versions, meaning operator[] checks the index and throws an
> exception and at() would allow an unchecked access? I'm assuming of course
> that either beginner starts to use the vector via the operator[]. If this
> assumption would be correct, then a beginner would start with the safe
> version and maybe, if he needs that additional speed, later use the at()
> version.
I am not too sure about this. Perhaps the designer decides to make the
overhead more "visible" to the user. Viz. most people feel just from the
accessing code that at() is potentially a more costly operation because
it looks like so (consider ++a and increment(a).)
Furthermore, it is not impossible for the C++ system to have a safe
operator[] optionally for debug builds.
>
> This is of course easiest done in libraries, because then the language
> doesn't have to be changed. But I wonder whether it would make sense also
> in a new language design. It wouldn't be C++ anymore, because it would break
> backwards compatibility, but maybe a worthy C++ successor?
In fact, if you want to make this little change to the core language
then possible many people will want their changes, too, in the core
language. You will have an inexhaustible list of demands and that alone
will make the language several times bigger and more complex than what
C++ already is.
The reason why C++ is so complex (as most people think) is, in my
opinion, that C++ tries to address to a wide range of audience. So
within the core language which everyone needs to make an agreement upon
the only viable version of accessing an array is, well, the unsafe built
in array.
Others who thinks the built in array facility is a bad idea can
implement their own version the suit their needs. This is the beauty of
C++ you rarely see in other languages. After all, these better versions
mostly will make use of the "unwanted" built in array facility or other
implementations which uses the built in array facility. This justifies
why the built in array is there.
>
> To give some background on this thoughts:
>
> I'm developing in C++ for about 15 years and in recent years I got the
> feeling, that C++ is loosing ground to other languages like Java and C#. I
> simply love the sheer power I have with C++ and if I shoot myself in the
> foot, so to speak, I simply learn to aim better, but I also learned, that
> for beginners it is often to difficult to learn. There are simply to many
> mistakes a beginner can make and only trough education does he learn to
> avoid those beginner mistakes. Mind you, I'm only referring to unsafe
> constructs which led to crashes. I'm not talking about supposedly complex
> features like operator overloading or multiple inheritance. If a beginner
> doesn't understand these features he probably won't use them. But because
> of the forementioned ignorant developers, the language is getting a bad
> reputation for it's "unsafety" which also influences decision makers to
> abandon C++ projects. I read of course Bjarne Stroustrup's excellent
> posting (http://www.research.att.com/~bs/blast.html) where he addresses
> some of the issues. But the questions remains:
Here are some points to make:
1. You simply can't expect the Igorants to become a good programer. Not
in C++, nor in any other languages (such as Java)
2. Every language have their own set of unsafe constructs. C++ does, do
do Java, C#, Ada, etc. For example, the array in Java is not 100% type
safe. These unsafe constructs are inevitable because they are essential,
or compromises to balance other potential unsafeties. The bottom line
is, unsafe construct must look obvious. Honestly, there are many
not-so-obvious dangers in C++ which you and me and many other users will
have to face, but compare to the real world programming, the lauguage
itself is usually a small chapter of a long story.
3. C++ was never an educational language, IMO, and it will be unlikely
to be so in the near future. I am under the impression that an
educational language will stay an educational language (Pascal); a RAD
language will stay a RAD language (Visual Basic.)
4. The ugliest part of C++ IMO is things like declaring a const pointer,
a pointer to const object, a const pointer to a function taking a const
reference to a const object and returns a reference to a pointer to a
const object, etc. Today, shame to say, I still don't master this area
in the language. Fortunately, I can typedef all these in templates,
which is very useful.
5. Sometimes an immediate crash is not a bad thing. This means the
problem is determinible and can mostly caught by a debugger. Exceptions
too can crash the program if not handled properly. The point is, never
let the program run with a problem unnoticed.
>
> Should C++ changed to be a more beginner friendly language?
Yes. But probably not in a way you have described.
>
> I'm very interested in hearing your opinions.
>
> Kind regards
>
> Peter
>
Regards,
Ben
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
benben
|
12/7/2005 2:30:06 PM
|
|
Peter Most wrote:
> Hello everybody,
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
>
No. C++ is good in it's area. You can easilly write real word
applications
that will perform as expected. No run time surprises there. You get
what you write.
>
> Should C++ changed to be a more beginner friendly language?
If that means complex compilers then answer is no, for sure.
Those friendly languages get ugly when tryng to do some
havy duty stuff with reasonable performance and resource usage
.. In that case they are not beginner friendly as all unsafe features
are there multiplied by lack of support for them in "safe"
environment. Also begginer friendly languages usually limit the number
of ways in which program architecture can be implemented.
So C++ will never be some easy language but many things can
be done more quickly and more easilly then many others once
you grasp it.
It's just a pragmatic thing. Whenever is possible to use some
other language I always prefer other, but in many cases
it's just not applicable or I'm not satisfied with performance
or behavior of other language and that I can't usually change
without waiting for patch or next version of language or.....
Greetings, Bane.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Branimir
|
12/7/2005 2:33:23 PM
|
|
It is more a question of writing a tutorial that introduces as few features
as possible and still allows the novice to do things. I would not advice to
change the language; it is pretty good like that and stability is such a
virtue.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
John
|
12/7/2005 2:59:11 PM
|
|
David Abrahams wrote:
> Peter Most <Peter_Most@gmx.de> writes:
>
>> Hello everybody,
>>
>> I would like to get some opinions about whether it would make sense to
>> change the C++ design principles, so it would become safer for beginner?
>>
[claim that at() is safer than operator[] in std::vector]
>>
> Safer how? It won't make an unintentional out-of-bounds access any
> more correct.
Safer in that it avoids undefined behavior, which can (and for some classes
of mistakes -- like out of bounds access or dereferencing a deleted object
-- often does) result in behavior that covers up the bug.
> Incorrect code can always have unintended (i.e. unsafe) behaviors.
Still, there is nothing wrong with thinking about how to reduce the
likelihood.
Best
Kai-Uwe Bux
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Kai
|
12/7/2005 3:59:43 PM
|
|
Peter Most wrote:
> Hello everybody,
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
>
[snipped: suggestion to swap at() and operator[] in std::vector]
>
I do not think this is a good idea. I would rather have operator[] do
something like:
reference operator[] ( size_type pos ) {
assert( pos < this->size() );
return ...;
}
so that if DEBUG is defined I will have bug detection, and I do not have a
performance penalty in production code.
It might be even better to have a second incarnation of assert for like
std_assert that is used by the standard library to enforce contracts and
that kicks in whenever _STD_ENFOCRE_CONTRACTS is defined. The idea would be
to turn as much undefined behavior into defined runtime errors as feasible.
Best
Kai-Uwe Bux
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Kai
|
12/7/2005 4:00:04 PM
|
|
David Abrahams wrote:
> Peter Most <Peter_Most@gmx.de> writes:
>
>> Hello everybody,
>>
>> I would like to get some opinions about whether it would make sense to
>> change the C++ design principles, so it would become safer for beginner?
>>
>> I'm assuming there are two kind of beginner:
>> 1) A beginner who, through reading and education, will eventually become
>> an advanced developer and maybe even an expert developer. For this
>> discussion I simple call them "novice" developer.
>> 2) A beginner who doesn't read or educate himself and simply muddles
>> through. I would like to call this kind of developer "ignorant"
>> developer.
>>
>> Now both kind of developers start to program and sooner or later they
>> start to use arrays. Both will probably at some point start to use a
>> vector. I'm also assuming that both developers have difficulties with the
>> index and the designer of vector obviously also assumed this, why else
>> would there an at()?
>>
>> The novice developer reads more about vector and learns, that there are
>> two ways to access the elements. A fast but unsafe way via the operator[]
>> and a version which is little slower but safer way via at(). He starts to
>> use at() because it is a lot safer and he doesn't need that last bit of
>> speed.
>
> Safer how? It won't make an unintentional out-of-bounds access any
> more correct. Incorrect code can always have unintended (i.e. unsafe)
> behaviors.
>
Safer in the way that it is not simply crashing, but throws an exception
which hopefully will be caught somewhere. At least that way you can add a
toplevel exception handler and know that this error won't crash your
program.
> --
> Dave Abrahams
> Boost Consulting
> www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/7/2005 4:03:48 PM
|
|
Sektor van Skijlen wrote:
> Dnia 6 Dec 2005 13:17:34 -0500, Peter Most skrobie:
[snip]
> Regarding what you mean in the "beginner" term, I think it would, but with
> your proposition it wouln't either.
>
> The problem of safety in C++ is not only in case of indexing vector out of
> bounds, but also other things, like accessing objects in freed memory. I'd
Well I have to start somewhere ;-)
> rather see the solution in making appropriate high-level environment in
> which the program in C++ runs so that every invalid access will end up
> with an exception. Cheching bounds in [] can be added by an implementation
> as well, on a direct request.
>
> This change in Standard terms would change also existing programs, which
> is not what "non-beginners" want.
>
But would it change existing programs for the worse?
[snip]
> This thought is still worth taking into consideration. But still the only
> way that leads to a safe usage of C++ is learning. So if a developer is
> assigned to a work in a production environment, there must be someone to
> look at this code and detect such problematic usages. I heard, for
> example, that inability to overload operators in Java is its advantage.
> Why? Because it prevents immature developers from doing stupid things.
I tried to find examples of such "stupid things" and couldn't find a good
example. The best I could find was some example where a developer overloads
the '+=' for a container but actually removes an element (or something like
that). The argument goes on to explain that with methods this doesn't
happen, completely ignoring that a developer could write an 'add' method
which also removes an element or formats your harddrive.
So if an immature developer would overload the '+=' operator in such a way,
who can say if he wouldn't write the 'add' in a similar way?
> However a language, which is suitable for beginners is rather not suitable
> for professionals. C++ can be made safer, but only by cost of removing
> features, which professionals would require in this language. That's why
> it depends, which developers the project needs, which of them can be
> allocated for a project.
>
The idea was to provide 2 sets of features, like the vector example. One
safe (default) feature for the beginner i.e. operator[] with range check
and a second set i.e. at() without range check.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/7/2005 6:53:36 PM
|
|
Rob wrote:
> Peter Most wrote:
>
[snip]
>> The novice developer reads more about vector and learns, that there
>> are two ways to access the elements. A fast but unsafe way via the
>> operator[] and a version which is little slower but safer way via
>> at(). He starts to use at() because it is a lot safer and he doesn't
>> need that last bit of speed.
>
> No. The novice developer is unlikely to pick up on existance of at(),
> but will be more careful in use of operator[]. This isn't a fault of
> the novice: it's because most examples (at least those that don't
> involve using iterators) in introductory texts use operator[]
>
That's the point I'm trying to make. If operator[] would check the index
then the novice developer would start to use the safe version.
> What you are describing is the same sort of thinking that resulted in
> Java: they removed features from the language they din't like, and
> added others that they claimed were safer.
>
> The problem with that is that such a language is offputting to someone
> with any experience. With a disciplined approach, the benefits of a
> "safe" language or library are relatively limited. And can actually be
> limiting, by eliminating some powerful techniques.
>
A little further down my post I explain, that I don't want to remove
anything from the language ;-) but rather "change" or "tweak" it a little.
Although one could argue, that it is time to remove unsafe functions like
strcat(), strcpy() etc.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/7/2005 6:55:22 PM
|
|
"Peter Most" <Peter_Most@gmx.de> wrote in message
news:4395bb80$0$27884$9b4e6d93@newsread4.arcor-online.net...
> Hello everybody,
>
> I would like to get some opinions about whether it would make sense to
> change the C++ design principles, so it would become safer for beginner?
>
[]
>
> Should C++ changed to be a more beginner friendly language?
>
> I'm very interested in hearing your opinions.
>
> Kind regards
>
> Peter
When I was in school, I was very impressed by what was called the Fortran
Checkout Compiler. It was used by students on a mainframe, and there was
nothing that they could do to break the system, or affect other students on
the mainframe. It ran quite slow, with all of its checking, but there was
no need to change the Fortran programs for this extra safety. These same
programs, once they were fully debugged and tested, could be recompiled, if
desired, on a "normal" Fortran compiler to run full speed.
C++ Builder by Borland has a similer feature called CodeGuard (which I
am going to miss, because we are leaving Builder for Visual Studio), which
operates in much the same way as the Fortran Checkout Compiler. There is
another tool I hope to become acquainted with called BoundsChecker. These
tools add much checking to programs, which slow them down considerably, but
make them safe. The big plus for me, is it is a make option to turn it on
or off. This is the way I would recommend beginners use C++, in addition to
maximizing the warning level.
Another thought that has occurred to me is that if the above tools work
well enough, they could make C++ safe enough to be run in a browser. There
is a difference, however, between program correctness and good program
behavior. Still, I wouldn't mind at all seeing a tool to make it possible
that a browser would be willing to run my C++ application, provided it was
identified to be compiled in "good behavior mode".
my 0.02,
Robert Kindred
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Robert
|
12/7/2005 7:10:20 PM
|
|
"Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
message news:dn4o7b$s62$1@kujawiak.man.lodz.pl...
> For a complicated language (that is, a language that provides a lot of
> capabilities and programming paradigms) there is only one way to use it
> correctly: learning. And for "ignorant" developers there's no hope.
Hardening
> C++ is not a help for that.
I must say I disagree with this somewhat fatalistic viewpoint. Complexity
doesn't necessarilly come from power. It often comes from:
1) inconsistency
2) a poor match of language design with the paradigm used
3) attempts to retain backwards compatibility with obsolete features
4) poor understanding of what the problem is
5) adherance to ideas that are conventionally assumed to be true, but are
not
6) features that are inconsistent with intuition
Anyone can design something complicated. Genius is in discovering the
underlying simplicity and designing to that. A good test of genius in design
is when, after it is introduced, everyone else slaps their head and thinks
"of course that's the way it should be, it's obvious."
For example, consider the evolution of the design of guns. They've gotten
far more capable, reliable, flexible, etc., but are much simpler and safer
to use. The revolutionary improvements made are so obvious to us now, we
wonder why nobody thought of it before the genius that did, and we find it
difficult to imagine making a gun any other way.
Back to C++, and for an example of an inconsistency, consider this example
from C++ 98 13.4-5:
-------------------
struct X {
int f(int);
};
int (X::*p1)(int) = &X::f; // OK
int (X::*p5)(int) = &(X::f); // error: wrong syntax for pointer to member
-----------------------------
This is the only place in C++ where parenthesizing an expression changes its
semantic meaning other than operator precedence. It's inconsistent, it's
buried in the spec, and it requires a special kludge in the compiler
implementation to make it work. I've never found an explanation for it.
Sure, this is very obscure, and even the C++ experts don't know it's there.
But there are some that do trip people up:
1) the well-known template > angle bracket tokenizing problems
2) inconsistent behavior between std::string's and quoted string literals
3) inconsistent behavior between core arrays, and std::vector
4) inconsistent and counterintuitive name lookup rules for dependent and
non-dependent names
5) two level name lookup
Are these inconsistencies necessary to get the power? I don't believe so.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/7/2005 11:25:35 PM
|
|
> Another thought that has occurred to me is that if the above tools work
> well enough, they could make C++ safe enough to be run in a browser. There
> is a difference, however, between program correctness and good program
> behavior. Still, I wouldn't mind at all seeing a tool to make it possible
> that a browser would be willing to run my C++ application, provided it was
> identified to be compiled in "good behavior mode".
Don't you think C++ is a bit too powerful for browsers? Letting the C++
program to use pointers means it can access anything within its memory
space, which may include the browser itself if the code is dynamically
linked.
Ben
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
benben
|
12/8/2005 11:51:34 AM
|
|
kanze wrote:
>> The novice developer reads more about vector and learns, that
>> there are two ways to access the elements. A fast but unsafe
>> way via the operator[] and a version which is little slower
>> but safer way via at(). He starts to use at() because it is a
>> lot safer and he doesn't need that last bit of speed.
>
> What makes you say that there is any difference in speed. I
> would expect that in the default mode, operator[] and at() have
> about the same performance. The only difference is that at()
> raises an exception, so represents an error which is, in some
> way, expected (or expectable), albeit exceptional, where as
> operator[] will provoke an immediate core dump -- an assertion
> failure.
>
It's probably very small if it can be measured at all, but the range check
in at() isn't free. But the question remains, why are there two versions to
access the elements?
>> The ignorant developer also starts to use vector but doesn't
>> learn that there a two interface, so he still uses operator[]
>> and still has problems and bugs with the index access.
>
> A developer who has problems with indexes has problems.
> Regardless of what the library does. Using operator[], and
> crashing immediately, is probably the best solution. But note
> that if he has problems with indexes, it won't always be
> apparent -- accessing the index 2 when the data needed is at
> index 1 will not cause an immediate error if the vector has a
> size of 10, but will result in something worse than a program
> crash -- a silently incorrect result.
>
I have to disagree. Crashing is never a good solution. If the program stops
because the exception wasn't handled, then it's probably OK.
>> So my question is now, would it not have been better to
>> reverse the semantic of the two versions, meaning operator[]
>> checks the index and throws an exception and at() would allow
>> an unchecked access?
>
> That sounds like a good way of increasing the danger.
>
What danger would it increase?
> The C++ standard has no concept of "must crash" (except in the
> case of the library functions assert() and abort()). Maybe this
If an exception isn't handled, then basically it "crashes" but in a less
dramatic way.
> For some things. The reasons have nothing to do with the
> relative safety of the languages, however, but with the
> available infrastructure -- it's possible to write server side
> dynamic web pages in C++, but the available infrastructures make
> it far easier in Java.
>
That's probably another reason why I have the feeling C++ is loosing ground,
because of the lack of standard libraries. The Boost library is making
excellent progress to remedy this, but the huge companies behind Java and
C# make it very hard to catch up.
> It's interesting to note that Java hasn't replaced C++ in
> critical applications, because it isn't safe enough. In the
> hands of an organization which knows what it is doing, C++ is in
> fact far safer than Java.
>
But that's exactly my point, that those developers who don't know what they
are doing, are giving C++ a bad reputation.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/8/2005 11:52:43 AM
|
|
Robert Kindred wrote:
> Another thought that has occurred to me is that if the
> above tools work well enough, they could make C++ safe
> enough to be run in a browser.
I run C++ all the time in my browser. The browser itself
(Firefox on my Linux box, Netscape under Solaris) is written in
C++, as are, I imagine, a number of plug-ins.
> There is a difference, however, between program correctness
> and good program behavior.
A good program can still be a bad neighbor. Some of the viruses
are very well written programs, and "correct" by all classical
measures. When running unknown code, you run it as a user with
no rights, and the OS protects you (or should).
> Still, I wouldn't mind at all seeing a tool to make it
> possible that a browser would be willing to run my C++
> application, provided it was identified to be compiled in
> "good behavior mode".
Part of the problem is that C++ is designed to be statically
compiled. It should be possible to define a portable "byte
code" with an interpreter for it, but to my knowledge, no one
has done so. It would be very difficult for a browser to run
code compled for Windows on a PC on my Sparc under Solaris. THe
current state of affairs is that to run C++ directly, my browser
would have to download the sources, and compile and link them.
So my browsers limit themselves to running C++ which has already
been compiled for my platform.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:16:23 PM
|
|
David Abrahams wrote:
> Peter Most <Peter_Most@gmx.de> writes:
> > I would like to get some opinions about whether it would
> > make sense to change the C++ design principles, so it would
> > become safer for beginner?
> > I'm assuming there are two kind of beginner: 1) A beginner
> > who, through reading and education, will eventually become
> > an advanced developer and maybe even an expert developer.
> > For this discussion I simple call them "novice" developer.
> > 2) A beginner who doesn't read or educate himself and simply
> > muddles through. I would like to call this kind of developer
> > "ignorant" developer.
> > Now both kind of developers start to program and sooner or
> > later they start to use arrays. Both will probably at some
> > point start to use a vector. I'm also assuming that both
> > developers have difficulties with the index and the designer
> > of vector obviously also assumed this, why else would there
> > an at()?
> > The novice developer reads more about vector and learns,
> > that there are two ways to access the elements. A fast but
> > unsafe way via the operator[] and a version which is little
> > slower but safer way via at(). He starts to use at() because
> > it is a lot safer and he doesn't need that last bit of
> > speed.
> Safer how? It won't make an unintentional out-of-bounds
> access any more correct. Incorrect code can always have
> unintended (i.e. unsafe) behaviors.
Well, if an out of bounds access in operator[] were required to
abort, it would be safer in the sense that bad programs won't
run as wrong, and no one will accidentally interpret bad results
as good. (I know that wasn't what he was proposing, but it's
the only "improvement" which makes sense.)
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:20:45 PM
|
|
Bob Hairgrove wrote:
> On 6 Dec 2005 13:17:34 -0500, Peter Most <Peter_Most@gmx.de>
> wrote:
[...]
> >Should C++ changed to be a more beginner friendly language?
> No ... isn't that why we have Java, C# and Visual Basic? ;)
I don't know about C# or Visual Basic, but I certainly wouldn't
consider Java more beginner friendly than C++. Except, perhaps,
in the sense that its syntax has less "traps". (But that's a
good thing even for experts.) It's far harder to write a robust
and correct application in Java than it is in C++.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:21:07 PM
|
|
Peter Most wrote:
> David Abrahams wrote:
> > Peter Most <Peter_Most@gmx.de> writes:
> >> I would like to get some opinions about whether it would
> >> make sense to change the C++ design principles, so it would
> >> become safer for beginner?
> >> I'm assuming there are two kind of beginner: 1) A beginner
> >> who, through reading and education, will eventually become
> >> an advanced developer and maybe even an expert developer.
> >> For this discussion I simple call them "novice" developer.
> >> 2) A beginner who doesn't read or educate himself and
> >> simply muddles through. I would like to call this kind of
> >> developer "ignorant" developer.
> >> Now both kind of developers start to program and sooner or
> >> later they start to use arrays. Both will probably at some
> >> point start to use a vector. I'm also assuming that both
> >> developers have difficulties with the index and the
> >> designer of vector obviously also assumed this, why else
> >> would there an at()?
> >> The novice developer reads more about vector and learns,
> >> that there are two ways to access the elements. A fast but
> >> unsafe way via the operator[] and a version which is little
> >> slower but safer way via at(). He starts to use at()
> >> because it is a lot safer and he doesn't need that last bit
> >> of speed.
> > Safer how? It won't make an unintentional out-of-bounds
> > access any more correct. Incorrect code can always have
> > unintended (i.e. unsafe) behaviors.
> Safer in the way that it is not simply crashing, but throws an
> exception which hopefully will be caught somewhere. At least
> that way you can add a toplevel exception handler and know
> that this error won't crash your program.
But that makes it more dangerous. The only "good" thing that
wrong code can do is crash, so that you know it's wrong. And
the sooner it crashes, the better. The unsafe behavior of
operator[] is that it might not crash. That you might not even
notice the error, and the program will simply give bad results.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:21:28 PM
|
|
Peter Most wrote:
> Rob wrote:
> > Peter Most wrote:
> [snip]
> >> The novice developer reads more about vector and learns,
> >> that there are two ways to access the elements. A fast but
> >> unsafe way via the operator[] and a version which is little
> >> slower but safer way via at(). He starts to use at()
> >> because it is a lot safer and he doesn't need that last bit
> >> of speed.
> > No. The novice developer is unlikely to pick up on
> > existance of at(), but will be more careful in use of
> > operator[]. This isn't a fault of the novice: it's
> > because most examples (at least those that don't involve
> > using iterators) in introductory texts use operator[]
> That's the point I'm trying to make. If operator[] would check
> the index then the novice developer would start to use the
> safe version.
You don't seem to be too familiar with C++ to begin with.
Calling operator[] with an out of bounds index is undefined
behavior precisely so that an implementation *can* check the
index, and do whatever is appropriate *on* *that* *platform* for
a programming error. On Unix, I expect a core dump, but from
what I understand, this is not the usual case under Windows.
Most serious implementations of the standard library today come
with debugging versions, which check not only the index, but a
lot of other things. I would expect that to be the version a
beginner is using -- there is probably some work to be done
concerning compiler options and defaults, for this to be the
case, but compiler options and defaults are not a subject of
standardization.
> > What you are describing is the same sort of thinking that
> > resulted in Java: they removed features from the language
> > they din't like, and added others that they claimed were
> > safer.
I view the situation a little different. Java's designers
decided that one specific level of safety is appropriate for
all. It's implemented in the language, and you can't change it.
C++ leaves a lot more up to the implementation and the
programmer. It can be as dangerous OR as safe as you want.
IMHO, the built in level of safety in Java is too low for
serious applications, and since you cannot increase it, Java as
largely unusable for large classes of applications, where
reliability is a must. And while I sometimes wish that the
default level of safety of C++ was a little higher, it's not
that difficult to make it as safe as is needed, and I have no
qualms about using it in critical software.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:21:50 PM
|
|
Peter Most wrote:
> kanze wrote:
> >> The novice developer reads more about vector and learns,
> >> that there are two ways to access the elements. A fast but
> >> unsafe way via the operator[] and a version which is little
> >> slower but safer way via at(). He starts to use at()
> >> because it is a lot safer and he doesn't need that last bit
> >> of speed.
> > What makes you say that there is any difference in speed. I
> > would expect that in the default mode, operator[] and at()
> > have about the same performance. The only difference is
> > that at() raises an exception, so represents an error which
> > is, in some way, expected (or expectable), albeit
> > exceptional, where as operator[] will provoke an immediate
> > core dump -- an assertion failure.
> It's probably very small if it can be measured at all, but the
> range check in at() isn't free. But the question remains, why
> are there two versions to access the elements?
Because there are cases when accessing with an index out of
bounds isn't an error, but simply an exceptional condition from
which you want to recover. Such cases aren't all that frequent,
but when they occur, at() is there. (One could argue that such
cases are rare enough not to require support in the standard
library -- it is, after all, easy enough to write the indexing
with an if.)
> >> The ignorant developer also starts to use vector but
> >> doesn't learn that there a two interface, so he still uses
> >> operator[] and still has problems and bugs with the index
> >> access.
> > A developer who has problems with indexes has problems.
> > Regardless of what the library does. Using operator[], and
> > crashing immediately, is probably the best solution. But
> > note that if he has problems with indexes, it won't always
> > be apparent -- accessing the index 2 when the data needed is
> > at index 1 will not cause an immediate error if the vector
> > has a size of 10, but will result in something worse than a
> > program crash -- a silently incorrect result.
> I have to disagree. Crashing is never a good solution. If the
> program stops because the exception wasn't handled, then it's
> probably OK.
And if the program doesn't stop? Crashing is the accepted way
of handling programming errors. It's required in critical
systems, but it is also generally a good idea in most
non-critical systems, at least those which handle real data, and
whose results are used. Masking errors may give the user a
warm, fuzzy fealing, but it also gives him wrong results.
> >> So my question is now, would it not have been better to
> >> reverse the semantic of the two versions, meaning
> >> operator[] checks the index and throws an exception and
> >> at() would allow an unchecked access?
> > That sounds like a good way of increasing the danger.
> What danger would it increase?
The programmers will continue to use operator[], and get an
exception (which in newbie code may end up being caught and
ignored). The real danger, of course, is that a program with an
error continues to run, and that a user counts on its results,
and treats them as correct. This is a danger which must be
avoided at all costs.
> > The C++ standard has no concept of "must crash" (except in
> > the case of the library functions assert() and abort()).
> > Maybe this
> If an exception isn't handled, then basically it "crashes" but
> in a less dramatic way.
If an exception isn't handled, abort() is called. The only
difference between this and an assertion failure is that you
don't get an error message, and the stack has been unwound. The
first is a pain, since you don't know why or where the program
went wrong, and the second is a real disaster -- you have an
inconsistent program state, and you want to run around executing
who knows what additional code.
> > For some things. The reasons have nothing to do with the
> > relative safety of the languages, however, but with the
> > available infrastructure -- it's possible to write server
> > side dynamic web pages in C++, but the available
> > infrastructures make it far easier in Java.
> That's probably another reason why I have the feeling C++ is
> loosing ground, because of the lack of standard libraries. The
> Boost library is making excellent progress to remedy this, but
> the huge companies behind Java and C# make it very hard to
> catch up.
It's more than just libraries -- it's a complete infrastructure.
But it's true that the lack of libraries does hurt in some
cases; if I had to write a GUI front-end today, I'd do it in
Java, because Swing is fairly good, it's portable, and it's
standard. There are some portable GUI libraries for C++, of
course, but you don't automatically expect the next C++
programmer on the project to know them -- he may have used a
different library on his last project.
On the other hand, the fact that Java raises an exception in
case of a bounds check error, instead of aborting, means that
I'm not about to use it in any program which handles important
data.
> > It's interesting to note that Java hasn't replaced C++ in
> > critical applications, because it isn't safe enough. In the
> > hands of an organization which knows what it is doing, C++
> > is in fact far safer than Java.
> But that's exactly my point, that those developers who don't
> know what they are doing, are giving C++ a bad reputation.
Developers who don't know what they are doing did give C++ a bad
reputation in the past. But developers who don't know what they
are doing tend to go to the trendiest language -- they've since
given Java a worse reputation than it deserves, and today,
they're all working in C#, so C++ doesn't have to worry. Unless
it becomes the latest trend again:-).
Seriously, a developer who doesn't know what he is doing will
write incorrect code in any language. Which is worse: for the
program to crash, or for it to give the user a warm fuzzy
feeling and wrong answers?
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/8/2005 4:22:12 PM
|
|
On 8 Dec 2005 11:22:12 -0500, "kanze" <kanze@gabi-soft.fr> wrote:
>Developers who don't know what they are doing did give C++ a bad
>reputation in the past. But developers who don't know what they
>are doing tend to go to the trendiest language -- they've since
>given Java a worse reputation than it deserves, and today,
>they're all working in C#, so C++ doesn't have to worry.
LOL ... good one!
>Unless it becomes the latest trend again:-).
Actually, I think this would actually improve the situation. More
developers (even bad ones) == more compilers sold == better support
for the C++ standard.
My reasoning?
(a) It costs lots of time/effort/money to develop a truly
standards-conforming implementation;
(b) The important compiler vendors/implementers nowadays all realize
how important it is to be standards-conforming, even if 100% remains
an unreachable goal for some;
(c) Developers will pay more attention to writing standards-conforming
code if the implementation they use enforces this;
(d) The C++ standard itself will improve when more developers use it
and provide feedback and suggestions to the standards committee.
The worst thing that could happen to C++ is to become a little-used,
"niche" or elitist language. But I see no danger of that today.
--
Bob Hairgrove
NoSpamPlease@Home.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/8/2005 8:14:00 PM
|
|
kanze wrote:
> Peter Most wrote:
>> Rob wrote:
>
>> > Peter Most wrote:
>
>> [snip]
>> >> The novice developer reads more about vector and learns,
>> >> that there are two ways to access the elements. A fast but
>> >> unsafe way via the operator[] and a version which is little
>> >> slower but safer way via at(). He starts to use at()
>> >> because it is a lot safer and he doesn't need that last bit
>> >> of speed.
>
>> > No. The novice developer is unlikely to pick up on
>> > existance of at(), but will be more careful in use of
>> > operator[]. This isn't a fault of the novice: it's
>> > because most examples (at least those that don't involve
>> > using iterators) in introductory texts use operator[]
>
>> That's the point I'm trying to make. If operator[] would check
>> the index then the novice developer would start to use the
>> safe version.
>
> You don't seem to be too familiar with C++ to begin with.
Please be careful with such statements!
> Calling operator[] with an out of bounds index is undefined
> behavior precisely so that an implementation *can* check the
> index, and do whatever is appropriate *on* *that* *platform* for
> a programming error. On Unix, I expect a core dump, but from
> what I understand, this is not the usual case under Windows.
>
Wouldn't it be easier to simply define that operator[] has to check the
index, then it would be one less undefined behavior?
> Most serious implementations of the standard library today come
> with debugging versions, which check not only the index, but a
> lot of other things. I would expect that to be the version a
> beginner is using -- there is probably some work to be done
> concerning compiler options and defaults, for this to be the
> case, but compiler options and defaults are not a subject of
> standardization.
>
>> > What you are describing is the same sort of thinking that
>> > resulted in Java: they removed features from the language
>> > they din't like, and added others that they claimed were
>> > safer.
>
> I view the situation a little different. Java's designers
> decided that one specific level of safety is appropriate for
> all. It's implemented in the language, and you can't change it.
> C++ leaves a lot more up to the implementation and the
> programmer. It can be as dangerous OR as safe as you want.
>
> IMHO, the built in level of safety in Java is too low for
> serious applications, and since you cannot increase it, Java as
> largely unusable for large classes of applications, where
> reliability is a must. And while I sometimes wish that the
> default level of safety of C++ was a little higher, it's not
> that difficult to make it as safe as is needed, and I have no
> qualms about using it in critical software.
>
> --
> James Kanze GABI Software
> Conseils en informatique orient�e objet/
> Beratung in objektorientierter Datenverarbeitung
> 9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/8/2005 8:14:22 PM
|
|
kanze wrote:
> Peter Most wrote:
>> David Abrahams wrote:
>
>> > Peter Most <Peter_Most@gmx.de> writes:
>
>> >> I would like to get some opinions about whether it would
>> >> make sense to change the C++ design principles, so it would
>> >> become safer for beginner?
>
>> >> I'm assuming there are two kind of beginner: 1) A beginner
>> >> who, through reading and education, will eventually become
>> >> an advanced developer and maybe even an expert developer.
>> >> For this discussion I simple call them "novice" developer.
>
>> >> 2) A beginner who doesn't read or educate himself and
>> >> simply muddles through. I would like to call this kind of
>> >> developer "ignorant" developer.
>
>> >> Now both kind of developers start to program and sooner or
>> >> later they start to use arrays. Both will probably at some
>> >> point start to use a vector. I'm also assuming that both
>> >> developers have difficulties with the index and the
>> >> designer of vector obviously also assumed this, why else
>> >> would there an at()?
>
>> >> The novice developer reads more about vector and learns,
>> >> that there are two ways to access the elements. A fast but
>> >> unsafe way via the operator[] and a version which is little
>> >> slower but safer way via at(). He starts to use at()
>> >> because it is a lot safer and he doesn't need that last bit
>> >> of speed.
>
>> > Safer how? It won't make an unintentional out-of-bounds
>> > access any more correct. Incorrect code can always have
>> > unintended (i.e. unsafe) behaviors.
>
>> Safer in the way that it is not simply crashing, but throws an
>> exception which hopefully will be caught somewhere. At least
>> that way you can add a toplevel exception handler and know
>> that this error won't crash your program.
>
> But that makes it more dangerous. The only "good" thing that
> wrong code can do is crash, so that you know it's wrong. And
> the sooner it crashes, the better. The unsafe behavior of
> operator[] is that it might not crash. That you might not even
> notice the error, and the program will simply give bad results.
>
If crashing would be such a good thing, then why check for errors at all? I
think only small, short running programs can "live" with a crash. But all
other programs simply can not accept a crash.
> --
> James Kanze GABI Software
> Conseils en informatique orient�e objet/
> Beratung in objektorientierter Datenverarbeitung
> 9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/8/2005 8:16:07 PM
|
|
kanze wrote:
> IMHO, the built in level of safety in Java is too low for
> serious applications,
Specifically what?
> and since you cannot increase it, Java as
> largely unusable for large classes of applications, where
> reliability is a must. And while I sometimes wish that the
> default level of safety of C++ was a little higher, it's not
> that difficult to make it as safe as is needed, and I have no
> qualms about using it in critical software.
I'd say "poppycock!" if it weren't for two reasons: (1) it's you, and
(2) I don't know exactly what "poppycock" means.
How can you make C++ "as safe as is needed"? Let's say, I need to make
C++ such that there are no soft memory errors, just like Java. (I
understand that's a pretty low bar, in wake of what you wrote above.)
How do I do that?
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/8/2005 8:19:30 PM
|
|
kanze wrote:
> You don't seem to be too familiar with C++ to begin with.
> Calling operator[] with an out of bounds index is undefined
> behavior precisely so that an implementation *can* check the
> index, and do whatever is appropriate *on* *that* *platform* for
> a programming error.
Huh? Could you substantiate that statement, particularly since you used
"precisely"? I thought the behavior of operator[] undefined for the sake
of speed. Otherwise it would have been very easy to define illegal
invocations of operator[] to stop execution in a way specified by the
implementation.
As far as I've known for the longest time, "undefined" often stands for
"for the sake of generating the fastest code possible when the program
has no error."
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/8/2005 8:20:08 PM
|
|
kanze wrote:
> Bob Hairgrove wrote:
>
>>On 6 Dec 2005 13:17:34 -0500, Peter Most <Peter_Most@gmx.de>
>>wrote:
>
>
> [...]
>
>
>>>Should C++ changed to be a more beginner friendly language?
>
>
>>No ... isn't that why we have Java, C# and Visual Basic? ;)
>
>
> I don't know about C# or Visual Basic, but I certainly wouldn't
> consider Java more beginner friendly than C++. Except, perhaps,
> in the sense that its syntax has less "traps". (But that's a
> good thing even for experts.) It's far harder to write a robust
> and correct application in Java than it is in C++.
I don't have hard data, but my intuition goes the exact opposite way.
IMHO the learning curve of C++ quite resembles a wall, and the multitude
of soft errors make it very tough for a beginner to make sure they wrote
a robust and correct application. Java doesn't have soft errors, and
intuitively that should make it easier for a beginner to make a program
work by other means than pure chance.
Bus since your statement above is this resolute, perhaps you do have
some evidence. Could you please share?
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/8/2005 8:20:30 PM
|
|
kanze wrote:
> Peter Most wrote:
>> kanze wrote:
>
>> I have to disagree. Crashing is never a good solution. If the
>> program stops because the exception wasn't handled, then it's
>> probably OK.
>
> And if the program doesn't stop? Crashing is the accepted way
> of handling programming errors. It's required in critical
> systems, but it is also generally a good idea in most
> non-critical systems, at least those which handle real data, and
> whose results are used. Masking errors may give the user a
> warm, fuzzy fealing, but it also gives him wrong results.
>
Since when is crashing an accepted way of handling errors? It's the worst
handling!
Are you considering exception handling as masking errors?
>> >> So my question is now, would it not have been better to
>> >> reverse the semantic of the two versions, meaning
>> >> operator[] checks the index and throws an exception and
>> >> at() would allow an unchecked access?
>
>> > That sounds like a good way of increasing the danger.
>
>> What danger would it increase?
>
> The programmers will continue to use operator[], and get an
> exception (which in newbie code may end up being caught and
> ignored). The real danger, of course, is that a program with an
> error continues to run, and that a user counts on its results,
> and treats them as correct. This is a danger which must be
> avoided at all costs.
>
I agree, if a developer would actually catch and ignore the exception then
there is a huge problem and then and only then would a crash actually be
acceptable. But are there really such irresponsible developers?
>> > The C++ standard has no concept of "must crash" (except in
>> > the case of the library functions assert() and abort()).
>> > Maybe this
>
>> If an exception isn't handled, then basically it "crashes" but
>> in a less dramatic way.
>
> If an exception isn't handled, abort() is called. The only
> difference between this and an assertion failure is that you
> don't get an error message, and the stack has been unwound. The
> first is a pain, since you don't know why or where the program
> went wrong, and the second is a real disaster -- you have an
> inconsistent program state, and you want to run around executing
> who knows what additional code.
>
But it would be quite easy to add a toplevel exception handler and log the
error message. In the case of a crash you can only hope that you either are
getting some kind of core/memory dump which you can analyse with a debugger
or the program is already running in the debugger to get the call stack.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/8/2005 9:58:04 PM
|
|
Peter Most wrote:
> kanze wrote:
> > Calling operator[] with an out of bounds index is undefined
> > behavior precisely so that an implementation *can* check the
> > index, and do whatever is appropriate *on* *that* *platform* for
> > a programming error. On Unix, I expect a core dump, but from
> > what I understand, this is not the usual case under Windows.
> >
> Wouldn't it be easier to simply define that operator[] has to check the
> index, then it would be one less undefined behavior?
If you want it to throw an exception like at() does, then no, I don't
think that would be easier. I think that would in many cases mask bugs
-- most of the time, calling operator[] with an out-of-bounds index is
a bug; throwing an exception when a bug occurs simply allows the
program to continue running with a bug.
I much prefer the debugging checks described by James above.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/8/2005 9:59:40 PM
|
|
Peter Most wrote:
> kanze wrote:
>
> > Peter Most wrote:
> >> Safer in the way that it is not simply crashing, but throws an
> >> exception which hopefully will be caught somewhere. At least
> >> that way you can add a toplevel exception handler and know
> >> that this error won't crash your program.
> >
> > But that makes it more dangerous. The only "good" thing that
> > wrong code can do is crash, so that you know it's wrong. And
> > the sooner it crashes, the better. The unsafe behavior of
> > operator[] is that it might not crash. That you might not even
> > notice the error, and the program will simply give bad results.
> >
> If crashing would be such a good thing, then why check for errors at all?
To *make sure* it crashes, so that we can debug the program.
> I
> think only small, short running programs can "live" with a crash. But all
> other programs simply can not accept a crash.
But they can accept producing incorrect results? You seem to be saying
that incorrect results are preferred over crashes.
"Continue running at all costs, even when bugs are detected," simply
makes it more difficult to find and fix bugs, decreasing (not
increasing) the reliability of the program and the correctness of its
results.
(Controlled) crashing (i.e., from an assertion or other debugging
check), on the other hand, frequently makes it easier to find and fix
bugs, increasing reliability.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/8/2005 11:47:56 PM
|
|
"Peter Most" <Peter_Most@gmx.de> wrote in message
news:43988959$0$27885$9b4e6d93@newsread4.arcor-online.net...
> kanze wrote:
> > But that makes it more dangerous. The only "good" thing that
> > wrong code can do is crash, so that you know it's wrong. And
> > the sooner it crashes, the better. The unsafe behavior of
> > operator[] is that it might not crash. That you might not even
> > notice the error, and the program will simply give bad results.
> >
> If crashing would be such a good thing, then why check for errors at all?
I
> think only small, short running programs can "live" with a crash. But all
> other programs simply can not accept a crash.
James is right.The alternative to a failed program crashing in an obvious
manner is having it fail in an undetectable way, like for example generating
subtly corrupted results. Would you care to use, say, a bridge design
program, that generated corrupt stress numbers? Would you even know anything
was wrong with the numbers? How about a bank's financial processing program?
A crash, as early and as obvious as possible, is the only way. Then, the
user of the program *knows* it's broken and will not mistakenly rely on its
output. The developer of the program *knows* there's a bug, which is the
first step towards fixing it. And the airliner autopilot *knows* its
crashed, and the backup system can wake up the pilot before it augers into a
mountainside.
Solid, professional programs should have as much Contract Programming and
sanity tests on its internal workings as practical considering the
performance requirements. If a fault is detected, the program should
promptly inform the user and shut itself down (crash, if you will).
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/8/2005 11:54:21 PM
|
|
Peter Most wrote:
> Wouldn't it be easier to simply define that operator[] has to check the
> index, then it would be one less undefined behavior?
>
Main reason I'm not using 'at' is that it throws exception.
What to do with that exception? what action to perform?
try ' ing around vectors 'at' is just silly.
if I can reliably get file and line then ok but it is much easier to
place assertion and get core dump which will trace to assertion that
failed,
and one can also examine variables with debugger.
Much more usable.
Greetings, Bane.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Branimir
|
12/8/2005 11:54:44 PM
|
|
"Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes:
> kanze wrote:
>> You don't seem to be too familiar with C++ to begin with.
>> Calling operator[] with an out of bounds index is undefined
>> behavior precisely so that an implementation *can* check the
>> index, and do whatever is appropriate *on* *that* *platform* for
>> a programming error.
>
> Huh? Could you substantiate that statement, particularly since you used
> "precisely"? I thought the behavior of operator[] undefined for the sake
> of speed.
Exactly. On some platforms the appropriate behavior in case of such a
programming error is... hope that it never occurs, because we can't
afford the cost of checking.
> Otherwise it would have been very easy to define illegal
> invocations of operator[] to stop execution in a way specified by the
> implementation.
>
> As far as I've known for the longest time, "undefined" often stands for
> "for the sake of generating the fastest code possible when the program
> has no error."
In this case it means, "on some implementations it may be important
not to pay for the check, and on some others we don't want to restrict
the implementation's choice of behavior in case a check fails"
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
12/9/2005 12:01:23 AM
|
|
Peter Most wrote:
> kanze wrote:
>> Calling operator[] with an out of bounds index is undefined
>> behavior precisely so that an implementation *can* check the
>> index, and do whatever is appropriate *on* *that* *platform* for
>> a programming error. On Unix, I expect a core dump, but from
>> what I understand, this is not the usual case under Windows.
>>
> Wouldn't it be easier to simply define that operator[] has to check the
> index, then it would be one less undefined behavior?
I'm pretty capable of making sure that I have no out of bounds
accesses when using arrays (of whatever form, be it built-in arrays,
'std::vector<T>', 'std::deque<T>', etc.). I don't want any redundant
checks for whether I did the right thing. I don't mind them if they
come with no extra cost but it isn't my experience that they do.
Even if there are checks, I guess we would choose different
implementations for how they deal with programming errors: I want the
implementation to "crash" (i.e. write an inspectable post-mortem image
and abort); your preference seems to be that an exception is thrown
such that you can try to continue (which is, IMO, a futile attempt
anyway: if the programmer is incapable of correctly implementing the
successful case, how will he be capable of recovering from his own
errors at all?).
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/9/2005 10:49:18 AM
|
|
Peter Most wrote:
> If crashing would be such a good thing, then why check for errors at all?
Do you check for programming errors in production code? I do not!
.... and I also don't spent any time trying to recover from situations
I don't know about. After all, how can I recover from something I
don't know?
> I think only small, short running programs can "live" with a crash.
No program can live with a crash. Neither "small, short running" ones,
nor large, long running ones. However, it does not matter: if there is
a programming error, we are best off stopping as soon as possible
before causing more problems by stumbling on. Better have a loud
crash than a silent failure. Undetected problems are much worse than
those problems you don't know about.
> But all other programs simply can not accept a crash.
This is why it is important to have a good software production
process in place and verify that the software is correct. Trying to
recover from an error the programmer made is futile. After all, if
the programmer had anticipated this error he would have been better
off removing it rather than recovering from it in the first place.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/9/2005 10:50:35 AM
|
|
Peter Most wrote:
> kanze wrote:
>> And if the program doesn't stop? Crashing is the accepted way
>> of handling programming errors. It's required in critical
>> systems, but it is also generally a good idea in most
>> non-critical systems, at least those which handle real data, and
>> whose results are used. Masking errors may give the user a
>> warm, fuzzy fealing, but it also gives him wrong results.
>>
> Since when is crashing an accepted way of handling errors? It's the worst
> handling!
It may be the worst kind of handling from a sales point of view. After
all, visible crashes are clearly an indication of bad stability.
However, crashing is much preferable to causing damage by continuing
after an unknown and thus almost certainly only partially recovered
programming error. However, the programming error is there, whether
the program crashes or tries to stumble on. The error is obviously
not due to a [n anticipated] bad external state.
> Are you considering exception handling as masking errors?
I would consider some forms of exception handling as masking errors.
There are clearly reasonable uses of exceptions, e.g. to recover
from an anticipated error. Recovery from a programming errors is,
as I stated before, very unlikely to be successful and I would
consider trying to handle it with an exception "masking errors". For
example, an out of bounds access in an array is a programming error
unless it deliberately used a method with specified semantics in case
of an out of bounds access.
> I agree, if a developer would actually catch and ignore the exception then
> there is a huge problem and then and only then would a crash actually be
> acceptable. But are there really such irresponsible developers?
I have come across even more irresponsible developers: those who
think they can generically recover from their own errors by handling
exceptions! They were obviously incapable of creating correct code
but confident enough to think that they could correctly recover
automatically from their flaws. To my mind this is very strange
logic...
> But it would be quite easy to add a toplevel exception handler and log the
> error message.
.... and continue with some corrupted state?
> In the case of a crash you can only hope that you either
> are getting some kind of core/memory dump which you can analyse with a
> debugger or the program is already running in the debugger to get the call
> stack.
I don't know what kind of operating you have done but in the places
I have worked it is custom to monitor program failures and provide
some form of reaction. At the bare minimum programs are automatically
restarted (in a clean state, of course) and a human is alerted to a
problem caused by a program crash. Each crash was investigated to
determine the reason of the crash (which involved analysis of the
core dump). This may be impractical when shipping a product in which
case you are better off testing it thoroughly such that program
crashes are extremely unlikely. In any case it makes sense to test
the software with some system detecting various kinds of programming
errors, e.g. purify and/or a debugging version of the standard
library.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/9/2005 10:51:24 AM
|
|
Walter Bright wrote:
> James is right.The alternative to a failed program crashing in an obvious
> manner is having it fail in an undetectable way, like for example generating
> subtly corrupted results. Would you care to use, say, a bridge design
> program, that generated corrupt stress numbers? Would you even know anything
> was wrong with the numbers? How about a bank's financial processing program?
That's right, however the assertions being made don't really fit the
context of the discussion.
The context is: why is operator[] undefined for invalid input?
Answer: It is undefined so implementations can check and crash the
programs.
That answer is bogus. If the behavior is undefined, that means I can't
count on it. You can't count on it. We can't count on it. There's only
one who can count on it, and that's "Nobody". :o)
We can't write a standard-conforming C++ program that will "fail
rigorously" using operator[]. It's actually very likely that the program
will silently fail, thus doing everything that James says about "bad"
programs!
The natural conclusion that the answer above leads to, is that
operator[] should be defined to terminate execution for invalid input.
But it doesn't (proof that the anwer is bogus); behavior is undefined so
implementations can generate the fastest code for the correct case -
which brings us to the correct answer.
So IMHO the answer to "why is operator[] undefined for invalid input?"
is: "C++ sacrifices memory safety for the sake of efficiency. Although
some people believe that operator[] should be defined for all inputs and
at() should be the alternative with undefined behavior, this is how
things are as of today."
And I still don't understand how C++, being rife with soft errors that
travel below the type system's radar and dynamic checks alike, is safer
than Java, which has no memory errors and no undefined behavior. Please
illuminate me.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 10:55:58 AM
|
|
Branimir Maksimovic wrote:
> Peter Most wrote:
>
>>Wouldn't it be easier to simply define that operator[] has to check the
>>index, then it would be one less undefined behavior?
>>
>
>
> Main reason I'm not using 'at' is that it throws exception.
> What to do with that exception? what action to perform?
> try ' ing around vectors 'at' is just silly.
But the idea wasn't to try around 'at'. There are tons of cases in which
you can centralize error handling and return the system to a stable state.
Simple example: I load some data in a vector<Data> and an index into the
data into another vector<unsigned>. If the data is corrupt (an event of
low likelihood but possible nonetheless), at() will throw an exception,
I can report (in a centralized manner) that the data is corrupt, clear
the index, and rebuild or whatnot.
I disagree that the only thing to do in the case of an out-of-bound
access is to crash immediately. I think it's a natural outcome of our
history: we C++ programmers are so used to unchecked array access, we
have in mind that out-of-bounds access indicates a deep application
error - and we develop programs like that.
However, if we know that bounds checking is performed reliably and
reproductibly, we can develop programming styles that allow better
centralization of error handling.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 10:56:28 AM
|
|
> I have to disagree. Crashing is never a good solution.
Your right, it is not good. But the better alternative is... (please fill in
the blanks)?
It also depends on the type of error.
If it some form of input error or range error, it maybe that the program can
recover gracefully, continue with an alternative course of action. That
seems right.
But if it is a logical inconsistency in the program, the type of error that
assert() is useful to catch, any action could be wrong. "Continuing" may
make it hard to diagnose what is wrong.
In the absence of anything better, termination is not bad (hopefully with
enough output to help the programmer to work out what is wrong). The
programmer can fix the logical bug and retry the program.
Stephen Howe
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Stephen
|
12/9/2005 10:59:20 AM
|
|
Peter Most wrote:
> kanze wrote:
> > Peter Most wrote:
> >> David Abrahams wrote:
> >> > Peter Most <Peter_Most@gmx.de> writes:
[...]
> >> > Safer how? It won't make an unintentional out-of-bounds
> >> > access any more correct. Incorrect code can always have
> >> > unintended (i.e. unsafe) behaviors.
> >> Safer in the way that it is not simply crashing, but throws
> >> an exception which hopefully will be caught somewhere. At
> >> least that way you can add a toplevel exception handler and
> >> know that this error won't crash your program.
> > But that makes it more dangerous. The only "good" thing
> > that wrong code can do is crash, so that you know it's
> > wrong. And the sooner it crashes, the better. The unsafe
> > behavior of operator[] is that it might not crash. That you
> > might not even notice the error, and the program will simply
> > give bad results.
> If crashing would be such a good thing, then why check for
> errors at all?
Because bounds errors (and any number of other types of errors)
don't usually crash.
> I think only small, short running programs can "live" with a
> crash. But all other programs simply can not accept a crash.
Realistically, all programs must be able to live with a crash.
At least on my system, dereferencing an out of bounds pointer
will cause a crash, as will accessing through a mis-aligned
pointer. And there's really no way to catch the first of these.
In critical applications, it is an absolute rule that in the
slightest doubt concerning the correction of the program, it
must crash. Stumbling on with possibly incorrect data is not
considered an alternative.
The same thing holds for any number of other applications. Most
of the time, wrong answers are worse than no answers.
There are clearly exceptions: the most obvious is a demo program
for an exposition, where a crash is highly visible, and no one
is going to use the results, or even verify that they are
correct. But any time the results are to be used for anything
significant, it is important that they be correct, and crashing
is preferable to wrong results.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/9/2005 3:08:33 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Walter Bright wrote:
> > James is right.The alternative to a failed program crashing
> > in an obvious manner is having it fail in an undetectable
> > way, like for example generating subtly corrupted results.
> > Would you care to use, say, a bridge design program, that
> > generated corrupt stress numbers? Would you even know
> > anything was wrong with the numbers? How about a bank's
> > financial processing program?
> That's right, however the assertions being made don't really
> fit the context of the discussion.
The topic of this subthread has shifted a bit.
> The context is: why is operator[] undefined for invalid input?
> Answer: It is undefined so implementations can check and crash
> the programs.
> That answer is bogus. If the behavior is undefined, that means
> I can't count on it. You can't count on it. We can't count on
> it. There's only one who can count on it, and that's "Nobody".
> :o)
That's one way of looking at it, and I agree that requiring a
program to crash every time there is undefined behavior would be
a major step forward. (Given the difficulty of detecting some
of the cases, I'm not sure that people more concerned than I am
about performance would agree:-).)
The other way is to use an implementation that you know, which
does give the desired behavior. Said implementation would, of
course, still be conform.
> We can't write a standard-conforming C++ program that will
> "fail rigorously" using operator[]. It's actually very likely
> that the program will silently fail, thus doing everything
> that James says about "bad" programs!
> The natural conclusion that the answer above leads to, is that
> operator[] should be defined to terminate execution for
> invalid input. But it doesn't (proof that the anwer is
> bogus); behavior is undefined so implementations can generate
> the fastest code for the correct case - which brings us to the
> correct answer.
> So IMHO the answer to "why is operator[] undefined for invalid
> input?" is: "C++ sacrifices memory safety for the sake of
> efficiency. Although some people believe that operator[]
> should be defined for all inputs and at() should be the
> alternative with undefined behavior, this is how things are as
> of today."
As a general response, of course, you're right. As an answer to
the suggestion that the behaviors of at() and operator[]()
should be inversed, however...
> And I still don't understand how C++, being rife with soft
> errors that travel below the type system's radar and dynamic
> checks alike, is safer than Java, which has no memory errors
> and no undefined behavior. Please illuminate me.
First, of course, the claim that Java has no undefined behavior
is not true. It very definitly has undefined behavior,
explicitly in the case of threading, and in practice, also
because the specification isn't always as precise as one would
like.
The major point, however, is that Java never gives you a choice.
No critical application would ever use dynamic loading, for
example, because of the problems involving versioning; Java
requires it, and at the level of the class (or at the least, the
package). The critical applications I've worked on have made
extensive use of programming by contract, with non-virtual
public functions enforcing the contract before calling virtual
private functions -- a technique forbidden in Java, where an
"interface" can have no implementation code whatever, and
private functions cannot be virtual. On large projects, the
interface (in the classical sense of the word, not the
specialized Java sense -- the .hh files in the C++
implemenation) has been under the control of a central
architecture committee, and was only editable with explicit
authorisation. Java requires that the code and the interface be
in the same file, so anytime you can edit one, you can modify
the other. (Admittedly, this last point is more concerned with
project managability than with safety, and really only concerns
large projects.) And I don't think I have to explain to you the
advantages of destructors over finally blocks.
And of course, in Java, even critical failures, like a
VirtualMachineError, are exceptions, so you are forced to
continue executing (at least the finally blocks), even when you
know that the underlying machine is not working correctly.
Obviously, that doesn't mean that everything in Java is bad. I
regularly use garbage collection in C++, just as I regularly use
checking implementations of the STL, but as you correctly point
out, neither of these are requirements of the standard. And of
course, avoiding undefined behavior in C++ is a real art;
strictly specifying the order of evaluation in an expression
would go a long way to solving this. But globally, the problems
are less than in Java.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/9/2005 3:13:16 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Branimir Maksimovic wrote:
> > Peter Most wrote:
> >>Wouldn't it be easier to simply define that operator[] has
> >>to check the index, then it would be one less undefined
> >>behavior?
> > Main reason I'm not using 'at' is that it throws exception.
> > What to do with that exception? what action to perform?
> > try ' ing around vectors 'at' is just silly.
> But the idea wasn't to try around 'at'. There are tons of
> cases in which you can centralize error handling and return
> the system to a stable state.
If we're talking about "expected" errors, yes. The problem is
the "impossible" errors; the ones that cannot happen in correct
code.
> Simple example: I load some data in a vector<Data> and an
> index into the data into another vector<unsigned>. If the
> data is corrupt (an event of low likelihood but possible
> nonetheless), at() will throw an exception, I can report (in a
> centralized manner) that the data is corrupt, clear the index,
> and rebuild or whatnot.
Where does the data in the index vector come from? What makes
it corrupt? If the index data is, for example, read from disk,
I would tend to prefer validating it immediately after reading
it, but I can imagine cases where a "lazy" validation would be
appropriate, and in such cases, why not let vector<>::at() do
it? If, however, I've validated it, and at() still throws, what
does that tell me about the program state. *Only* that
something is wrong. No more. Whatever corrupted the data in
the index array has probably corrupted a lot of other things as
well -- if some code is writing random memory, it's rare that
the very first write will touch something that you'll immediatly
detect.
> I disagree that the only thing to do in the case of an
> out-of-bound access is to crash immediately. I think it's a
> natural outcome of our history: we C++ programmers are so used
> to unchecked array access, we have in mind that out-of-bounds
> access indicates a deep application error - and we develop
> programs like that.
In most of the code I write, an out of bounds access does
indicate a deep application error. That was true when I wrote
Java as well, but as you say, that could just be because of
deeply ingrained habits. The point is, however, it either is or
it isn't, and the programmer must know this. If he hasn't
thought about the issue, then it likely is a deep application
error -- he didn't think that it was possible. But I can easily
imagine (or rather, I can easily imagine that other people can
imagine) cases where an illegal index is part of the design.
Some of the Java programmers I worked with found it fully normal
to read user input and use it directly as an index into an
array, counting on catching the exception to report illegal
input.
> However, if we know that bounds checking is performed reliably
> and reproductibly, we can develop programming styles that
> allow better centralization of error handling.
I can understand that point of view. That there could be two
types of indexing errors, those that can't happen, and those
that can. When those that can't happen do, the only acceptable
behavior is to stop the program as quickly as possible -- no
stack unwinding or any of that stuff. And as you say, if you've
working with C and C++ -- where an indexing error is undefined
behavior -- you're definitly not in the habit of using it to
catch errors that you know can happen.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/9/2005 3:51:37 PM
|
|
Peter Most wrote:
> Wouldn't it be easier to simply define that operator[] has to check the
> index, then it would be one less undefined behavior?
No. This far it helps nothing. Specifying that the implementation
has to check for a condition but not specifying what to do in
that case does not work.
Than, it would be allowed to do the same as it is now. That doesn't
help you in any way.
Even more, using the general "as-if" rule, the compiler would be
allowed to remove the test, for you can't see using the specified
behaviour, if the test is executed or not.
As a consequence, and probably you meant it this way, you don't
have only to specify to check the index, but you have to specify
what to do when the test fails.
As you see in this discussion, this decision is not easy.
One user likes to get a core dump, another user likes to
get most high speed for the release version, an implementer
would like to start a debugger, ...
No, it is easier just to specify undefined behaviour, as the
current standard does...
Best regards,
Kurt.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Kurt
|
12/9/2005 3:58:12 PM
|
|
kanze wrote:
> Part of the problem is that C++ is designed to be statically
> compiled. It should be possible to define a portable "byte
> code" with an interpreter for it, but to my knowledge, no one
> has done so. It would be very difficult for a browser to run
> code compled for Windows on a PC on my Sparc under Solaris. THe
> current state of affairs is that to run C++ directly, my browser
> would have to download the sources, and compile and link them.
>
I'm not so familiar with the newer microsoft compilers, but isn't the
managed C++ compiler from Microsoft targeting the CLR?
See also: http://en.wikipedia.org/wiki/Managed_Extensions_for_C_Plus_Plus
And if Mono progresses as planned, then it would also run under Linux.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/9/2005 6:36:48 PM
|
|
Kurt Stege wrote:
> Peter Most wrote:
>
>> Wouldn't it be easier to simply define that operator[] has to check the
>> index, then it would be one less undefined behavior?
>
> No. This far it helps nothing. Specifying that the implementation
> has to check for a condition but not specifying what to do in
> that case does not work.
>
I meant to reverse the semantics i.e.:
1) operator[] checks the index and throws an out_of_range exception (as at()
is currently doing).
2) at() isn't checking anything and maybe crashes (as operator[] is
currently doing).
[snip]
> As you see in this discussion, this decision is not easy.
> One user likes to get a core dump, another user likes to
> get most high speed for the release version, an implementer
> would like to start a debugger, ...
>
I have to admit, I hadn't expect so much different opinions about a
supposedly simple thing like the vector access. But it gave me a lot more
insight for the *very* different environments where C++ is used.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/9/2005 6:37:09 PM
|
|
kanze wrote:
>> I think only small, short running programs can "live" with a
>> crash. But all other programs simply can not accept a crash.
>
> Realistically, all programs must be able to live with a crash.
> At least on my system, dereferencing an out of bounds pointer
> will cause a crash, as will accessing through a mis-aligned
> pointer. And there's really no way to catch the first of these.
>
The application I was working on was multithreaded, where the threads where
quite independent from each other, meaning if one thread crashes then the
program could and should continue to work with the other threads. But the
usual behavior for threads makes this very difficult, because if one thread
crashes then the complete process is crashing. In this scenario I prefer a
thrown exception which I can catch and log in a toplevel handler and then
let the tread die gracefully so that the other threads can still continue.
> In critical applications, it is an absolute rule that in the
> slightest doubt concerning the correction of the program, it
> must crash. Stumbling on with possibly incorrect data is not
> considered an alternative.
>
> The same thing holds for any number of other applications. Most
> of the time, wrong answers are worse than no answers.
>
> There are clearly exceptions: the most obvious is a demo program
> for an exposition, where a crash is highly visible, and no one
> is going to use the results, or even verify that they are
> correct. But any time the results are to be used for anything
> significant, it is important that they be correct, and crashing
> is preferable to wrong results.
>
That's a point I still don't quite understand. If I access the vector with a
wrong index and an exception is thrown, how can it be that the program is
continuing with wrong data? This would only be possible if the developer
deliberately and irresponsible catches the exception and tries to muddle
trough with some made up and hence wrong data. Are you talking about such a
scenario?
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/9/2005 9:17:58 PM
|
|
On 8 Dec 2005 19:01:23 -0500, David Abrahams
<dave@boost-consulting.com> wrote:
>On some platforms the appropriate behavior in case of such a
>programming error is... hope that it never occurs, because we can't
>afford the cost of checking.
[...]
>In this case it means, "on some implementations it may be important
>not to pay for the check, and on some others we don't want to restrict
>the implementation's choice of behavior in case a check fails"
The problem are half-encapsulated classes (functions, templates) that
half-protect the user against misuse. Although that may be an
acceptable compromise for low-level code like STL it should be
considered bad style in general.
Best regards,
Roland Pibinger
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
rpbg123
|
12/9/2005 9:19:55 PM
|
|
kanze wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>The context is: why is operator[] undefined for invalid input?
>
>
>>Answer: It is undefined so implementations can check and crash
>>the programs.
>
>
>>That answer is bogus. If the behavior is undefined, that means
>>I can't count on it. You can't count on it. We can't count on
>>it. There's only one who can count on it, and that's "Nobody".
>>:o)
>
>
> That's one way of looking at it, and I agree that requiring a
> program to crash every time there is undefined behavior would be
> a major step forward. (Given the difficulty of detecting some
> of the cases, I'm not sure that people more concerned than I am
> about performance would agree:-).)
>
> The other way is to use an implementation that you know, which
> does give the desired behavior. Said implementation would, of
> course, still be conform.
But advising one to lock into an implementation is exactly what
experienced people know is not good. They advocate writing portable
code. You've done the same in the past. Why the sudden change of view in
this particular case?
> As a general response, of course, you're right. As an answer to
> the suggestion that the behaviors of at() and operator[]()
> should be inversed, however...
We need safety more than we need speed. The easiest solution
syntactically should be safe. Speed should be attainable via extra syntax.
>>And I still don't understand how C++, being rife with soft
>>errors that travel below the type system's radar and dynamic
>>checks alike, is safer than Java, which has no memory errors
>>and no undefined behavior. Please illuminate me.
>
>
> First, of course, the claim that Java has no undefined behavior
> is not true. It very definitly has undefined behavior,
> explicitly in the case of threading, and in practice, also
> because the specification isn't always as precise as one would
> like.
Wrong. Check Java 1.5. It defines behavior of even incorrect threaded
programs.
Undefined behavior in Java has a very different meaning. It does NOT
mean memory errors. For example, sorting with a wrongly-written
comparitor has "undefined behavior" in Java because the collection may
get sorted in an arbitrary order, or the call to sort may never terminate.
> The major point, however, is that Java never gives you a choice.
> No critical application would ever use dynamic loading, for
> example, because of the problems involving versioning; Java
> requires it, and at the level of the class (or at the least, the
> package).
I suppose it's easy to check that the version is the expected one? Not
convinced that that's a major problem. Sounds like Java makes it
impossible for you to make sure you're using the right packages. In the
worset case, there's plenty of extralinguistic security features that
allow you to lock a program to a specific set of files.
> The critical applications I've worked on have made
> extensive use of programming by contract, with non-virtual
> public functions enforcing the contract before calling virtual
> private functions -- a technique forbidden in Java, where an
> "interface" can have no implementation code whatever, and
> private functions cannot be virtual.
That's entirely obscure. The same behavior can be achieved through a ton
of other techniques.
Abstract classes, dual interfaces, forwarding, package-level hiding,
inner classes,... come to mind.
> On large projects, the
> interface (in the classical sense of the word, not the
> specialized Java sense -- the .hh files in the C++
> implemenation) has been under the control of a central
> architecture committee, and was only editable with explicit
> authorisation. Java requires that the code and the interface be
> in the same file, so anytime you can edit one, you can modify
> the other. (Admittedly, this last point is more concerned with
> project managability than with safety, and really only concerns
> large projects.)
You can organize the files such that the interfaces and abstract classes
correspond to your interface concept, and isolate them.
If worse comes to worse, you can do a little file manipulation with
external tools. You have to do lots of that in a large project anyway.
> And I don't think I have to explain to you the
> advantages of destructors over finally blocks.
I do think you'd have to explain to me how that is a make-or-break for
*critical projects*. *Safety*. This discussion is not "Java vs. C++".
It's "Java is not usable for critical-mission projects". I do like
destructors, they are a nifty feature, but I fail to see how they make
or break safety.
Besides, C++ has no way of implementing "finally", and I found myself
enjoying to execute code upon scope exit within the context of that
scope. That feature is not attainable with C++ in any reasonable way.
> And of course, in Java, even critical failures, like a
> VirtualMachineError, are exceptions, so you are forced to
> continue executing (at least the finally blocks), even when you
> know that the underlying machine is not working correctly.
That's definitely worse than obliterating some arbitrary memory location
and continuing execution *thinking all is ok*... or is it? :o)
> Obviously, that doesn't mean that everything in Java is bad. I
> regularly use garbage collection in C++, just as I regularly use
> checking implementations of the STL, but as you correctly point
> out, neither of these are requirements of the standard. And of
> course, avoiding undefined behavior in C++ is a real art;
> strictly specifying the order of evaluation in an expression
> would go a long way to solving this. But globally, the problems
> are less than in Java.
That mellows your initial provocative statement quite some, but I assert
that that statement is simply untenable.
At every line of code, C++ offers you a vast array of means to destroy
the most solid design, in ways that are undetectable during compilation,
with static checking with any existing tool, or via dynamic checks. How
come destructors and virtual private functions make up for that?
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:21:00 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Branimir Maksimovic wrote:
>
>>Peter Most wrote:
>>
>>
>>>Wouldn't it be easier to simply define that operator[] has to check the
>>>index, then it would be one less undefined behavior?
>>>
>>
>>
>>Main reason I'm not using 'at' is that it throws exception.
>>What to do with that exception? what action to perform?
>>try ' ing around vectors 'at' is just silly.
>
>
> But the idea wasn't to try around 'at'. There are tons of cases in which
> you can centralize error handling and return the system to a stable state.
>
> Simple example: I load some data in a vector<Data> and an index into the
> data into another vector<unsigned>. If the data is corrupt (an event of
> low likelihood but possible nonetheless), at() will throw an exception,
> I can report (in a centralized manner) that the data is corrupt, clear
> the index, and rebuild or whatnot.
Yes, this is common idiom when implementing database index files.
vector<unsigned> is vector of records for example and vector<Data>
is vector of keys that point within records.
Example is excelent but unfortuantelly bounds checking
is useless in this case.
If Key is corrupted it will probably point within data record
file most of the time. So checksum of key pointer is real
answer here. If key is loaded and checksum fails then
exception is thrown, which will result in rebuilding index file.
Bounds check would be ok if all invalid keys point
out of bounds which is not the case.
>
> I disagree that the only thing to do in the case of an out-of-bound
> access is to crash immediately. I think it's a natural outcome of our
> history: we C++ programmers are so used to unchecked array access, we
> have in mind that out-of-bounds access indicates a deep application
> error - and we develop programs like that.
I completelly agree. It's just I've never have a case that
out of bounds error is something else but fatal error.
>
> However, if we know that bounds checking is performed reliably and
> reproductibly, we can develop programming styles that allow better
> centralization of error handling.
I am not worried about out of bounds errors. It's ones that
are not out of bounds, that point to incorrect data worries
me.
Greetings, Bane.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Branimir
|
12/9/2005 9:21:44 PM
|
|
kanze wrote:
> Realistically, all programs must be able to live with a crash.
> At least on my system, dereferencing an out of bounds pointer
> will cause a crash, as will accessing through a mis-aligned
> pointer. And there's really no way to catch the first of these.
No way an illegal pointer access will cause a crash.
What if the pointer points to some memory that is within the right
address space, but of the wrong type?
void foo() {
float a;
int b[3];
float b;
int * p = b + 3;
// *p will likely obliterate either a or b
}
That won't cause a crash. It will, however, cause soft errors.
> In critical applications, it is an absolute rule that in the
> slightest doubt concerning the correction of the program, it
> must crash. Stumbling on with possibly incorrect data is not
> considered an alternative.
Sure. That leads to the idea that operator[] must be checked and
execution must be aborted if the check fails, not that it yields
undefined behavior.
I think Peter has made an innocent semantic mistake, that other posters
pick on. The way I read what he's saying, when he says "checking", he
really means "checking and taking contingency measures if the check
fails". Many people seem to imply that he requres throwing an exception
as the only acceptable contingency measure.
Speaking for me at least, I'm interested in making behavior defined for
the "usual" case and allow unsafe performance with extra syntactic effort.
It's a pity that the discussion took so many meanders just to get to its
point. Instead of discussing whether behavior should be defined or
not, we discuss whether throwing or aborting is the best thing to do.
Somehow it is being aired in this thread that "checking" means
"throwing", and that "undefined behavior" naturally leads to "aborting".
That is simply false.
Checking is good. Defined behavior is good. Unchecked, undefined, are
bad. Slow is also bad, and that's the tension we are fighting against.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:23:41 PM
|
|
Kurt Stege wrote:
> Peter Most wrote:
>
>
>>Wouldn't it be easier to simply define that operator[] has to check the
>>index, then it would be one less undefined behavior?
>
>
> No. This far it helps nothing. Specifying that the implementation
> has to check for a condition but not specifying what to do in
> that case does not work.
>
> Than, it would be allowed to do the same as it is now. That doesn't
> help you in any way.
Ok, let's not nitpick. I suppose Peter meant "operator[] has to check
the index and do something sensible in case the index is invalid". It
remains to figure out what is sensible to do.
Please note that right now the code can do *anything*, including
obliterating otherwise valid data that sits next to vector's data. That
is *not* sensible. At all.
> Even more, using the general "as-if" rule, the compiler would be
> allowed to remove the test, for you can't see using the specified
> behaviour, if the test is executed or not.
Of course. That shouldn't be part of the discussion.
> As a consequence, and probably you meant it this way, you don't
> have only to specify to check the index, but you have to specify
> what to do when the test fails.
Likely so.
> As you see in this discussion, this decision is not easy.
> One user likes to get a core dump, another user likes to
> get most high speed for the release version, an implementer
> would like to start a debugger, ...
>
>
> No, it is easier just to specify undefined behaviour, as the
> current standard does...
But that's totally not the conclusion that the premise above it leads
naturally towards. The conclusion is "stops execution in a manner
defined by the implementation." Not "undefined behavior."
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:24:03 PM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134136164.940627.219400@g47g2000cwa.googlegroups.com...
> There are clearly exceptions: the most obvious is a demo program
> for an exposition, where a crash is highly visible, and no one
> is going to use the results, or even verify that they are
> correct. But any time the results are to be used for anything
> significant, it is important that they be correct, and crashing
> is preferable to wrong results.
My favorite exception is a standalone DVD player. Since there's no
possibility of updating the software on it, I'd rather it soldiered on
trying to play the DVD, regardless of if it's displaying corrupted data or
not. There's not much worse than having some friends over watching a movie,
and having the DVD player quit with "corrupted data!" leaving you to try to
skip over it with the fast forward before it hangs at the crucial spot.
It's interesting how software has embedded itself into everything. I have a
TV that needs to be "rebooted" by cycling the power every now and then.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/9/2005 9:24:51 PM
|
|
kanze wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>
>>However, if we know that bounds checking is performed reliably
>>and reproductibly, we can develop programming styles that
>>allow better centralization of error handling.
>
>
> I can understand that point of view. That there could be two
> types of indexing errors, those that can't happen, and those
> that can. When those that can't happen do, the only acceptable
> behavior is to stop the program as quickly as possible -- no
> stack unwinding or any of that stuff. And as you say, if you've
> working with C and C++ -- where an indexing error is undefined
> behavior -- you're definitly not in the habit of using it to
> catch errors that you know can happen.
Ok, it seems like we reached agreement. Let me push my luck a little bit
more: it then looks we'd need two indexing operators:
1. Out-of-bounds operator[] stops execution in an implementation-defined
manner.
2. Out-of-bounds at() throws an exception.
3. Illegal indexing into a random iterator has undefined behavior.
The problem I have with the unchecked operator[] is that it is the most
convenient to use, and therefore the most used. The default should be
safety because most of the time we want safety. Only rarely we need
absolute speed, and therefore the unsafe speed should be achievable only
with extra syntax.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:25:45 PM
|
|
Dietmar Kuehl wrote:
> I'm pretty capable of making sure that I have no out of bounds
> accesses when using arrays (of whatever form, be it built-in arrays,
> 'std::vector<T>', 'std::deque<T>', etc.). I don't want any redundant
> checks for whether I did the right thing. I don't mind them if they
> come with no extra cost but it isn't my experience that they do.
I think the question is, would you be willing to write an extra
".unchecked_at()" to avoid that extra cost?
> Even if there are checks, I guess we would choose different
> implementations for how they deal with programming errors: I want the
> implementation to "crash" (i.e. write an inspectable post-mortem image
> and abort); your preference seems to be that an exception is thrown
> such that you can try to continue (which is, IMO, a futile attempt
> anyway: if the programmer is incapable of correctly implementing the
> successful case, how will he be capable of recovering from his own
> errors at all?).
That's an entirely valid point, and it leads to a very interesting
discussion: modular exceptions.
How about defining exceptions such that they cannot be handled but in a
restricted set of modules? They should pass right through the other
modules without any chance of being caught, and make it to a few
carefully designed modules that handle them. Such a design can be
partially attained by defining exception classes in unnamed namespaces.
Still, catch (...) will eat them :o(.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:26:36 PM
|
|
Peter Most wrote:
> Kurt Stege wrote:
>
>
>>Peter Most wrote:
>>
>>
>>>Wouldn't it be easier to simply define that operator[] has to check the
>>>index, then it would be one less undefined behavior?
>>
>>No. This far it helps nothing. Specifying that the implementation
>>has to check for a condition but not specifying what to do in
>>that case does not work.
>>
>
> I meant to reverse the semantics i.e.:
> 1) operator[] checks the index and throws an out_of_range exception (as at()
> is currently doing).
> 2) at() isn't checking anything and maybe crashes (as operator[] is
> currently doing).
I think that's a more sensible choice than tha status quo. I also think
the suggestion will not make it into the standard :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/9/2005 9:26:58 PM
|
|
Peter Most wrote:
> kanze wrote:
>
>
>>>I think only small, short running programs can "live" with a
>>>crash. But all other programs simply can not accept a crash.
>>
>>Realistically, all programs must be able to live with a crash.
>>At least on my system, dereferencing an out of bounds pointer
>>will cause a crash, as will accessing through a mis-aligned
>>pointer. And there's really no way to catch the first of these.
>>
>
> The application I was working on was multithreaded, where the threads where
> quite independent from each other, meaning if one thread crashes then the
> program could and should continue to work with the other threads. But the
> usual behavior for threads makes this very difficult, because if one thread
> crashes then the complete process is crashing. In this scenario I prefer a
> thrown exception which I can catch and log in a toplevel handler and then
> let the tread die gracefully so that the other threads can still continue.
>
I would do it this way: when thread finds fatal error eg out of bounds,
it forks then aborts in child to produce core dump for debugging
purposes, while in parent it throws exception which is handled
in top level code, which then just picks another task and
continues to work. there would be also counter of fatal
errors for each module, when sufficient number is reached
threads would avoid execution of such module and
start sending some mail and SMS to author of module.
Greetings, Bane.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Branimir
|
12/10/2005 2:05:23 AM
|
|
Peter Most wrote:
> kanze wrote:
>>There are clearly exceptions: the most obvious is a demo program
>>for an exposition, where a crash is highly visible, and no one
>>is going to use the results, or even verify that they are
>>correct. But any time the results are to be used for anything
>>significant, it is important that they be correct, and crashing
>>is preferable to wrong results.
>>
>
> That's a point I still don't quite understand. If I access the vector with a
> wrong index and an exception is thrown, how can it be that the program is
> continuing with wrong data?
Why did the program use a wrong index in the first place? Something og
the calling code must be wrong ... can you prove which part it is?
-Thorsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thorsten
|
12/10/2005 2:06:18 AM
|
|
Branimir Maksimovic wrote:
> I am not worried about out of bounds errors. It's ones that
> are not out of bounds, that point to incorrect data worries
> me.
I think the errors that we should be worried about are nonlocal errors
caused by obliterating data of a different type sitting innocently next
to data manipulated by buggy code.
Memory protection has solved that at the OS level. We should strive for
solving that at the language level, too.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/10/2005 2:08:43 AM
|
|
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail@moderncppdesign.com> wrote in message
news:Ir8vF3.pos@beaver.cs.washington.edu...
> The problem I have with the unchecked operator[] is that it is the most
> convenient to use, and therefore the most used. The default should be
> safety because most of the time we want safety. Only rarely we need
> absolute speed, and therefore the unsafe speed should be achievable only
> with extra syntax.
I agree, this is the sensible approach. Too often in C++, the
straightforward intuitive way to do something is the least safe and most
deprecated way. (Virtualness of destructors comes to mind.)
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/10/2005 2:09:35 AM
|
|
"Branimir Maksimovic" <bmaxa@hotmail.com> wrote in message
news:dnck3n$n0c$1@domitilla.aioe.org...
> I completelly agree. It's just I've never have a case that
> out of bounds error is something else but fatal error.
Consider this:
try
{
for (i = 0; 1; i++)
array[i] = ...;
}
catch (ArrayBoundsException)
{
}
If array is a large array, that version of the loop will be significantly
faster than:
for (i = 0; i < array.length; i++)
array[i] = ...;
because in the latter the array bounds check gets done twice per iteration,
rather than once. The redundant check can sometimes be eliminated by a more
advanced data flow analysis optimizer, but not always.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/10/2005 2:10:32 AM
|
|
Walter Bright wrote:
> "kanze" <kanze@gabi-soft.fr> wrote in message
> news:1134136164.940627.219400@g47g2000cwa.googlegroups.com...
>
>>There are clearly exceptions: the most obvious is a demo program
>>for an exposition, where a crash is highly visible, and no one
>>is going to use the results, or even verify that they are
>>correct. But any time the results are to be used for anything
>>significant, it is important that they be correct, and crashing
>>is preferable to wrong results.
>
>
> My favorite exception is a standalone DVD player. Since there's no
> possibility of updating the software on it, I'd rather it soldiered on
> trying to play the DVD, regardless of if it's displaying corrupted
data or
> not. There's not much worse than having some friends over watching a
movie,
> and having the DVD player quit with "corrupted data!" leaving you to
try to
> skip over it with the fast forward before it hangs at the crucial spot.
I'd also prefer a cell phone conversation with hiccups than one that's
interrupted because the packet processor has a bug.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/10/2005 2:12:16 AM
|
|
"Peter Most" <Peter_Most@gmx.de> skrev i meddelandet
news:4399b714$0$27899$9b4e6d93@newsread4.arcor-online.net...
> kanze wrote:
>
>> Part of the problem is that C++ is designed to be statically
>> compiled. It should be possible to define a portable "byte
>> code" with an interpreter for it, but to my knowledge, no one
>> has done so. It would be very difficult for a browser to run
>> code compled for Windows on a PC on my Sparc under Solaris. THe
>> current state of affairs is that to run C++ directly, my browser
>> would have to download the sources, and compile and link them.
>>
> I'm not so familiar with the newer microsoft compilers, but isn't
> the
> managed C++ compiler from Microsoft targeting the CLR?
But that is not ISO C++, but Managed C++ or ECMA C++/CLI. These are
all totally different languages with major syntactic and semantic
differences, just like Java and C#.
> See also:
> http://en.wikipedia.org/wiki/Managed_Extensions_for_C_Plus_Plus
> And if Mono progresses as planned, then it would also run under
> Linux.
Theoretically, yes. Practically, maybe not.
Bo Persson
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bo
|
12/10/2005 2:16:04 AM
|
|
i beleive that a C++ which used the exception behaviour of Java would
be great for the language. I the example mentioned above the ignorant
programmer would not be able to compile his code using the [] operator
unless he caught a "invalid access" exception.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
gerg
|
12/10/2005 2:17:37 AM
|
|
* Andrei Alexandrescu (See Website For Email):
>
> Besides, C++ has no way of implementing "finally", and I found myself
> enjoying to execute code upon scope exit within the context of that
> scope. That feature is not attainable with C++ in any reasonable way.
OK, heated discussion, so the first sentence, "no way", is presumably an
immediate that's-how-I-feel-about-it, and the the second sentence, "[no]
reasonable way", a more intellectual correction.
I think that statement was true just a few years ago, because of lack of
compiler support. Translated from the [no.it.programmering.c++] FAQ,
<url: http://utvikling.com/cppfaq/04/04/02/index.html>, (Norwegian):
"Even if this is no permanent solution, absolutely not recommended,
and not supported by all C++ compilers, here's a program that shows
how to simulate finally:"
(Interestingly you're directly mentioned in that FAQ item! Written
mostly by me! And now I see that all should have been timestamped! :-))
Given that compilers now generally do support this technique, as I see
it the remaining unreasonability must rest with the awkwardness of the
construction, the slight inefficiency, and/or the fragility of relying
on convention: could you elaborate on what you find so unreasonable that
in the heat of the moment you write "no way"?
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/10/2005 2:18:11 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
>
>>The critical applications I've worked on have made
>>extensive use of programming by contract, with non-virtual
>>public functions enforcing the contract before calling virtual
>>private functions -- a technique forbidden in Java, where an
>>"interface" can have no implementation code whatever, and
>>private functions cannot be virtual.
>
>
> That's entirely obscure. The same behavior can be achieved through a ton
> of other techniques.
>
> Abstract classes, dual interfaces, forwarding, package-level hiding,
> inner classes,... come to mind.
what is dual interfaces?
Anyway, all of those things seems like poor hacks. Abstract classes
usually won't work because you don't have multiple inheritance.
> I do think you'd have to explain to me how that is a make-or-break for
> *critical projects*. *Safety*. This discussion is not "Java vs. C++".
> It's "Java is not usable for critical-mission projects". I do like
> destructors, they are a nifty feature, but I fail to see how they make
> or break safety.
when you only have to implement the "finally" once in the destructor,
it makes it much less likely that you'll miss it. Repetition is evil
and finally clauses readily support that.
> Besides, C++ has no way of implementing "finally", and I found myself
> enjoying to execute code upon scope exit within the context of that
> scope. That feature is not attainable with C++ in any reasonable way.
try
{
...
}
catch( ... )
{
finally();
throw;
}
?
-Thorsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thorsten
|
12/10/2005 3:03:06 AM
|
|
David Abrahams wrote:
> "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes:
>>As far as I've known for the longest time, "undefined" often stands for
>>"for the sake of generating the fastest code possible when the program
>>has no error."
>
>
> In this case it means, "on some implementations it may be important
> not to pay for the check, and on some others we don't want to restrict
> the implementation's choice of behavior in case a check fails"
....which is no different from what I said. I thought "Me too" posts are
not recommended by moderators :o).
{Paraphrasing can add clarity which makes it not a 'me too'. Of course
the moderators should have rejected this post as well as 'adding nothing
new' :-) but this moderator thought that a little clarification might be
of general interest. -mod/fwg}
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/10/2005 11:29:04 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Dietmar Kuehl wrote:
>> I'm pretty capable of making sure that I have no out of bounds
>> accesses when using arrays (of whatever form, be it built-in arrays,
>> 'std::vector<T>', 'std::deque<T>', etc.). I don't want any redundant
>> checks for whether I did the right thing. I don't mind them if they
>> come with no extra cost but it isn't my experience that they do.
>
> I think the question is, would you be willing to write an extra
> ".unchecked_at()" to avoid that extra cost?
Absolutely not! But I would be willing to create my own container
replacement which uses 'operator[]()' for an unchecked access if
mandatory range checking in standard containers would cause a
performance problem (big deal: I have them floating around anyway;
well, I haven't implemented deque). The use of the operator is very
important due to at least two reasons:
- I want my code be easy to read. I tend to create expressions which
take up the horizontal capacity of my screen using short names and
I think that expressions broken over multiple lines are just
incomprehensible. Long names would make the situation worse.
... and the reason I have long lines already is normally not that
I'm doing to much stuff on one line but that there is already to
much syntactic clutter which cannot reasonably be avoided (e.g.
'typename' keywords).
- Much of my actual algorithmic code is generic code and non-operator
functions are e.g. not applicable to built-in arrays. Thus, it is a
non-starter to try using different names.
On the other hand, I would also provide checked containers to
relative novices, also using 'operator[]()' but a range checked one.
IMO, creating a custom container isn't a big deal and if I have
specific requirements that is exactly what I just do. I would still
appreciate the freedom for the library implementer to choose which
[default] policy suits the targeted user base best.
>> Even if there are checks, I guess we would choose different
>> implementations for how they deal with programming errors: I want the
>> implementation to "crash" (i.e. write an inspectable post-mortem image
>> and abort); your preference seems to be that an exception is thrown
>> such that you can try to continue (which is, IMO, a futile attempt
>> anyway: if the programmer is incapable of correctly implementing the
>> successful case, how will he be capable of recovering from his own
>> errors at all?).
>
> That's an entirely valid point, and it leads to a very interesting
> discussion: modular exceptions.
Do modular exceptions do stack unwinding like normal exceptions do?
Both possible answers cause situations increasing the problem: if
they don't do normal stack unwinding, they cannot restore partially
changed data structures. If they do normal stack unwinding they run
the risk of corrupting the program's state even more.
> How about defining exceptions such that they cannot be handled but in a
> restricted set of modules? They should pass right through the other
> modules without any chance of being caught, and make it to a few
> carefully designed modules that handle them.
Handle them to what end? To continue with corrupted program state?
I doubt that you can come up with carefully designed modules which
recover from yet unknown programming errors (if the programming
errors were known, it would be easier to fix them than to create a
module recovering from them).
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/10/2005 12:59:17 PM
|
|
gerg wrote:
> i beleive that a C++ which used the exception behaviour of Java would
> be great for the language. I the example mentioned above the ignorant
> programmer would not be able to compile his code using the [] operator
> unless he caught a "invalid access" exception.
>
I disagree. Such a feature of the compiler would reward undisciplined
programming techniques (eg encourage people not to worry about whether
an array index is valid) and add unnecessary penalties for disciplined
programmers. A programmer who *knows* that, by design, his code will
not cause an invalid access would be forced to catch invalid access
exceptions for no reason. This means the compiler would effectively
offer a reward for undisciplined programming techniques.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Rob
|
12/10/2005 1:00:12 PM
|
|
Walter Bright wrote:
> "Branimir Maksimovic" <bmaxa@hotmail.com> wrote in message
> news:dnck3n$n0c$1@domitilla.aioe.org...
> > I completelly agree. It's just I've never have a case that
> > out of bounds error is something else but fatal error.
>
> Consider this:
>
> try
> {
> for (i = 0; 1; i++)
> array[i] = ...;
> }
> catch (ArrayBoundsException)
> {
> }
>
> If array is a large array, that version of the loop will be significantly
> faster than:
>
> for (i = 0; i < array.length; i++)
> array[i] = ...;
>
> because in the latter the array bounds check gets done twice per iteration,
> rather than once. The redundant check can sometimes be eliminated by a more
> advanced data flow analysis optimizer, but not always.
I doubt it would be signficantly faster unless "..." is trivial. This
seems more like an abuse of exception handling than an argument in
favor of checked array indices. In any case, I would think the "safer
is more important than faster" criteria would prefer the second
alternative over the first. It may be slower, but it is easier to read
and understand as correct. The first one requires more thinking, and
all it takes is a spurious out of bounds array index from the "..."
(which could be caused by a bug) to ruin the correctness.
I also don't recall ever having an out of bounds index that wasn't
caused by a bug.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/10/2005 1:00:46 PM
|
|
Walter Bright wrote:
> "Andrei Alexandrescu (See Website For Email)"
> <SeeWebsiteForEmail@moderncppdesign.com> wrote in message
> news:Ir8vF3.pos@beaver.cs.washington.edu...
>
>>The problem I have with the unchecked operator[] is that it is the most
>>convenient to use, and therefore the most used. The default should be
>>safety because most of the time we want safety. Only rarely we need
>>absolute speed, and therefore the unsafe speed should be achievable only
>>with extra syntax.
>
> I agree, this is the sensible approach. Too often in C++, the
> straightforward intuitive way to do something is the least safe and most
> deprecated way. (Virtualness of destructors comes to mind.)
I don't disagree, but let me put it this way: it is a conflict between
having orthogonal features vs having them in the most commonly used way.
No member function is virtual unless explicitly declared so or inherited
from a virtual one, and it's just that a destructor is no exception. A
function's being virtual and its being a destructor are orthogonal and
do not affect each other. It makes the language neater and easier to
explain and understand, as there's one less (special) rule to remember.
On the other hand, in practice we often need the destructor to be
virtual if any member function is virtual; hence the "guideline" such as
"Declare your destructor virtual if any member function is virtual." -
one more to remember.
This may not be the best example, but a const variable's ability to
participate in an integral constant-expression(ICE) is where C++ takes
the other direction: a non-const variable cannot be used as an ICE while
a const variable can. This is a special rule, and a variable's constness
and its ability to participate in an ICE are not orthogonal. It helps
you avoid the dangerous preprocessor macros for constants, but at the
same time adding or removing a const may affect the program in a rather
obscure way and can create tricky corner cases.
I feel that many advantages and disadvantages of C++, as it is, come
from trying to be orthogonal wherever possible. But, which one is
better, is an open question, I think. :)
--
Seungbeom Kim
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Seungbeom
|
12/10/2005 1:02:07 PM
|
|
Alf P. Steinbach wrote:
> * Andrei Alexandrescu (See Website For Email):
>
>>Besides, C++ has no way of implementing "finally", and I found myself
>>enjoying to execute code upon scope exit within the context of that
>>scope. That feature is not attainable with C++ in any reasonable way.
>
>
> OK, heated discussion, so the first sentence, "no way", is presumably an
> immediate that's-how-I-feel-about-it, and the the second sentence, "[no]
> reasonable way", a more intellectual correction.
>
> I think that statement was true just a few years ago, because of lack of
> compiler support. Translated from the [no.it.programmering.c++] FAQ,
> <url: http://utvikling.com/cppfaq/04/04/02/index.html>, (Norwegian):
>
> "Even if this is no permanent solution, absolutely not recommended,
> and not supported by all C++ compilers, here's a program that shows
> how to simulate finally:"
>
> (Interestingly you're directly mentioned in that FAQ item! Written
> mostly by me! And now I see that all should have been timestamped! :-))
I was of course well aware of techniques such as the one mentioned. It's
something that I've been mulling over for the longest time, and
ScopeGuard is a pale incarnation of it.
> Given that compilers now generally do support this technique, as I see
> it the remaining unreasonability must rest with the awkwardness of the
> construction, the slight inefficiency, and/or the fragility of relying
> on convention: could you elaborate on what you find so unreasonable that
> in the heat of the moment you write "no way"?
I said "no way" because of the reasons you mentioned, to which I'd add
the added awkwardness in the presence of loops (break and continue will
not properly execute the simulated finally block unless you add more
cruft to the code).
IMHO the programming landscape would be vastly better off if the
language allowed us to plant code to be executed upon some function's
exit. After many years of intense meditation and sitting in lotus, I
strongly believe that much of destructors' usefulness has a lot to do
with exactly that - they are a hook on a scope termination.
Perl has an interesting module (see
http://search.cpan.org/~abergman/Hook-Scope-0.04/Scope.pm) that allows
you to insert code to be executed upon a scope exit, *within the context
of the caller*. The sheer fact that Perl allows one to implement such a
module raised my opinion of Perl greatly.
Hook::Scope's approach is superior to Java's finally because it doesn't
require a block preceding it and can be planted straight within the
current scope to be executed later. That makes it way more powerful and
vastly simplifies idiomatic usage. For me it was quite a change of view
to realize was that, contrary to their name, "finally" blocks don't
belong to the end of the normal course of action; they must be planted
piecemeal and as soon as the need to finalize occurs in the control
flow. That's why Java's finally (which requires a block preceding it) is
fatally flawed.
I believe three constructs would simplify writing robust code:
1. on_scope_exit { ... code .. }
Executes code upon the current scope's exit. Several on_scope_exit
blocks in a scope are executed LIFO.
2. on_scope_success { ... code ... }
Executes code if the current scope is exited normally (no exception
engendered).
3. on_scope_failure { ... code ... }
Executes code if the current scope is exited via an exception.
With such language constructs, writing robust programs would be much
easier. Programmers can easily change the state of the program and leave
safeguards behind them to ensure things are properly cleaned up in case
something fails down the road. Little RAII classes built just for the
sake of undoing stuff (I've seen "IntDecrementer" in my time...) would
not be necessary anymore. Such a construct is so useful, it makes
ScopeGuard very popular in spite of its awkwardness.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/10/2005 1:06:14 PM
|
|
Thorsten Ottosen wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>kanze wrote:
>
>>>The critical applications I've worked on have made
>>>extensive use of programming by contract, with non-virtual
>>>public functions enforcing the contract before calling virtual
>>>private functions -- a technique forbidden in Java, where an
>>>"interface" can have no implementation code whatever, and
>>>private functions cannot be virtual.
>>
>>
>>That's entirely obscure. The same behavior can be achieved through a ton
>>of other techniques.
>>
>>Abstract classes, dual interfaces, forwarding, package-level hiding,
>>inner classes,... come to mind.
>
> what is dual interfaces?
>
> Anyway, all of those things seems like poor hacks. Abstract classes
> usually won't work because you don't have multiple inheritance.
Dual interfaces mean, there's one interface for client code and one
private interface for implementation code.
So... shall we now say that C++ is safe because it allows multiple
inheritance?!?
> when you only have to implement the "finally" once in the destructor,
> it makes it much less likely that you'll miss it. Repetition is evil
> and finally clauses readily support that.
Destructors don't execute within the context of the current scope.
That's sometimes eminently useful. I want finally (actually
on_scope_exit, on_scope_success, and on_scope_failure) *in addition to*
destructors. They don't compete; they serve different purposes.
>>Besides, C++ has no way of implementing "finally", and I found myself
>>enjoying to execute code upon scope exit within the context of that
>>scope. That feature is not attainable with C++ in any reasonable way.
>
> try
> {
> ...
> }
> catch( ... )
> {
> finally();
> throw;
> }
>
> ?
:o}
Try the following inside the try block:
0. fall through the end of the block
1. return
2. break
3. continue
4. goto (ouch!)
None of these fundamental control flow means will execute finally().
A few months ago I had embarked on a macro system for C++ that would
entirely replace the built-in macro system and would be powerful enough
to handle things like typesafe variadic functions, automatic type,
function, and value reification (a la templates, just way cleaner and
more general :o)), user-defined constants, "smart" enums, all sorts of
code transformations, the works. I was enthused! After a few iterations
through the design, I gave up in frustration because I found no simple,
clean AST transformation that would allow implementing on_scope_exit,
on_scope_success, and on_scope_failure.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/10/2005 1:08:12 PM
|
|
Non-virtual destructors, IMO, has a strong meaning. Which helps to
self-document code.
When one sees a class with a virtual destructor it knows it is using
polymorphism or alike with this,
but one sees a class without any virtual functions, he must really
think if inheriting from it is really
a good idea. IMHO, it really helps designing software in C++.
best regards,
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Felipe
|
12/10/2005 1:09:40 PM
|
|
On 2005-12-10 02:10, Walter Bright wrote:
> "Branimir Maksimovic" <bmaxa@hotmail.com> wrote:
:
>> I completelly agree. It's just I've never have a case that
>> out of bounds error is something else but fatal error.
>
> Consider this:
>
> try
> {
> for (i = 0; 1; i++)
> array[i] = ...;
> }
> catch (ArrayBoundsException)
> {
> }
>
> If array is a large array, that version of the loop will be
> significantly faster than:
>
> for (i = 0; i < array.length; i++)
> array[i] = ...;
>
> because in the latter the array bounds check gets done twice per
> iteration, rather than once. The redundant check can sometimes be
> eliminated by a more advanced data flow analysis optimizer, but not
> always.
It's fundamentally wrong to (have to) write such code. Programming
languages should cater to the programmer, not the other way round.
Also, this has the usual problem of selective exception catching:
Some other code executed within the loop (i.e. within the "..." or
the assignment operator) could also happen to access arrays (if it
doesn't now, some future program modifications could create such a
situation), and (due to some bug) also throw an ArrayBoundsException,
and you don't want to catch those here. A robust version of this code
would have to be written as:
for (i = 0;; i++)
{
ElemType * e;
try
{
e = &array[i];
}
catch (ArrayBoundsException)
{
break;
}
*e = ...;
}
It would be much better to be able to write
for (i = 0; i < array.length; i++)
{
array.unchecked_access[i] = ...;
}
or even better
for (i = 0; i < array.length; i++)
{
assume(i < array.length); // hint to the optimizer
array[i] = ...;
}
But even this should only be very rarely necessary.
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/10/2005 1:10:00 PM
|
|
In article <1134136164.940627.219400@g47g2000cwa.googlegroups.com>,
kanze <kanze@gabi-soft.fr> writes
>In critical applications, it is an absolute rule that in the
>slightest doubt concerning the correction of the program, it
>must crash. Stumbling on with possibly incorrect data is not
>considered an alternative.
How critical is an operating system? Surely such a program should never
crash regardless of how it is abused it needs to recover and continue.
Ideally it should discontinue the process that caused the problem whilst
continuing with everything else. Operating systems that fail to meet
this criterion are not normally considered to be satisfactory (though
most of us have had to endure using such systems.)
--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Francis
|
12/10/2005 2:08:40 PM
|
|
In article <Ir8y4A.rCv@beaver.cs.washington.edu>, "Andrei Alexandrescu
(See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes
>> I meant to reverse the semantics i.e.:
>> 1) operator[] checks the index and throws an out_of_range exception (as at()
>> is currently doing).
>> 2) at() isn't checking anything and maybe crashes (as operator[] is
>> currently doing).
>
>I think that's a more sensible choice than tha status quo. I also think
>the suggestion will not make it into the standard :o).
You can absolutely bet on that. Unfortunately as one of the functions is
an operator we do not even have the option of providing an extra
parameter that defaults to the current status. In simple terms we will
have to improve our education of C++ programmers by warning them that
STL implementations of operator[] have the same problem that operator[]
has for raw arrays.
Now that last sentence may explain why we have the current requirements,
in general user defined operators should behave like built in ones.
--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Francis
|
12/10/2005 2:09:01 PM
|
|
In article <3vufgoF17u942U1@individual.net>, Bo Persson <bop@gmb.dk>
writes
>But that is not ISO C++, but Managed C++ or ECMA C++/CLI. These are
>all totally different languages with major syntactic and semantic
>differences, just like Java and C#.
Which is one reason that the BSI (UK National Body) C++ Panel is deeply
unhappy with the name being applied to this new language. C++ is the
name of an ISO Standard language that is maintained by SC22/WG21. We
should not tolerate other Standards bodies (or worse still, ISO itself
via the fast track process) confusing people with 'C++/CLI. The owners
of the Java marque went bananas (and, IIRC, took legal action) over
those that wanted to use Java as the name for some extended version of
the language.
It has been difficult enough over the years to educate people that
'Visual C++' is just an implementation of C++ (albeit one with many
extensions) and not a distinct language. What hope have we of now
reversing the process and teaching people that 'Managed C++' or
'C++/CLI' is not C++.
Because C++ was very nearly a superset of C we got away with calling it
C++ rather than, for example, D but the differences between C++/CLI and
C++ are rather more extensive and the name is not indicative of that.
--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Francis
|
12/10/2005 2:09:22 PM
|
|
Bo Persson wrote:
>> I'm not so familiar with the newer microsoft compilers, but isn't
>> the
>> managed C++ compiler from Microsoft targeting the CLR?
>
> But that is not ISO C++, but Managed C++ or ECMA C++/CLI. These are
> all totally different languages with major syntactic and semantic
> differences, just like Java and C#.
>
>> See also:
>> http://en.wikipedia.org/wiki/Managed_Extensions_for_C_Plus_Plus
>
As I said above, I'm not very familiar with it, but from what I've read it's
pretty much normal C++ with some minor changes. But it's not fully
supporting ISO C++ or even certified by ISO, so in this regard you are
right.
Nonetheless it is a C++ compiler which targets a kind of virtual machine and
that's what I wanted to show.
>
>
>> And if Mono progresses as planned, then it would also run under
>> Linux.
>
> Theoretically, yes. Practically, maybe not.
>
Well I've played with C#/Mono a little bit and I think they are not to far
away from fully supporting the CLR. But this is of course a different
discussion and probably a little off topic. But it's worth observing how
C++ could evolve to reach new platforms and survive against allegedly more
modern languages like Java/C#.
Wouldn't it be very interesting if there were a C++ compiler which targets
the JVM, like Jython? This would open up a now world of possibilities like
uses SWING from C++ and hence a standard GUI.
Does such a compiler exist? I couldn't find one, so if anybody knows more
please post!
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/10/2005 2:11:26 PM
|
|
kanze wrote:
> In critical applications, it is an absolute rule that in the
> slightest doubt concerning the correction of the program, it
> must crash. Stumbling on with possibly incorrect data is not
> considered an alternative.
The rule is that it must enter a safe state. A crash can be a safe
state, but sometimes it is not.
--
mail1dotstofanetdotdk
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bjorn
|
12/10/2005 9:24:29 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Branimir Maksimovic wrote:
>>I am not worried about out of bounds errors. It's ones that
>>are not out of bounds, that point to incorrect data worries
>>me.
> I think the errors that we should be worried about are
> nonlocal errors caused by obliterating data of a different
> type sitting innocently next to data manipulated by buggy
> code.
> Memory protection has solved that at the OS level. We should
> strive for solving that at the language level, too.
Intel actually provided the mechanisms to do that at one time;
they might still be present in the current 32 bit architecture,
but as far as I know, no one uses them. (They do have a
negative impact on runtime, when each function call and each
memory access loads a new segment register, with its own set of
protections.)
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:26:18 PM
|
|
Dietmar Kuehl wrote:
> Peter Most wrote:
>>kanze wrote:
>>>Calling operator[] with an out of bounds index is undefined
>>>behavior precisely so that an implementation *can* check the
>>>index, and do whatever is appropriate *on* *that* *platform*
>>>for a programming error. On Unix, I expect a core dump, but
>>>from what I understand, this is not the usual case under
>>>Windows.
>>Wouldn't it be easier to simply define that operator[] has to
>>check the index, then it would be one less undefined behavior?
> I'm pretty capable of making sure that I have no out of bounds
> accesses when using arrays (of whatever form, be it built-in
> arrays, 'std::vector<T>', 'std::deque<T>', etc.). I don't want
> any redundant checks for whether I did the right thing. I
> don't mind them if they come with no extra cost but it isn't
> my experience that they do.
Well, the additional cost is only an issue if the checks are in
a tight loop. And the statistics I've seen for languages which
require such checks is that the compiler is almost always able
to hoist them out of the loop. (Of course, in such languages,
the array type is always built in, and the compiler knows
precisely why the array bounds check is there, and what it is
checking. In cases concerning library types, it would take a
lot more analysis on the part of the compiler. But I think that
it is within reach of the better optimizers today.)
And of course, unless you're a much better coder than I am, such
checks aren't necessarily redundant during the development
phase.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:27:02 PM
|
|
gerg wrote:
> i beleive that a C++ which used the exception behaviour of
> Java would be great for the language.
You mean you'ld like for it to become impossible to write a
correct program? Exception safety can only be implemented if
there are certain operations which are guaranteed not to raise
an exception.
> I the example mentioned above the ignorant programmer would
> not be able to compile his code using the [] operator unless
> he caught a "invalid access" exception.
But Java doesn't require this. Java seems to have adopted the
worst of both worlds, by doing some static analsysis, so you pay
for it, and then throwing in a lot of exceptions which mean that
you cannot count on it -- in sum, you don't get what you've paid
for.
I would have preferred that C++ require a static analysis. But
such analysis is only useful if it includes all exceptions.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:27:44 PM
|
|
Peter Most wrote:
> kanze wrote:
>>>I think only small, short running programs can "live" with a
>>>crash. But all other programs simply can not accept a crash.
>>Realistically, all programs must be able to live with a crash.
>>At least on my system, dereferencing an out of bounds pointer
>>will cause a crash, as will accessing through a mis-aligned
>>pointer. And there's really no way to catch the first of
>>these.
> The application I was working on was multithreaded, where the
> threads where quite independent from each other, meaning if
> one thread crashes then the program could and should continue
> to work with the other threads. But the usual behavior for
> threads makes this very difficult, because if one thread
> crashes then the complete process is crashing. In this
> scenario I prefer a thrown exception which I can catch and log
> in a toplevel handler and then let the tread die gracefully so
> that the other threads can still continue.
Did the threads share data? If not, then why threads, and not
separate processes ? If so, how do you know that the shared
data is not corrupted?
>>In critical applications, it is an absolute rule that in the
>>slightest doubt concerning the correction of the program, it
>>must crash. Stumbling on with possibly incorrect data is not
>>considered an alternative.
>>The same thing holds for any number of other applications.
>>Most of the time, wrong answers are worse than no answers.
>>There are clearly exceptions: the most obvious is a demo
>>program for an exposition, where a crash is highly visible,
>>and no one is going to use the results, or even verify that
>>they are correct. But any time the results are to be used for
>>anything significant, it is important that they be correct,
>>and crashing is preferable to wrong results.
> That's a point I still don't quite understand. If I access the
> vector with a wrong index and an exception is thrown, how can
> it be that the program is continuing with wrong data?
Where did the index come from? Data, I presume. If the data
wasn't corrupted, the index wouldn't be out of bounds.
> This would only be possible if the developer deliberately and
> irresponsible catches the exception and tries to muddle trough
> with some made up and hence wrong data. Are you talking about
> such a scenario?
It's a frequent scenario. But you don't have to be so extreme.
What do your destructors do? You're executing unnecessary code
in an unsure environment.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:30:06 PM
|
|
Francis Glassborow wrote:
> In article
> <1134136164.940627.219400@g47g2000cwa.googlegroups.com>, kanze
> <kanze@gabi-soft.fr> writes
>>In critical applications, it is an absolute rule that in the
>>slightest doubt concerning the correction of the program, it
>>must crash. Stumbling on with possibly incorrect data is not
>>considered an alternative.
> How critical is an operating system?
Depends on the machine.
> Surely such a program should never crash regardless of how it
> is abused it needs to recover and continue.
Regardless of how it is abused. That's the key. We're not
talking about external abuse here; we're talking about internal
errors. And if an operating system happens to detect an
internal error, it should crash -- all of the ones I use do.
> Ideally it should discontinue the process that caused the
> problem whilst continuing with everything else. Operating
> systems that fail to meet this criterion are not normally
> considered to be satisfactory (though most of us have had to
> endure using such systems.)
But that's irrelevant to the question at hand. The operating
system validates all parameters to system calls, and terminates
the process if any of them are wrong. The operating system also
does some internal consistency checks... and crashes if they
fail. And we're talking here about internal consistency checks
failing, not about some "external" user passing a bad parameter.
(Although appropriate for processes, I do believe that
terminating a human user because he passed a bad parameter would
be a bit extreme. Although I have heard of at least one program
that did just that.)
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:33:39 PM
|
|
Walter Bright wrote:
> "Andrei Alexandrescu (See Website For Email)"
> <SeeWebsiteForEmail@moderncppdesign.com> wrote in message
> news:Ir8vF3.pos@beaver.cs.washington.edu...
>>The problem I have with the unchecked operator[] is that it is
>>the most convenient to use, and therefore the most used. The
>>default should be safety because most of the time we want
>>safety. Only rarely we need absolute speed, and therefore the
>>unsafe speed should be achievable only with extra syntax.
> I agree, this is the sensible approach. Too often in C++, the
> straightforward intuitive way to do something is the least
> safe and most deprecated way. (Virtualness of destructors
> comes to mind.)
The problem, if it is a problem, is that C++ caters to many
different types of users, and allows implementations to vary in
order to do so. Thus, there is no requirement that operator[]
bounds check and abort, because for some users (probably a very
small minority), this would represent an unacceptable runtime
overhead. On the other hand, it is certainly a legal behavior,
and one that I would expect from a quality implementation.
There are, of course, other cases where you are right -- some of
them, of course, due to issues of C compatibility, and others
just historical. On the other hand, from what I've seen of
other languages which try to "correct" these errors, they end up
making things worse.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:34:20 PM
|
|
Bob Bell wrote:
> Walter Bright wrote:
>>"Branimir Maksimovic" <bmaxa@hotmail.com> wrote in message
>>news:dnck3n$n0c$1@domitilla.aioe.org...
>>>I completelly agree. It's just I've never have a case that
>>>out of bounds error is something else but fatal error.
>>Consider this:
>>try
>>{
>> for (i = 0; 1; i++)
>> array[i] = ...;
>>}
>>catch (ArrayBoundsException)
>>{
>>}
>>If array is a large array, that version of the loop will be significantly
>>faster than:
>> for (i = 0; i < array.length; i++)
>> array[i] = ...;
>>because in the latter the array bounds check gets done twice
>>per iteration, rather than once. The redundant check can
>>sometimes be eliminated by a more advanced data flow analysis
>>optimizer, but not always.
> I doubt it would be signficantly faster unless "..." is trivial.
I doubt it would be faster at all unless the compiler is
particularly stupid. Any decent compiler should hoist array
bounds checking out of the loop.
> This seems more like an abuse of exception handling than an
> argument in favor of checked array indices. In any case, I
> would think the "safer is more important than faster" criteria
> would prefer the second alternative over the first. It may be
> slower, but it is easier to read and understand as correct.
> The first one requires more thinking, and all it takes is a
> spurious out of bounds array index from the "..." (which could
> be caused by a bug) to ruin the correctness.
> I also don't recall ever having an out of bounds index that
> wasn't caused by a bug.
Well, one could argue that this is because you program in C/C++,
where it is a bug:-). If an array bounds error had defined
behavior, you could presumably write code which depended on it.
My personal feeling is that this would be a good trick for
obfuscation, and not much else. But of course, that's not based
on concrete experience.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/10/2005 9:35:01 PM
|
|
On 2005-12-10 14:11, Peter Most wrote:
:
> Wouldn't it be very interesting if there were a C++ compiler which
> targets the JVM, like Jython? This would open up a now world of
> possibilities like uses SWING from C++ and hence a standard GUI.
> Does such a compiler exist? I couldn't find one, so if anybody knows
> more please post!
There is at least one C-to-JVM compiler (AMPC, http://www.axiomsol.com/),
and there are C++-to-C compilers, so in theory you could combine them to
get a C++-to-JVM compiler. But this won't get you the mapping of class
types and exceptions you're probably thinking of.
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/10/2005 9:36:25 PM
|
|
"Felipe Magno de Almeida" <felipe.m.almeida@gmail.com> wrote in message
news:1134206624.321703.180790@f14g2000cwb.googlegroups.com...
> Non-virtual destructors, IMO, has a strong meaning. Which helps to
> self-document code.
> When one sees a class with a virtual destructor it knows it is using
> polymorphism or alike with this,
> but one sees a class without any virtual functions, he must really
> think if inheriting from it is really
> a good idea. IMHO, it really helps designing software in C++.
The problem with that approach is that a non-virtual destructor *implies*
one is not meant to derive from the class, but it is implication only. For a
code reviewer, a language guarantee of such would be preferable, if that is
indeed the intent of the original programmer rather than a (unfortunately
common) mistake.
In the D programming language, this idea is approached from the opposite
direction. Declaring a class to be 'final' means that one cannot derive from
it, and all its members become non-virtual. It's less likely that one types
in 'final' by accident than one forgetting to add 'virtual'. Furthermore,
since the language offers a guarantee that a 'final' class cannot be derived
from, I view that as more reliably self-documenting than an implication by
omission.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 1:10:56 AM
|
|
Francis Glassborow wrote:
> In article <Ir8y4A.rCv@beaver.cs.washington.edu>, "Andrei Alexandrescu
> (See Website For Email)" <SeeWebsiteForEmail@moderncppdesign.com> writes
>
>>>I meant to reverse the semantics i.e.:
>>>1) operator[] checks the index and throws an out_of_range exception (as at()
>>>is currently doing).
>>>2) at() isn't checking anything and maybe crashes (as operator[] is
>>>currently doing).
>>
>>I think that's a more sensible choice than tha status quo. I also think
>>the suggestion will not make it into the standard :o).
>
>
> You can absolutely bet on that. Unfortunately as one of the functions is
> an operator we do not even have the option of providing an extra
> parameter that defaults to the current status. In simple terms we will
> have to improve our education of C++ programmers by warning them that
> STL implementations of operator[] have the same problem that operator[]
> has for raw arrays.
>
> Now that last sentence may explain why we have the current requirements,
> in general user defined operators should behave like built in ones.
They would behave the same, except when the built in operator[] would
not behave in any defined way, in which case it's fine to do whatever -
including some defined behavior. "Undefined" includes "something in
particular" :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 1:11:17 AM
|
|
"Seungbeom Kim" <musiphil@bawi.org> wrote in message
news:dndmqj$epi$1@news.Stanford.EDU...
> Walter Bright wrote:
> > I agree, this is the sensible approach. Too often in C++, the
> > straightforward intuitive way to do something is the least safe and most
> > deprecated way. (Virtualness of destructors comes to mind.)
>
> I don't disagree, but let me put it this way: it is a conflict between
> having orthogonal features vs having them in the most commonly used way.
>
> No member function is virtual unless explicitly declared so or inherited
> from a virtual one, and it's just that a destructor is no exception. A
> function's being virtual and its being a destructor are orthogonal and
> do not affect each other. It makes the language neater and easier to
> explain and understand, as there's one less (special) rule to remember.
> On the other hand, in practice we often need the destructor to be
> virtual if any member function is virtual; hence the "guideline" such as
> "Declare your destructor virtual if any member function is virtual." -
> one more to remember.
It's possible to look at rules from the other direction: all member
functions are virtual (including destructors) unless specifically marked
otherwise (such as with a 'final' attribute). A further refinement of this
idea is that a derived class cannot override a 'final' member function in a
base class, and a final attribute for the whole class makes all its member
functions final.
I believe this solution is superior because the program will still work
correctly even if all the 'final's are omitted. Adding in the 'final's would
be an optimization. This fits in neatly with the idea that the
straightforward, intuitive way is the correct way, and that the more
advanced methods are available for the more advanced programmer who wishes
to tune the code. Furthermore, some measure of safety against misuse is
there, as one cannot override a non-virtual method.
BTW, this is how virtualness is done in the D programming language.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 1:12:05 AM
|
|
"Bob Bell" <belvis@pacbell.net> wrote in message
news:1134189835.981497.296380@g44g2000cwa.googlegroups.com...
> Walter Bright wrote:
> > Consider this:
> >
> > try
> > {
> > for (i = 0; 1; i++)
> > array[i] = ...;
> > }
> > catch (ArrayBoundsException)
> > {
> > }
> >
> > If array is a large array, that version of the loop will be
significantly
> > faster than:
> >
> > for (i = 0; i < array.length; i++)
> > array[i] = ...;
> >
> > because in the latter the array bounds check gets done twice per
iteration,
> > rather than once. The redundant check can sometimes be eliminated by a
more
> > advanced data flow analysis optimizer, but not always.
>
> I doubt it would be signficantly faster unless "..." is trivial.
True, but often it is the trivial loops one needs to be very fast.
> This
> seems more like an abuse of exception handling than an argument in
> favor of checked array indices.
I would say it's a different style of programming, rather than abuse,
presuming one is using a language that guarantees array bounds checking
(such as Java). (Although D offers array bounds checking, one can turn it
off, so this would not be an acceptable D programming style.)
> In any case, I would think the "safer
> is more important than faster" criteria would prefer the second
> alternative over the first.
In Java, it is not more or less safe, it would be a matter of style.
> It may be slower, but it is easier to read
> and understand as correct. The first one requires more thinking,
If it becomes established as an idiom, it won't.
> and
> all it takes is a spurious out of bounds array index from the "..."
> (which could be caused by a bug) to ruin the correctness.
>
> I also don't recall ever having an out of bounds index that wasn't
> caused by a bug.
I've encountered Java programs which relied on this feature of Java. Those
programs were not incorrect. I agree it's a little jarring at first, as the
big "warning, will robinson!" flag starts waving in one's brain, but I
imagine one could rapidly become accustomed to it.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 1:12:53 AM
|
|
"Niklas Matthies" <usenet-nospam@nmhq.net> wrote in message
news:slrndplbsc.2npj.usenet-nospam@nmhq.net...
> It's fundamentally wrong to (have to) write such code.
True, but I don't see that anyone said one must write such.
> Programming languages should cater to the programmer, not the other way
round.
True as well, as that is the whole point of having a programming language,
but often there is no consensus of what that means!
> Also, this has the usual problem of selective exception catching:
> Some other code executed within the loop (i.e. within the "..." or
> the assignment operator) could also happen to access arrays (if it
> doesn't now, some future program modifications could create such a
> situation), and (due to some bug) also throw an ArrayBoundsException,
> and you don't want to catch those here.
You're quite right about that.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 1:13:15 AM
|
|
gerg wrote:
> i beleive that a C++ which used the exception behaviour of Java would
> be great for the language. I the example mentioned above the ignorant
> programmer would not be able to compile his code using the [] operator
> unless he caught a "invalid access" exception.
So one example of summing up a vector of integers could/should look like
this?:
int sum = 0;
try {
for (std::vector<int>::size_t i = 0; i != v.size(); ++i)
sum += v[i];
} catch ( const std::invalid_access& )
{
std::cerr << "ups." << std::endl;
}
ending with 2 * v.size() bound checks plus a pointless exception handler.
Fortunately there are still iterators and algorithms.
regards
Torsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Torsten
|
12/11/2005 1:13:36 AM
|
|
Bob Bell wrote:
> I also don't recall ever having an out of bounds index that wasn't
> caused by a bug.
Honest, me neither. I do recall, however, having spent lots of time on
soft errors caused by out of bounds access, versus finding the problem
with extreme ease when bounds checking was in vigor. How about you? How
about others?
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 1:14:36 AM
|
|
Seungbeom Kim wrote:
> Walter Bright wrote:
>
>>"Andrei Alexandrescu (See Website For Email)"
>><SeeWebsiteForEmail@moderncppdesign.com> wrote in message
>>news:Ir8vF3.pos@beaver.cs.washington.edu...
>>
>>
>>>The problem I have with the unchecked operator[] is that it is the most
>>>convenient to use, and therefore the most used. The default should be
>>>safety because most of the time we want safety. Only rarely we need
>>>absolute speed, and therefore the unsafe speed should be achievable only
>>>with extra syntax.
>>
>>I agree, this is the sensible approach. Too often in C++, the
>>straightforward intuitive way to do something is the least safe and most
>>deprecated way. (Virtualness of destructors comes to mind.)
>
>
> I don't disagree, but let me put it this way: it is a conflict between
> having orthogonal features vs having them in the most commonly used way.
Maybe sometimes, but there are too many examples where orthogonality has
nothing to do with it. E.g., all variables should be initialized by
default, unless the programmer asks they are not.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 1:14:58 AM
|
|
Dietmar Kuehl wrote:
> On the other hand, I would also provide checked containers to
> relative novices, also using 'operator[]()' but a range checked one.
> IMO, creating a custom container isn't a big deal and if I have
> specific requirements that is exactly what I just do. I would still
> appreciate the freedom for the library implementer to choose which
> [default] policy suits the targeted user base best.
How about the standard library? Should it just leave operator[] with
undefined behavior and wash its hands?
> Do modular exceptions do stack unwinding like normal exceptions do?
> Both possible answers cause situations increasing the problem: if
> they don't do normal stack unwinding, they cannot restore partially
> changed data structures. If they do normal stack unwinding they run
> the risk of corrupting the program's state even more.
I don't know. I do think, however, that the risk is low. Destructors
tend to clean modular data structures, and less often affect global
state. I know you can come with examples to the contrary, and then I can
come with examples to the contrary of the contrary. Absent hard data,
we'll reach a diplomatic impasse :o).
>>How about defining exceptions such that they cannot be handled but in a
>>restricted set of modules? They should pass right through the other
>>modules without any chance of being caught, and make it to a few
>>carefully designed modules that handle them.
>
>
> Handle them to what end? To continue with corrupted program state?
> I doubt that you can come up with carefully designed modules which
> recover from yet unknown programming errors (if the programming
> errors were known, it would be easier to fix them than to create a
> module recovering from them).
That's not true. I know of systems that must continue execution with a
known error for various reasons. For example, the fix + deployment takes
a long time, is impractical, or impossible (embedded applications). Or
because the application uses multiple, relatively independent threads -
a corrupt thread does not necessarily corrupt the state of the entire
application.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 1:15:19 AM
|
|
gerg wrote:
> i beleive that a C++ which used the exception behaviour of Java would
> be great for the language. I the example mentioned above the ignorant
> programmer would not be able to compile his code using the [] operator
> unless he caught a "invalid access" exception.
Java's behavior with respect to exception specifications is inherently
broken. So is C++'s but the impact in C++ is fortunately not at all
that severe. Essentially, the problem is that exception specifications
don't mix at all with generic code (here "generic" is used in the
sense that any form of polymorphism, not only static polymorphism, is
used): an interface (in the classical meaning of "interface" not Java's
meaning) accepting polymorphic objects cannot know what kind of
exceptions may be thrown from these objects. However, the user of the
interface passing in the polymorphic object can know. Any restrictive
exception specification does no good but is harmfully getting in the
way.
Also, the ignorant programmer would just declare the function to
throw the base exception class and would not care about specific
forms of an exception. In the light of using polymorphic entities,
the same would be done by the well-educated programmer, although
due to very different reasons than those of the ignorants...
Essentially, exception specifications, possibly with the exception
of a no-throw specification which can be used by the optimizer, are
a bad idea which is best eliminated from those languages which got
stuck with them. If this is not an option, specifying the most
generic exception is the best thing to do. Of course, this defeats
the original purpose of exception specifications but the original
purpose cannot be delivered anyway.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/11/2005 12:51:44 PM
|
|
Walter Bright wrote:
> try
> {
> for (i = 0; 1; i++)
> array[i] = ...;
> }
> catch (ArrayBoundsException)
> {
> }
>
> If array is a large array, that version of the loop will be significantly
> faster than:
>
> for (i = 0; i < array.length; i++)
> array[i] = ...;
>
> because in the latter the array bounds check gets done twice per
> iteration, rather than once. The redundant check can sometimes be
> eliminated by a more advanced data flow analysis optimizer, but not
> always.
I consider this to be an excellent example of not bounds checking
the array access. I this case the natural looking loop works fastest.
Well, the other loop does not work at all but I don't consider this
to be a problem.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/11/2005 12:53:01 PM
|
|
On 10 Dec 2005 16:33:39 -0500, James Kanze <kanze@none.news.free.fr>
wrote:
>> How critical is an operating system?
>
>Depends on the machine.
Absolutely. An IBM AS/400, for example, can run several different
operating systems concurrently, each in their own virtual machine, but
the *real* OS is never touched by client code.
--
Bob Hairgrove
NoSpamPlease@Home.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/11/2005 12:53:58 PM
|
|
Peter Most wrote:
> The application I was working on was multithreaded, where the threads where
> quite independent from each other, meaning if one thread crashes then the
> program could and should continue to work with the other threads. But the
> usual behavior for threads makes this very difficult, because if one thread
> crashes then the complete process is crashing. In this scenario I prefer a
> thrown exception which I can catch and log in a toplevel handler and then
> let the tread die gracefully so that the other threads can still continue.
Usually the threads share all the same address space. If the threads are
really that independent you should use processes without shared memory
because on modern architectures the hardware will guaranty that on
process can not interfere with an other.
> That's a point I still don't quite understand. If I access the vector with a
> wrong index and an exception is thrown, how can it be that the program is
> continuing with wrong data?
Obviously the index is already wrong. So what is the correct value and
was it the only wrong data?
regards
Torsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Torsten
|
12/11/2005 12:54:35 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
>
> The problem I have with the unchecked operator[] is that it is the most
> convenient to use, and therefore the most used. The default should be
> safety because most of the time we want safety. Only rarely we need
> absolute speed, and therefore the unsafe speed should be achievable only
> with extra syntax.
While I agree with the sentiment, this would create an inconsistency
between vector and array usage, and inconsistencies can be quite vexing
when combined with template code. Though I suppose one could make the
argument that some checking beats none at all.
Sean
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sean
|
12/11/2005 12:56:53 PM
|
|
* Andrei Alexandrescu:
> * Alf P. Steinbach:
> >
> > compiler support. Translated from the [no.it.programmering.c++] FAQ,
> > <url: http://utvikling.com/cppfaq/04/04/02/index.html>, (Norwegian):
> >
> > "Even if this is no permanent solution, absolutely not recommended,
> > and not supported by all C++ compilers, here's a program that shows
> > how to simulate finally:"
> >
> > (Interestingly you're directly mentioned in that FAQ item! Written
> > mostly by me! And now I see that all should have been timestamped! :-))
>
> I was of course well aware of techniques such as the one mentioned. It's
> something that I've been mulling over for the longest time, and
> ScopeGuard is a pale incarnation of it.
>
> > Given that compilers now generally do support this technique, as I see
> > it the remaining unreasonability must rest with the awkwardness of the
> > construction, the slight inefficiency, and/or the fragility of relying
> > on convention: could you elaborate on what you find so unreasonable that
> > in the heat of the moment you write "no way"?
>
> I said "no way" because of the reasons you mentioned, to which I'd add
> the added awkwardness in the presence of loops (break and continue will
> not properly execute the simulated finally block unless you add more
> cruft to the code).
That's just the relying on convention. What would otherwise be the
function (or block) body, sans the declarations shared with the
simulated finally block, must always be a SESE block (heh ;-)) ending
with a throw. Inside that SESE block loops behave as they always have.
But now that I have to actually formulate it in words, I see one
restriction:
The technique is unable to execute the simulated finally block when one
of those shared declarations causes an exception. Which is pretty
restrictive. But on the other hand also good: allowing such reference
would enable the simulated finally block to refer to not yet constructed
variables.
[snip]
> I believe three constructs would simplify writing robust code:
>
> 1. on_scope_exit { ... code .. }
>
> Executes code upon the current scope's exit. Several on_scope_exit
> blocks in a scope are executed LIFO.
>
> 2. on_scope_success { ... code ... }
>
> Executes code if the current scope is exited normally (no exception
> engendered).
>
> 3. on_scope_failure { ... code ... }
>
> Executes code if the current scope is exited via an exception.
>
> With such language constructs, writing robust programs would be much
> easier.
So, that brings back to life the discussion on how to _detect stack
unwinding_ in a destructor...
Are we any closer on that?
Or, do you now think such constructs absolutely need direct language
support, and can't be build on top of the destructor mechanism, i.e.
that access to shared declarations in 'code' is critical?
And if the latter, aren't we then up against the possibily of referring
to not yet constructed variables?
That thing that's not supported by the simulated finally block?
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/11/2005 12:57:47 PM
|
|
"James Kanze" <kanze@none.news.free.fr> wrote in message
news:439b051e$0$29332$626a14ce@news.free.fr...
> I doubt it would be faster at all unless the compiler is
> particularly stupid. Any decent compiler should hoist array
> bounds checking out of the loop.
1) This is not a trivial optimization to determine if such yields an
equivalent program or not. It doesn't fall out of the usual data flow
analysis algorithms, so I take exception to saying a compiler would be
"particularly stupid" to not implement it.
2) If any functions are called in the loop body, the optimization can't be
done, as the functions may alter the array size.
3) Array bounds check hoisting wouldn't be done anyway. What is done, is to
examine all possible values of the array index and see if they can be proved
to all lie within the array. Then the array bounds check can be removed.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 12:58:50 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Bob Bell wrote:
> > I also don't recall ever having an out of bounds index that wasn't
> > caused by a bug.
>
> Honest, me neither. I do recall, however, having spent lots of time on
> soft errors caused by out of bounds access, versus finding the problem
> with extreme ease when bounds checking was in vigor. How about you? How
> about others?
Sure, that's why I favor a "stop the program and help me debug it"
behavior, such as a crash plus core dump. I would love it if such
behavior was somehow mandated by the standard, but I am also willing to
live with it as a QOI issue. I am not in favor of throwing exceptions
for these kinds of conditions because not only do they not help me
debug the program, they can make the problem worse by masking the bug.
Of course, one could (and it appears that some have) argue that if out
of bounds conditions were implemented to throw errors, then out of
bounds conditions would be expected and no longer considered bugs.
However, it's hard for me to wrap my head around that (perhaps, as
Walter suggested, because I program in C++) -- in such an "out of
bounds always throws" world it seems to me that sometimes out of bounds
conditions will arise intentionally because I write code that counts on
the behavior, but sometimes these conditions will arise
unintentionally, because I made a mistake. So sometimes, the exception
will be the result of a bug, yet nothing helps me discover that. This
just seems like a different kind of soft error.
It seems simpler to regard out of bounds conditions as errors --
provided the implementation detects them.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/11/2005 12:59:11 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> I believe three constructs would simplify writing robust code:
>
> 1. on_scope_exit { ... code .. }
>
> Executes code upon the current scope's exit. Several on_scope_exit
> blocks in a scope are executed LIFO.
>
> 2. on_scope_success { ... code ... }
>
> Executes code if the current scope is exited normally (no exception
> engendered).
>
> 3. on_scope_failure { ... code ... }
>
> Executes code if the current scope is exited via an exception.
>
> With such language constructs, writing robust programs would be much
> easier. Programmers can easily change the state of the program and leave
> safeguards behind them to ensure things are properly cleaned up in case
> something fails down the road. Little RAII classes built just for the
> sake of undoing stuff (I've seen "IntDecrementer" in my time...) would
> not be necessary anymore. Such a construct is so useful, it makes
> ScopeGuard very popular in spite of its awkwardness.
[second try...]
imho in the context of safe programming the lack of a finally statement
should be considered as advantage of c++ to languages like java. raii
is inherent in c++ design and a finally statement encourages
programming orthogonal to that concept, especially beginners for whom
finally is a concept much easier to understand upfront. why allow for
circumventing the 'native' c++ way of handling resources with finally
for those few cases where it is just awkward to work around without it?
afair bjarne stroustrup explains the decision to omit finally in c++
and the benefits of raii in 'the design and evolution of c++'.
reintroducing finally or variations of it now for the discussed reasons
seems contraproductive. why not tackle the problems of current
solutions like scopeguards by improving the language to fit such
concepts?
for example i could imagine that my favourite missing feature in c++,
namely lambda definitions with automatic scope variable forwarding
would allow to implement very convenient scope guards. unfortunately
such a feature seems to stay being my favorite _missing_ feature for a
long time still... :o)
-- peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
peter
|
12/11/2005 2:19:17 PM
|
|
Bjorn Reese wrote:
> kanze wrote:
>>In critical applications, it is an absolute rule that in the
>>slightest doubt concerning the correction of the program, it
>>must crash. Stumbling on with possibly incorrect data is not
>>considered an alternative.
> The rule is that it must enter a safe state. A crash can be a safe
> state, but sometimes it is not.
To be safe, the state must be known. In the context under
discussion, the only knowable state is the one following a
crash. That is, in fact, the whole point of the argument.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/11/2005 2:19:48 PM
|
|
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail@moderncppdesign.com> wrote in message
news:IrAvnF.1E0C@beaver.cs.washington.edu...
> Maybe sometimes, but there are too many examples where orthogonality has
> nothing to do with it. E.g., all variables should be initialized by
> default, unless the programmer asks they are not.
That's how it works in D. For example:
int a; // a is initialized to 0
int b = 3; // b is initialized to 3
int c = void; // c is not initialized
The idea is that instead of going to extra effort to ensure all variables
get initialized before use, one has to go to extra effort to *not* have a
variable get initialized. The same capability is there, it's just in a much
less error-prone form.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 2:20:21 PM
|
|
Walter Bright wrote:
> "Seungbeom Kim" <musiphil@bawi.org> wrote in message
> news:dndmqj$epi$1@news.Stanford.EDU...
>>Walter Bright wrote:
>>>I agree, this is the sensible approach. Too often in C++, the
>>>straightforward intuitive way to do something is the least
>>>safe and most deprecated way. (Virtualness of destructors
>>>comes to mind.)
>>I don't disagree, but let me put it this way: it is a conflict
>>between having orthogonal features vs having them in the most
>>commonly used way.
>>No member function is virtual unless explicitly declared so or
>>inherited from a virtual one, and it's just that a destructor
>>is no exception. A function's being virtual and its being a
>>destructor are orthogonal and do not affect each other. It
>>makes the language neater and easier to explain and
>>understand, as there's one less (special) rule to remember.
>>On the other hand, in practice we often need the destructor to
>>be virtual if any member function is virtual; hence the
>>"guideline" such as "Declare your destructor virtual if any
>>member function is virtual." - one more to remember.
> It's possible to look at rules from the other direction: all
> member functions are virtual (including destructors) unless
> specifically marked otherwise (such as with a 'final'
> attribute).
But that's a very dangerous default. You don't want a function
to be virtual unless you've designed the class to work with
different implementations of the function.
> A further refinement of this idea is that a derived class
> cannot override a 'final' member function in a base class, and
> a final attribute for the whole class makes all its member
> functions final.
> I believe this solution is superior because the program will
> still work correctly even if all the 'final's are omitted.
Provided no one overrides a function that they shouldn't. Which
is most functions.
The issues are, IMHO, orthogonal, and C++ would benefit by
having some means of saying, now that I've overriden this
virtual function, I don't want anyone else overriding my
definition. A way to turn off virtuality, in sum, once it is
there. But the reason this is useful is precisely because a
virtual function always represents a certain risk. You take the
risk when it pays something important in return, but above all,
when you take the risk, you program according to the known
presence of that risk; when you analyse your code, you know that
you cannot count on a particular implementation of the virtual
function, but only on it's meeting the contract that you have
programmatically enforced in the non-virtual function which
calls it.
> Adding in the 'final's would be an optimization.
Preventing errors is an optimization? Since when?
> This fits in neatly with the idea that the straightforward,
> intuitive way is the correct way,
The "correct" way depends on the function, but most of the time,
it is non-virtual. Unless the class has been designed with
virtual specifically in mind for the function in question.
Or are you arguing that it is safer to call functions not
knowing what they do.
There are obviously trade-offs involved here. But what virtual
offers is flexibility, not safety. The more safety becomes
important, the less you use virtual, and the more flexibility
becomes important, the more you use virtual -- for the most
extreme flexibility, you throw away static type checking
completely (can you say Smalltalk), but it becomes significantly
more difficult to achieve the same levels of safety.
There's an old saw that people who regularly use static type
checking systematically underestimate just how much additional
flexibility purely dynamic type checking provided, and that
people who regularly use purely dynamic type checking
systematically underestimate just how much additional safety
static type checking provides. What is certain is that almost
any flexibility also includes the flexibility to introduce
undetected errors, i.e. a lack of safety. To that degree, the
two are in opposition, and everything you add to increase
flexibility makes safety more difficult.
Given that, any language represents a compromise between safety
and flexibility. And while there are many things I would
criticize in C++ (the declaration syntax, for starters), I find
that it makes a pretty good compromize for a large number of
application domains -- you can do some very dynamic programming
with it, just as you can do critical systems with it. And while
I'm all in favor of improvements, making functions virtual by
default is not IMHO an improvement.
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/11/2005 2:21:04 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Bob Bell wrote:
>>I also don't recall ever having an out of bounds index that
>>wasn't caused by a bug.
> Honest, me neither. I do recall, however, having spent lots of
> time on soft errors caused by out of bounds access, versus
> finding the problem with extreme ease when bounds checking was
> in vigor. How about you? How about others?
In C++, or in general?
Statistically, both in C and in C++, I think the largest single
source of errors has been memory management -- accessing already
freed memory seems to be a favorite.
In C, bounds errors would have been a very, very close second,
with the most frequent cause of those being something along the
lines of:
char* p = strcpy( malloc( strlen( s ) ), s ) ;
i.e. forgetting to allocate the extra byte for the '\0'. In
C++, this has never been a problem, because from the very start,
we used string and array classes which did bounds checking.
(For many things, I find the STL a major step backwards compared
to what was current practice before.)
--
James Kanze mailto: james.kanze@free.fr
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
12/11/2005 2:21:33 PM
|
|
> A few months ago I had embarked on a macro system for C++ that would
> entirely replace the built-in macro system and would be powerful enough
> to handle things like typesafe variadic functions, automatic type,
> function, and value reification (a la templates, just way cleaner and
> more general :o)), user-defined constants, "smart" enums, all sorts of
> code transformations, the works. I was enthused! After a few iterations
> through the design, I gave up in frustration because I found no simple,
> clean AST transformation that would allow implementing on_scope_exit,
> on_scope_success, and on_scope_failure.
All the news coming out the C++ Standard Committees seem to the related to
library changes (most of which were already available from Boost). There
seems to be little or no interesting work being done on the core language to
allow new useful programming patterns, such as the ones you mentioned above.
Why?
Note: My information comes from this newsgroup and C/C++ User Journal.
Joe
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Joe
|
12/11/2005 4:34:51 PM
|
|
James Kanze wrote:
> Bjorn Reese wrote:
>
>>kanze wrote:
>
>
>>>In critical applications, it is an absolute rule that in the
>>>slightest doubt concerning the correction of the program, it
>>>must crash. Stumbling on with possibly incorrect data is not
>>>considered an alternative.
>
>
>>The rule is that it must enter a safe state. A crash can be a safe
>>state, but sometimes it is not.
>
>
> To be safe, the state must be known. In the context under
> discussion, the only knowable state is the one following a
> crash. That is, in fact, the whole point of the argument.
It is much easier to enter a safe state when bounds are checked, than
whey they are not. This is because the probability of having had a soft
memory error is lower. In a language with memory safety and without
undefined behavior, such as Java, you can *always* enter a safe state.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 11:57:50 PM
|
|
Joe wrote:
> > A few months ago I had embarked on a macro system for C++ that would
> > entirely replace the built-in macro system and would be powerful enough
> > to handle things like typesafe variadic functions, automatic type,
> > function, and value reification (a la templates, just way cleaner and
> > more general :o)), user-defined constants, "smart" enums, all sorts of
> > code transformations, the works. I was enthused! After a few iterations
> > through the design, I gave up in frustration because I found no simple,
> > clean AST transformation that would allow implementing on_scope_exit,
> > on_scope_success, and on_scope_failure.
>
> All the news coming out the C++ Standard Committees seem to the related to
> library changes (most of which were already available from Boost). There
> seems to be little or no interesting work being done on the core language to
> allow new useful programming patterns, such as the ones you mentioned above.
> Why?
a) Because changing the library is easier than changing the language.
b) There _is_ interesting work being done on the core language.
> Note: My information comes from this newsgroup and C/C++ User Journal.
See the standard commitee web page:
http://www.open-std.org/jtc1/sc22/wg21/
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/11/2005 11:58:35 PM
|
|
Walter Bright wrote:
> "Andrei Alexandrescu (See Website For Email)"
> <SeeWebsiteForEmail@moderncppdesign.com> wrote in message
> news:IrAvnF.1E0C@beaver.cs.washington.edu...
>
>>Maybe sometimes, but there are too many examples where orthogonality has
>>nothing to do with it. E.g., all variables should be initialized by
>>default, unless the programmer asks they are not.
>
>
> That's how it works in D. For example:
>
> int a; // a is initialized to 0
> int b = 3; // b is initialized to 3
> int c = void; // c is not initialized
>
> The idea is that instead of going to extra effort to ensure all variables
> get initialized before use, one has to go to extra effort to *not* have a
> variable get initialized. The same capability is there, it's just in a much
> less error-prone form.
Well, given that "void" can be used in other contexts as well, that's
not a very good decision because we can't grep for all uninitialized
variables in a project.
Worse, since D has no macros (I can't believe it's actually announcing
that with prinde :o)), we can't define some INITIALIZER and switch with
a macro definition between "all initialized" and "some uninitialized"
builds.
But wait, I'm not sure D can define something to return a
default-initialized value of any type requested. The equivalent C++ code
would be:
struct DefaultInitialized {
template <class T>
operator T() { return T(); }
};
Can I do something similar in D? Then I'd write a little macro
preprocessor on top of D to allow simple macros, and maybe also
automatic type deduction for template functions :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 11:58:57 PM
|
|
"Bob Bell" <belvis@pacbell.net> wrote in message
news:1134266461.421226.57930@g43g2000cwa.googlegroups.com...
> Sure, that's why I favor a "stop the program and help me debug it"
> behavior, such as a crash plus core dump. I would love it if such
> behavior was somehow mandated by the standard, but I am also willing to
> live with it as a QOI issue. I am not in favor of throwing exceptions
> for these kinds of conditions because not only do they not help me
> debug the program, they can make the problem worse by masking the bug.
Sometimes, though, it is necessary for it to throw an exception rather than
abort. This would be, for example, a program that accepts foreign "plugins".
The application could attempt to sandbox the plugin by catching any
exceptions it throws and aborting the plugin, not the application.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/11/2005 11:59:18 PM
|
|
James Kanze wrote:
> Statistically, both in C and in C++, I think the largest single
> source of errors has been memory management -- accessing already
> freed memory seems to be a favorite.
Hm. In my experience it's uninitialized variables.
> In C, bounds errors would have been a very, very close second,
> with the most frequent cause of those being something along the
> lines of:
>
> char* p = strcpy( malloc( strlen( s ) ), s ) ;
>
> i.e. forgetting to allocate the extra byte for the '\0'. In
> C++, this has never been a problem, because from the very start,
> we used string and array classes which did bounds checking.
> (For many things, I find the STL a major step backwards compared
> to what was current practice before.)
Aha, so are you finally saying that operator[] should perform bounds
checking? :o)
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/11/2005 11:59:40 PM
|
|
* Alf P. Steinbach:
>
> The technique is unable to execute the simulated finally block when one
> of those shared declarations causes an exception. Which is pretty
> restrictive. But on the other hand also good: allowing such reference
> would enable the simulated finally block to refer to not yet constructed
> variables.
No finally construct is executed when a shared declaration throws.
So the simulation technique isn't restricted, and neither are Andrei's
proposed constructs.
Never post to a moderated group before having a cup of coffee.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/12/2005 12:00:36 AM
|
|
peter steiner wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>
>>I believe three constructs would simplify writing robust code:
>>
>>1. on_scope_exit { ... code .. }
>>
>>Executes code upon the current scope's exit. Several on_scope_exit
>>blocks in a scope are executed LIFO.
>>
>>2. on_scope_success { ... code ... }
>>
>>Executes code if the current scope is exited normally (no exception
>>engendered).
>>
>>3. on_scope_failure { ... code ... }
>>
>>Executes code if the current scope is exited via an exception.
>>
>>With such language constructs, writing robust programs would be much
>>easier. Programmers can easily change the state of the program and leave
>>safeguards behind them to ensure things are properly cleaned up in case
>>something fails down the road. Little RAII classes built just for the
>>sake of undoing stuff (I've seen "IntDecrementer" in my time...) would
>>not be necessary anymore. Such a construct is so useful, it makes
>>ScopeGuard very popular in spite of its awkwardness.
>
>
> [second try...]
>
> imho in the context of safe programming the lack of a finally statement
> should be considered as advantage of c++ to languages like java. raii
> is inherent in c++ design and a finally statement encourages
> programming orthogonal to that concept, especially beginners for whom
> finally is a concept much easier to understand upfront. why allow for
> circumventing the 'native' c++ way of handling resources with finally
> for those few cases where it is just awkward to work around without it?
Because often a change to the state of the program can be comfortably
seen as a resource. Part of the reason for which today's programs behave
so badly in face of errors is that we don't yet understand what
programming with error entails, and what language facilities we really
need. The on_scope_xxx features are, I believe, one step forward. (A
future step would be a static analysis that inserts them automatically.)
For example, say in a function I need to add an element to a container,
and then do a few other things, some of which might fail.
void Widget::foo(const bool condition) {
cont.push_back(elem);
if (condition) ++counter;
database.insert(serialize(elem));
}
How would you express things like undoing adding an element to a
container with RAII? You'd have to implement VectorPopper. How about the
conditional increment? You'd have to implement ConditionalDecrementer.
With on_scope_failure, it's all very easy:
void Widget::foo(const bool condition) {
cont.push_back(elem);
on_scope_failure { cont.pop_back(); }
if (condition) ++counter;
on_scope_failure { if (condition) --counter; }
database.insert(serialize(elem));
}
> afair bjarne stroustrup explains the decision to omit finally in c++
> and the benefits of raii in 'the design and evolution of c++'.
> reintroducing finally or variations of it now for the discussed reasons
> seems contraproductive. why not tackle the problems of current
> solutions like scopeguards by improving the language to fit such
> concepts?
RAII is good, and on_scope_xxx does not compete with it. IMHO final is
fatally flawed because it forces you to change the code *preceding* it,
thus adding complexity to it and fostering idiomatic uses that are
coarse-grained and clunky. (Same shortcoming applies to "catch" btw;
both "catch" and "finally" are flawed because they don't allow
implementing transactional code easily.) Think of implementing the
function above in C++:
void Widget::foo(const bool condition) {
try {
cont.push_back(elem);
if (condition) ++counter;
try {
database.insert(serialize(elem));
} catch (...) { if (condition) --counter; throw; }
} catch (...) { cont.pop_back(); throw; }
}
You see, the fact that catch (or finally) forces a change (i.e.,
indentation) on the *normal path* code has fatal repercussions in
complexity when you try to scale up. The linearized alternative ain't
nicer at all:
void Widget::foo(const bool condition) {
int whereAmI = 0;
try {
cont.push_back(elem);
++whereAmI;
if (condition) ++counter;
++whereAmI;
database.insert(serialize(elem));
} catch (...) {
switch (whereAmI) {
case 2:
if (condition) --counter;
case 1:
cont.pop_back();
}
throw;
}
}
> for example i could imagine that my favourite missing feature in c++,
> namely lambda definitions with automatic scope variable forwarding
> would allow to implement very convenient scope guards. unfortunately
> such a feature seems to stay being my favorite _missing_ feature for a
> long time still... :o)
Finding the "principal abstraction" is indeed a great approach. What's
needed for on_scope_xxx are lexically nested functions coupled with the
ability to hook into a scope's exit. Lambdas offer the first thing, and
(with proper source transformations) catch/finally offer part of the
second thing.
What matters is that we know what we're looking for when it comes about
error handling. Because destructors, try, catch, and finally - those are
not enough.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 12:02:41 AM
|
|
Alf P. Steinbach wrote:
> * Andrei Alexandrescu:
>>I said "no way" because of the reasons you mentioned, to which I'd add
>>the added awkwardness in the presence of loops (break and continue will
>>not properly execute the simulated finally block unless you add more
>>cruft to the code).
>
> That's just the relying on convention. What would otherwise be the
> function (or block) body, sans the declarations shared with the
> simulated finally block, must always be a SESE block (heh ;-)) ending
> with a throw. Inside that SESE block loops behave as they always have.
SESE sucks and postconstructors rock, you can quote me on that :o).
> But now that I have to actually formulate it in words, I see one
> restriction:
>
> The technique is unable to execute the simulated finally block when one
> of those shared declarations causes an exception. Which is pretty
> restrictive.
I think we can move on.
> But on the other hand also good: allowing such reference
> would enable the simulated finally block to refer to not yet constructed
> variables.
That's a misunderstanding (much as it was in the case of
postconstructors; but no way I'll invest one more calorie into that).
The on_scope_xxx blocks are statically scoped. They only see the symbols
introduced prior to them, which have been constructed successfully by
virtue of control flow:
void foo() {
string s1 = "Hello";
// At this point, s1 has been fully constructed
on_scope_exit { ... can use s1, can't use s2 ... }
string s2 = ", world!";
on_scope_exit { ... can use s1 and s2 ... }
}
> So, that brings back to life the discussion on how to _detect stack
> unwinding_ in a destructor...
>
> Are we any closer on that?
<insert Rhett Butler's last line>
> Or, do you now think such constructs absolutely need direct language
> support, and can't be build on top of the destructor mechanism, i.e.
> that access to shared declarations in 'code' is critical?
Access to the scope being exited is crucial. For anything else, we have
local structs with destructors. That's not enough.
> And if the latter, aren't we then up against the possibily of referring
> to not yet constructed variables?
>
> That thing that's not supported by the simulated finally block?
We aren't. See above.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 12:03:08 AM
|
|
"James Kanze" <kanze@none.news.free.fr> wrote in message
news:439c06df$0$4338$626a54ce@news.free.fr...
> Walter Bright wrote:
> > It's possible to look at rules from the other direction: all
> > member functions are virtual (including destructors) unless
> > specifically marked otherwise (such as with a 'final'
> > attribute).
>
> But that's a very dangerous default. You don't want a function
> to be virtual unless you've designed the class to work with
> different implementations of the function.
Maybe on paper it can appear that way. But I write a lot of OOP code, and
I've never had that "dangerous default" trip me up. I have, however, many
times had the "I shoulda made it virtual" bug, and those bugs are
particularly hard to track down. Furthermore, it's the responsibility of the
writer of the derived class to 'fit in' with the base class design. If the
base class designer wants to insure nobody overrides a member, then he tags
it with 'final'.
I just don't see how that is dangerous, or at least more dangerous than the
missing virtual specifier. I also know of no cases where one *intentionally*
overrides a non-virtual member function with another function. C++'s method
does not prevent this kind of error, so how is it safer?
> > A further refinement of this idea is that a derived class
> > cannot override a 'final' member function in a base class, and
> > a final attribute for the whole class makes all its member
> > functions final.
>
> > I believe this solution is superior because the program will
> > still work correctly even if all the 'final's are omitted.
>
> Provided no one overrides a function that they shouldn't. Which
> is most functions.
> The issues are, IMHO, orthogonal, and C++ would benefit by
> having some means of saying, now that I've overriden this
> virtual function, I don't want anyone else overriding my
> definition.
If the base class designer creates a method that shouldn't be overridden, he
tags it with 'final'. And as you say, there's no way in C++ to tag a
function as "do not override", though I don't agree it is orthogonal.
> A way to turn off virtuality, in sum, once it is
> there. But the reason this is useful is precisely because a
> virtual function always represents a certain risk. You take the
> risk when it pays something important in return, but above all,
> when you take the risk, you program according to the known
> presence of that risk; when you analyse your code, you know that
> you cannot count on a particular implementation of the virtual
> function, but only on it's meeting the contract that you have
> programmatically enforced in the non-virtual function which
> calls it.
I'm not sure what you're saying there, but the problem in C++ comes from
overriding a non-virtual function, and the fact that doing so is almost
always inadvertent and a bug, exacerbated by:
1) virtualness is inherited, meaning you can't look at a function and tell
if it is virtual or not without examining all its base classes
2) programmers tend to pull the 'virtual' keyword off of functions in
classes that nobody derives from at the moment, for efficiency reasons.
Then, later, the class does get derived from, the virtual isn't put back on,
and voila, a difficult to find bug.
> > Adding in the 'final's would be an optimization.
> Preventing errors is an optimization? Since when?
>From the perspective that if you take a working, bug free program that uses
'final', and remove all the 'final' keywords, you will not introduce a bug.
Adding 'final' can expose a bug, but it won't create one. Adding 'final's in
a working program is often done as an optimization, because the compiler has
additional information about a function it can use to generate better code.
> > This fits in neatly with the idea that the straightforward,
> > intuitive way is the correct way,
>
> The "correct" way depends on the function, but most of the time,
> it is non-virtual. Unless the class has been designed with
> virtual specifically in mind for the function in question.
>
> Or are you arguing that it is safer to call functions not
> knowing what they do.
You and I definitely have a different perspective on how to write OOP
classes. I view overriding safely as being the responsibility of the derived
class designer, where as you think (I infer) of it as the base class
designer's responsibility. I don't understand that perspective, however, as
the base class designer cannot know in advance what a derived class' needs
and purposes are.
> What is certain is that almost
> any flexibility also includes the flexibility to introduce
> undetected errors, i.e. a lack of safety. To that degree, the
> two are in opposition, and everything you add to increase
> flexibility makes safety more difficult.
That axiom sounds true, and has a certain seductive fatalism to it, but I
refuse to accept it. In general, I don't buy the conventional wisdom that
the non-safety issues C++ has are a *necessary* byproduct of its power.
> Given that, any language represents a compromise between safety
> and flexibility.
I don't accept the axiom as true, so I don't necessarilly accept the
conclusion <g>. It's also slightly irrelevant in this particular discussion,
as the virtual by default methodology is not less flexible.
> And while there are many things I would
> criticize in C++ (the declaration syntax, for starters), I find
> that it makes a pretty good compromize for a large number of
> application domains -- you can do some very dynamic programming
> with it, just as you can do critical systems with it.
There's no doubt you can do those things with C++, but that doesn't at all
imply the language cannot be improved, or that flexibility would be lost in
such improvements.
> And while
> I'm all in favor of improvements, making functions virtual by
> default is not IMHO an improvement.
We'll agree to disagree on that point, then, as I've wasted too many hours
trying to discover which non-virtual method got inadvertantly overridden in
a complex OOP heirarchy. This isn't just a theoretical issue for me.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/12/2005 12:03:52 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Because often a change to the state of the program can be comfortably
seen as a resource.
Oopsies.
s/\bcan\b/cannot/;
(I'm pipelining this correction with a post of mine that hasn't appeared
yet.)
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 12:04:35 AM
|
|
{Moderator's note: Further discussion of D will be rejected without a
Follow-Up header directed somewhere else than
comp.lang.c++.moderated. -mod/dk}
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail@moderncppdesign.com> wrote in message
news:IrCoJH.1uM4@beaver.cs.washington.edu...
> Walter Bright wrote:
> > That's how it works in D. For example:
> >
> > int a; // a is initialized to 0
> > int b = 3; // b is initialized to 3
> > int c = void; // c is not initialized
> >
> > The idea is that instead of going to extra effort to ensure all
variables
> > get initialized before use, one has to go to extra effort to *not* have
a
> > variable get initialized. The same capability is there, it's just in a
much
> > less error-prone form.
>
> Well, given that "void" can be used in other contexts as well, that's
> not a very good decision because we can't grep for all uninitialized
> variables in a project.
That's a criticism of the implementation of the idea, not the idea itself.
Also, "= void" does not occur in any other context, so it is reasonably
greppable. (Yes, one could write "= /*comment*/void", but it's still far
more greppable than uninitialized variables in C++, which are not greppable
*at all*.) You can also simply grep for void, and ignore the false
positives. There's no requirement that grep not give false positives. After
all, even if D added an "uninitialized" keyword, you'll still get false
positives for /* uninitialized */.
> Worse, since D has no macros (I can't believe it's actually announcing
> that with prinde :o)),
Nothing says you can't use a macro preprocessor with D. The language has
specific support for tools that generate D as output, because it supports
the #line construct. The macro processor was added to C as an easy to way to
add much power that was lacking in the core language. There's no reason why
we can't do better than that.
> we can't define some INITIALIZER and switch with
> a macro definition between "all initialized" and "some uninitialized"
> builds.
You can do this:
version (initialized)
int a;
else
int a = void;
Or this:
version (initialized)
typedef int myint;
else
typedef int myint = void;
myint a;
You might argue this is not the easiest solution if one desires a lot of
uninitialized variables. I'd argue that the need for uninitialized variables
is very low, so this is not a real problem. (Note that even a basic data
flow analysis optimizer can eliminate most redundant initializations, so
this is rarely an efficiency issue.)
> But wait, I'm not sure D can define something to return a
> default-initialized value of any type requested. The equivalent C++ code
> would be:
>
> struct DefaultInitialized {
> template <class T>
> operator T() { return T(); }
> };
>
> Can I do something similar in D?
Yes:
T.init
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/12/2005 12:02:44 PM
|
|
* Andrei Alexandrescu (See Website For Email):
>
> void foo() {
> string s1 = "Hello";
> // At this point, s1 has been fully constructed
> on_scope_exit { ... can use s1, can't use s2 ... }
> string s2 = ", world!";
> on_scope_exit { ... can use s1 and s2 ... }
> }
OK, a kind of (conceptually, at least) dynamic "registration" or
"activation", as with try blocks: an on_scope_exit will not have effect
unless the execution has passed over it, and the handlers are executed
in LIFO order.
Is there a proposal somewhere?
Firing up my editor & compiler I found that on_scope_failure( ... )
could be implemented by a macro scheme, with client code like
void foo()
{
std::string const s1 = "Hello";
// At this point, s1 has been fully constructed
on_scope_failure std::cout << s1 << std::endl; end_oscofa
std::string const s2 = ", world!";
on_scope_failure std::cout << s1+s2 << std::endl; end_oscofa
end_checked_scope_2_handlers
}
but it eludes me how to catch a 'return' or 'break' from the block's
main code while having access to the declarations above the handler
code, so on_scope_success and on_scope_exit seem to be difficult.
On the other hand, ScopeGuard does that difficult part of the job,
for the cases that matter most in practice.
On second thoughts, ScopeGuard does part of the job to on_scope_exit,
and what's left is then an inline defined on_scope_success, e.g. for a
database commit: are you sure you don't care about detecting stack
unwinding for destructors?
Cheers,
- Alf
PS: You really don't want to know the innards of the above macros. ;-) It's
the try-catch based 'finally' simulation embedded in a loop implemented by
longjmp, with cloning of exceptions. For each on_scope_failure.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/12/2005 12:04:23 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Walter Bright wrote:
> > "kanze" <kanze@gabi-soft.fr> wrote in message
> > news:1134136164.940627.219400@g47g2000cwa.googlegroups.com...
> >>There are clearly exceptions: the most obvious is a demo
> >>program for an exposition, where a crash is highly visible,
> >>and no one is going to use the results, or even verify that
> >>they are correct. But any time the results are to be used
> >>for anything significant, it is important that they be
> >>correct, and crashing is preferable to wrong results.
> > My favorite exception is a standalone DVD player. Since
> > there's no possibility of updating the software on it, I'd
> > rather it soldiered on trying to play the DVD, regardless of
> > if it's displaying corrupted data or not. There's not much
> > worse than having some friends over watching a movie, and
> > having the DVD player quit with "corrupted data!" leaving
> > you to try to skip over it with the fast forward before it
> > hangs at the crucial spot.
> I'd also prefer a cell phone conversation with hiccups than
> one that's interrupted because the packet processor has a bug.
If you can guarantee that it only hiccups, fine. If you don't
know the state, however, you can't guarantee that. And privacy
laws insist that it is better to cut the communication than to
accidentally have it audible to others.
I've worked on fixed line telephone systems, so in this case, I
can speak from actual experience. In case of doubt, you crash.
The hardware is designed to maintain existing communications
during reboot, and it is better to not allow new connections
than to run the risk of accidentally silently connecting two
conversations. And everything above the lowest level switch
where you tap in is redundant; you get out of there as soon as
possible, so that the back-up can recognize that you're out of
it, and take over.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 12:07:29 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
> > Realistically, all programs must be able to live with a
> > crash. At least on my system, dereferencing an out of
> > bounds pointer will cause a crash, as will accessing through
> > a mis-aligned pointer. And there's really no way to catch
> > the first of these.
> No way an illegal pointer access will cause a crash.
It might, and it might not. My point is that there is no way
you can verify a pointer before accessing it, to be sure that it
will not crash. So a program has to be able to live with a
crash.
> What if the pointer points to some memory that is within the
> right address space, but of the wrong type?
I didn't say that all errors will cause a crash. I said that
there are causes of crashes that you cannot test before hand.
So all systems must be able to live with a program crash.
[...]
> > In critical applications, it is an absolute rule that in the
> > slightest doubt concerning the correction of the program, it
> > must crash. Stumbling on with possibly incorrect data is
> > not considered an alternative.
> Sure. That leads to the idea that operator[] must be checked
> and execution must be aborted if the check fails, not that it
> yields undefined behavior.
Agreed. The current standard allows correct behavior, but does
not impose it.
The proposed change would forbid this correct behavior.
> I think Peter has made an innocent semantic mistake, that
> other posters pick on. The way I read what he's saying, when
> he says "checking", he really means "checking and taking
> contingency measures if the check fails". Many people seem to
> imply that he requres throwing an exception as the only
> acceptable contingency measure.
I'm not sure. He specifically said to swap the semantics of
operator[] and at(). at() has well defined semantics, and those
don't include crashing. (Of course, you can core dump in a call
to at(), because of undefined behavior elsewhere.)
> Speaking for me at least, I'm interested in making behavior
> defined for the "usual" case and allow unsafe performance with
> extra syntactic effort.
I agree, provided the usual case is defined to be something
useful. Currently, the standard doesn't really have to concept
of "guaranteed to crash" (except for assert() and, according to
how you look at it, abort()). The best we can do in the context
of the current standard is undefined behavior, with an
expression of intent (missing in the current standard). After
that, it is a problem of quality of implementation and market
forces.
Personally, I think this IS a problem in the standard. IMHO, 1)
there are far too many cases of undefined behavior where the
behavior could easily be well defined, and 2) there are a number
of cases where the standard should either limit the possible
undefined behavior, or introduce new types of defined behavior,
like crashing.
> It's a pity that the discussion took so many meanders just to
> get to its point. Instead of discussing whether behavior
> should be defined or not, we discuss whether throwing or
> aborting is the best thing to do. Somehow it is being aired
> in this thread that "checking" means "throwing", and that
> "undefined behavior" naturally leads to "aborting". That is
> simply false.
That's NOT what I'm arguing against, at any rate, and if I've
been understood like that, then I've not expressed myself
clearly. Undefined behavior is always undesirable. In some
cases, it's probably unavoidable, but there's far more of it in
C++ than there should be.
> Checking is good. Defined behavior is good. Unchecked,
> undefined, are bad. Slow is also bad, and that's the tension
> we are fighting against.
Then we are in perfect agreement. (No matter how you cut it,
slow is less bad than wrong. And undefined behavior that the
implementation hasn't defined is an invitation to wrong.)
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 12:09:58 PM
|
|
As you said, and I agree, non-virtual descrutors *implies* one is not
meant to derive from the class. But, someone who knows what he is doing
can derive from it. Like a customized char_traits, for example.
struct my_char_traits : std::char_traits<char>
{
static bool eq(char c1, char c2)
{
return /* whatever appropriate */ ;
}
};
If you're not using polymophism, you dont want your classes to be
polymorphic. It could introduze subtle errors.
IIRC, in my projects I have less than 10% of my classes using
polymorphism. The rest is just for generic programming, functors and
metaprogramming.
Note that if char_traits were declared virtual, it would make
my_char_traits virtual. In this example, it wouldnt really hurt
(ignoring performance), but there are times where it does hurt. And if
it was declared final, I couldnt derive from it, and reuse its static
functions (or could I?), without having to write all functions and have
them forward to the right ones.
best regards,
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Felipe
|
12/12/2005 12:11:58 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
> > Andrei Alexandrescu (See Website For Email) wrote:
> >>The context is: why is operator[] undefined for invalid input?
> >>Answer: It is undefined so implementations can check and
> >>crash the programs.
> >>That answer is bogus. If the behavior is undefined, that
> >>means I can't count on it. You can't count on it. We can't
> >>count on it. There's only one who can count on it, and
> >>that's "Nobody".
> >>:o)
> > That's one way of looking at it, and I agree that requiring
> > a program to crash every time there is undefined behavior
> > would be a major step forward. (Given the difficulty of
> > detecting some of the cases, I'm not sure that people more
> > concerned than I am about performance would agree:-).)
> > The other way is to use an implementation that you know,
> > which does give the desired behavior. Said implementation
> > would, of course, still be conform.
> But advising one to lock into an implementation is exactly
> what experienced people know is not good. They advocate
> writing portable code. You've done the same in the past. Why
> the sudden change of view in this particular case?
What's your choice. If there
is a strict requirement that the code crash, say because we are
working on a critical system, then we have a choice: use the
defined behavior of the implementation, or implement our one
version with the behavior we want. Those are the two
alternatives, today.
If it's not a requirement, only preferred behavior, then your
code doesn't really depend on this behavior in order to be
correct. We're talking about what happens in a case that won't,
or shouldn't occur, and for which there are no requirements
(except the requirement that it not occur:-)).
(As I said, requiring the crash would be an improvement in the
standard. But at present, I think that the only time the
standard requires a crash is if you actively write assert --
it's not a behavior that the standard recognizes as
"standardizable".)
> > As a general response, of course, you're right. As an
> > answer to the suggestion that the behaviors of at() and
> > operator[]() should be inversed, however...
> We need safety more than we need speed. The easiest solution
> syntactically should be safe. Speed should be attainable via
> extra syntax.
I totally agree. And the only safe solution is to crash. The
standard allows the safe solution. It doesn't require it:-(.
But it doesn't forbid it, either. The proposal was to forbid
it. (And you surely know that I don't put speed before safety.)
> >>And I still don't understand how C++, being rife with soft
> >>errors that travel below the type system's radar and dynamic
> >>checks alike, is safer than Java, which has no memory errors
> >>and no undefined behavior. Please illuminate me.
> > First, of course, the claim that Java has no undefined
> > behavior is not true. It very definitly has undefined
> > behavior, explicitly in the case of threading, and in
> > practice, also because the specification isn't always as
> > precise as one would like.
> Wrong. Check Java 1.5. It defines behavior of even incorrect
> threaded programs.
The last time I looked was Java 1.2. But in practice, this
rather surprises me; totally defining behavior in a threaded
context can be very, very costly -- it could potentially require
a fence between every memory access to ensure order.
> Undefined behavior in Java has a very different meaning.
It means that the program doesn't work the way it is supposed
to.
> It does NOT mean memory errors.
It does NOT mean that you notice the error immediatly. Which
increases the probability that you unwittingly use a wrong
result.
The problem I have with undefined behavior in C++ isn't that it
might cause a core dump. The problem I have with it is that it
might not cause a core dump.
I've cut most of the rest, because my argument isn't that
anything is really impossible -- just that Java makes a number
of important things more difficult than they should be. Beyond
that, this isn't really the forum to argue the merits or lack of
merits of Java. And I think we agree that C++ could be made
safer than it is, and that this would be a good evolution. And
that regardless of how we evaluate the global merits, there are
some things in Java that we would do well to adopt: garbage
collection, and a strictly defined order of evaluation come to
mind.
What I don't want us to adopt is the Java philosophy of throwing
an exception anytime you don't know what else to do. Exceptions
have a role, but it isn't a case of one size fits all.
[...]
> > Obviously, that doesn't mean that everything in Java is bad.
> > I regularly use garbage collection in C++, just as I
> > regularly use checking implementations of the STL, but as
> > you correctly point out, neither of these are requirements
> > of the standard. And of course, avoiding undefined behavior
> > in C++ is a real art; strictly specifying the order of
> > evaluation in an expression would go a long way to solving
> > this. But globally, the problems are less than in Java.
> That mellows your initial provocative statement quite some,
> but I assert that that statement is simply untenable.
Which provocative statement do you mean:-)?
I made two statements which seem to have raised some objections.
I'll stand by both of them:
-- If you don't know the exact state of a program, and what it
is doing, then the only appropriate thing to do is to abort
(except in special cases like demonstration programs).
-- It is far more difficult to attain what I consider an
acceptable level of quality in Java than in C++.
That last statement doesn't mean that C++ is without problems,
or that the quality comes automatically.
> At every line of code, C++ offers you a vast array of means to
> destroy the most solid design, in ways that are undetectable
> during compilation, with static checking with any existing
> tool, or via dynamic checks. How come destructors and virtual
> private functions make up for that?
Because I don't have to do it.
Part of this is probably linked with tradition and history. The
problems that C++ presents are not new, and they are not unique
to C++. They are low level, and the mechanisms are generally in
place to prevent them. The problems that tend to slip through
code review are the design problems, and the deployment problems
(which of course a code review never sees). And those are
problems which are easier to solve in C++ than in Java -- in
some specific cases, Java even seems to go out of its way to
exacerbate them.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 12:14:13 PM
|
|
Walter Bright wrote:
> "kanze" <kanze@gabi-soft.fr> wrote in message
> news:1134136164.940627.219400@g47g2000cwa.googlegroups.com...
> > There are clearly exceptions: the most obvious is a demo
> > program for an exposition, where a crash is highly visible,
> > and no one is going to use the results, or even verify that
> > they are correct. But any time the results are to be used
> > for anything significant, it is important that they be
> > correct, and crashing is preferable to wrong results.
> My favorite exception is a standalone DVD player. Since
> there's no possibility of updating the software on it, I'd
> rather it soldiered on trying to play the DVD, regardless of
> if it's displaying corrupted data or not. There's not much
> worse than having some friends over watching a movie, and
> having the DVD player quit with "corrupted data!" leaving you
> to try to skip over it with the fast forward before it hangs
> at the crucial spot.
As long as the "error recovery" isn't to retry, I can agree with
you. After all, no body is going to make any important
decisions based on the corrupt data -- you'll just have some
scenes in the film which look different from what they were
meant to. Or?
> It's interesting how software has embedded itself into
> everything. I have a TV that needs to be "rebooted" by
> cycling the power every now and then.
Ignoring the error and trying to continue is probably acceptable
in an "entertainment" system. On the other hand, any time the
system is doing anything important, it's generally better to not
do it, than to do it wrong. (On a locomotive brake system I
worked on, the first thing they did when a error was detected
was cut the power to the processor -- they weren't taking any
risk of a software error trying to override the manual
overrides.)
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 12:14:35 PM
|
|
Walter Bright wrote:
> "James Kanze" <kanze@none.news.free.fr> wrote in message
> news:439c06df$0$4338$626a54ce@news.free.fr...
> > Walter Bright wrote:
> > > It's possible to look at rules from the other direction:
> > > all member functions are virtual (including destructors)
> > > unless specifically marked otherwise (such as with a
> > > 'final' attribute).
> > But that's a very dangerous default. You don't want a
> > function to be virtual unless you've designed the class to
> > work with different implementations of the function.
> Maybe on paper it can appear that way. But I write a lot of
> OOP code, and I've never had that "dangerous default" trip me
> up.
Given that my OO experience (15 years) is largely in C++, and
that even in Java, I make liberal use of final, I can't say that
it's tripped me up, either. Because I've never given it a
chance.
The idea that all that is necessary for a function to be
overriden is to declare it virtual, however, is false. It takes
more care when programming around virtual functions than around
non-virtual functions.
> I have, however, many times had the "I shoulda made it
> virtual" bug, and those bugs are particularly hard to track
> down.
Funny, but I've never had a bug because I should have declared a
function virtual, and didn't. Which functions, if any, can be
overridden is part of the basic design.
It does happen that later, we decide that a class needs more
flexibility, and add virtual functions to it. But I've never
seen that happen where other things didn't need to be modified
as well.
> Furthermore, it's the responsibility of the writer of the
> derived class to 'fit in' with the base class design.
It's the responsibility of the author of the derived class to
meet the documented contract. If you know how to specify a
documented contract which can be met with different
implementations, you do so, and you declare the function
virtual. But you have to be much more careful with contracts
when dealing with virtual functions -- when calling the
function, you don't really know what it does.
> If the base class designer wants to insure nobody overrides a
> member, then he tags it with 'final'.
> I just don't see how that is dangerous, or at least more
> dangerous than the missing virtual specifier.
It requires more care when programming around a virtual
function. Unless the author of the base class has specifically
designed the class with this in mind, it probably won't work.
That doesn't mean that virtual functions are bad -- I use them
all the time. But it does mean that making a function virtual
should be a conscious decision on the part of the base class
designer.
> I also know of no cases where one *intentionally* overrides a
> non-virtual member function with another function. C++'s
> method does not prevent this kind of error, so how is it
> safer?
I don't understand what you are asking. You can't override a
non-virtual member function, intentionally or otherwise. On the
other hand, it's not that rare to provide non-virtual functions
in the derived class with the same name as non-virtual functions
in the base class, e.g. to provide looser pre-conditions, for
the user who knows he is dealing with a derived class.
> > > A further refinement of this idea is that a derived class
> > > cannot override a 'final' member function in a base class,
> > > and a final attribute for the whole class makes all its
> > > member functions final.
> > > I believe this solution is superior because the program
> > > will still work correctly even if all the 'final's are
> > > omitted.
> > Provided no one overrides a function that they shouldn't.
> > Which is most functions. The issues are, IMHO, orthogonal,
> > and C++ would benefit by having some means of saying, now
> > that I've overriden this virtual function, I don't want
> > anyone else overriding my definition.
> If the base class designer creates a method that shouldn't be
> overridden, he tags it with 'final'. And as you say, there's
> no way in C++ to tag a function as "do not override", though I
> don't agree it is orthogonal.
Perhaps the safest solution is to require every function to be
marked one way or the other:-). Barring that, the C++ solution
favors safety, requiring an explicit mark when the added
flexibility is desired; the Java solution favors flexibility,
requiring an explicit mark when the added safety is required.
My experience is that Java programmers tend to forget the final.
The code works anyway, so who cares. At least, the code works
for now, as long as no one actually does override the function.
On the other hand, a C++ programmer cannot forget the virtual
where it is needed, because the code won't work. (The one
exception is the destructor, where the code may appear to work
until the class is used in a multiple inheritance lattice, or a
derived class has a non-trivial destructor. But a good compiler
will warn if a class has virtual functions, but a non-virtual
destructor.)
> > A way to turn off virtuality, in sum, once it is there.
> > But the reason this is useful is precisely because a
> > virtual function always represents a certain risk. You
> > take the risk when it pays something important in return,
> > but above all, when you take the risk, you program
> > according to the known presence of that risk; when you
> > analyse your code, you know that you cannot count on a
> > particular implementation of the virtual function, but only
> > on it's meeting the contract that you have programmatically
> > enforced in the non-virtual function which calls it.
> I'm not sure what you're saying there, but the problem in C++
> comes from overriding a non-virtual function,
You can't override a non-virtual function. You'll have to
explain what you mean here.
> and the fact that doing so is almost always inadvertent and a
> bug, exacerbated by:
> 1) virtualness is inherited, meaning you can't look at a
> function and tell if it is virtual or not without examining
> all its base classes
That is a bit of a problem. All of the coding guidelines I'm
familiar with require using the virtual keyword in the derived
class, but of course, it's easy to forget, and may confuse
programmers deriving from the derived class.
> 2) programmers tend to pull the 'virtual' keyword off of
> functions in classes that nobody derives from at the moment,
> for efficiency reasons.
If a programmer insists on being stupid, there's not much you
can do about it. All I can say is that I've never encountered
one that stupid.
Whether a function is virtual or not is part of the contract of
the class. How on earth does a programmer of the base class
know what the derived classes may or may not do? If the design
says that a function may be overridden, then he must make it
virtual. If the design says that it cannot be overridden, or
the function is part of the implementation, then it may not be
virtual.
> Then, later, the class does get derived from, the virtual
> isn't put back on, and voila, a difficult to find bug.
If that's the way you run a project, then it really doesn't
matter what the default is, because the program isn't going to
work correctly anyway. In the places I've worked, a programmer
doesn't change an interface on a whim.
I must say, too, that your experience is far different than
mine. In the code I've worked on, it has been very rare that a
virtual function wasn't pure virtual in the base class, or that
an existing definition of a virtual function in a derived class
was further overridden.
> > > Adding in the 'final's would be an optimization.
> > Preventing errors is an optimization? Since when?
> From the perspective that if you take a working, bug free
> program that uses 'final', and remove all the 'final'
> keywords, you will not introduce a bug.
First, that's potentially false for some idioms. You don't
introduce an immediate bug, but you introduce serious
maintenance problems.
> Adding 'final' can expose a bug, but it won't create one.
> Adding 'final's in a working program is often done as an
> optimization, because the compiler has additional information
> about a function it can use to generate better code.
The presence or absense of final is a design decision, just like
the presence or absense of virtual. I've never heard of anyone
removing a virtual because a program was too slow (although I
guess in certain domains it could happen).
> > > This fits in neatly with the idea that the
> > > straightforward, intuitive way is the correct way,
> > The "correct" way depends on the function, but most of the
> > time, it is non-virtual. Unless the class has been designed
> > with virtual specifically in mind for the function in
> > question.
> > Or are you arguing that it is safer to call functions not
> > knowing what they do.
> You and I definitely have a different perspective on how to
> write OOP classes. I view overriding safely as being the
> responsibility of the derived class designer, where as you
> think (I infer) of it as the base class designer's
> responsibility. I don't understand that perspective, however,
> as the base class designer cannot know in advance what a
> derived class' needs and purposes are.
The base class defines a contract. If the base class doesn't
know what is or is not permitted in a derived class, and
document it, no one can use it.
> > What is certain is that almost any flexibility also includes
> > the flexibility to introduce undetected errors, i.e. a lack
> > of safety. To that degree, the two are in opposition, and
> > everything you add to increase flexibility makes safety more
> > difficult.
> That axiom sounds true, and has a certain seductive fatalism
> to it, but I refuse to accept it. In general, I don't buy the
> conventional wisdom that the non-safety issues C++ has are a
> *necessary* byproduct of its power.
Some are, some aren't. C++ certainly isn't a more powerful
language because the order of evaluation in an expression isn't
defined. But flexibility and safety are often antagonistic
concerns.
> > Given that, any language represents a compromise between
> > safety and flexibility.
> I don't accept the axiom as true, so I don't necessarilly
> accept the conclusion <g>. It's also slightly irrelevant in
> this particular discussion, as the virtual by default
> methodology is not less flexible.
It gives implicit flexibility, where the flexibility results in
a loss of safety.
> > And while there are many things I would criticize in C++
> > (the declaration syntax, for starters), I find that it makes
> > a pretty good compromize for a large number of application
> > domains -- you can do some very dynamic programming with it,
> > just as you can do critical systems with it.
> There's no doubt you can do those things with C++, but that
> doesn't at all imply the language cannot be improved, or that
> flexibility would be lost in such improvements.
Certainly. Some things are a win/win, like garbage collection
or specifying the order of evaluation in an expression. Some of
the biggest places for improvement are neutral with regards to
both safety and flexibility -- fixing the declaration syntax,
for example (although anything which makes the code more
predictable and readable indirectly makes programs safer).
There are a lot of things I'd change in C++. (Or would, if I
were designing a new language.) Making functions virtual by
default isn't one of them. I would add an attribute final, so
that virtuality could be turned off in a derived class.
> > And while I'm all in favor of improvements, making functions
> > virtual by default is not IMHO an improvement.
> We'll agree to disagree on that point, then, as I've wasted
> too many hours trying to discover which non-virtual method got
> inadvertantly overridden in a complex OOP heirarchy. This
> isn't just a theoretical issue for me.
Your first statement sounds more like an argument for the C++
situation, however. If the function isn't virtual, it can't get
overridden, inadvertantly or not, in C++. That's precisely the
safety I'm arguing for.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 1:59:32 PM
|
|
On 2005-12-12 12:14, kanze wrote:
> Walter Bright wrote:
:
>> My favorite exception is a standalone DVD player. Since
>> there's no possibility of updating the software on it, I'd
>> rather it soldiered on trying to play the DVD, regardless of
>> if it's displaying corrupted data or not. There's not much
>> worse than having some friends over watching a movie, and
>> having the DVD player quit with "corrupted data!" leaving you
>> to try to skip over it with the fast forward before it hangs
>> at the crucial spot.
>
> As long as the "error recovery" isn't to retry, I can agree with
> you. After all, no body is going to make any important
> decisions based on the corrupt data -- you'll just have some
> scenes in the film which look different from what they were
> meant to. Or?
Well, depending on the corrupt data and who is watching it,
they might get epileptic seizures and die. ;)
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/12/2005 1:59:55 PM
|
|
Dietmar Kuehl wrote:
> gerg wrote:
> > i beleive that a C++ which used the exception behaviour of
> > Java would be great for the language. I the example
> > mentioned above the ignorant programmer would not be able to
> > compile his code using the [] operator unless he caught a
> > "invalid access" exception.
> Java's behavior with respect to exception specifications is
> inherently broken. So is C++'s but the impact in C++ is
> fortunately not at all that severe. Essentially, the problem
> is that exception specifications don't mix at all with generic
> code (here "generic" is used in the sense that any form of
> polymorphism, not only static polymorphism, is used): an
> interface (in the classical meaning of "interface" not Java's
> meaning) accepting polymorphic objects cannot know what kind
> of exceptions may be thrown from these objects.
But it must, to some degree. Otherwise, the LSP doesn't hold,
and you can't use the derived object in place of the base.
There are really several separate issues involved here. The
first is that using exception specifications to describe the
current behavior of a function is an error; exception
specifications describe the contractual guarantees, guarantees
which user code can count on, also in future versions and in
derived classes. As you no doubt know, most of the time, the
only guarantee that is really useful is that a function will not
throw. Anything, ever. So there's generally no point in
specifying exceptions unless it is to say that there will be no
exceptions. (Java's exception specifications are broken because
you cannot guarantee this, ever.)
The second issue is one of syntax; it is quite reasonable, in a
generic class which cannot be used polymorphicly at run-time (so
the LSV doesn't apply) to want to specify the exceptions
according to what exceptions can or cannot be raised by some
other class. In practice, I know that with a sufficient
reserve() beforehand, std::vector<int>::push_back will not
throw. I can imagine code in which this guarantee could be
useful. There is no way syntactically to express it, but that
is a syntax problem, not a problem with the concept itself. To
be honest, I can't even imagine a syntaxe which could express
that guarantee.
Another issue is the question of static vs. dynamic checking.
> However, the user of the interface passing in the polymorphic
> object can know. Any restrictive exception specification does
> no good but is harmfully getting in the way.
> Also, the ignorant programmer would just declare the function
> to throw the base exception class and would not care about
> specific forms of an exception.
Which is probably the best answer in most cases. In practice,
the important distinction is can throw (something or other) vs.
cannot. Most of the time, there is no justification for
contractually limiting what can be thrown, unless it is to say
that nothing can be thrown.
> In the light of using polymorphic entities, the same would be
> done by the well-educated programmer, although due to very
> different reasons than those of the ignorants...
:-). Sounds like a good solution to me. The ignorant
programmer does the right thing by default, without
understanding the issues.
Seriously, on the larger projects I've worked on, the ignorant
programmers were restricted to coding, and only the
well-educated programmers were allowed to specify the contracts
at the interface level.
> Essentially, exception specifications, possibly with the
> exception of a no-throw specification which can be used by the
> optimizer, are a bad idea which is best eliminated from those
> languages which got stuck with them.
A good, working no-throw specification is a powerful tool for
contract enforcement. Given that, I have no bones about
allowing it to also be used to specify limited sets, although I
don't see this as a particularly useful feature.
> If this is not an option, specifying the most generic
> exception is the best thing to do. Of course, this defeats
> the original purpose of exception specifications but the
> original purpose cannot be delivered anyway.
It depends on what you consider the "original purpose". The
original purpose is, IMHO, to specify an enforceable contract.
As such, it is, in the abstract, a good thing. As we've gained
experience with exceptions, it has become evident that (except
maybe for some exceptional cases I'm not familiar with) the only
interesting and useful contract is the nothrow guarantee --
anything beyond that typically restricts flexibility for no gain
in safety or usability.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 2:04:13 PM
|
|
> If crashing would be such a good thing, then why check for errors at all? I
> think only small, short running programs can "live" with a crash. But all
> other programs simply can not accept a crash.
It depends on your error management strategy if you can catch all
exceptions.
How do you think about this approach?
http://en.wikipedia.org/wiki/Crash-only_software
Regards,
Markus
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
12/12/2005 2:04:36 PM
|
|
Walter Bright wrote:
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:1134266461.421226.57930@g43g2000cwa.googlegroups.com...
> > Sure, that's why I favor a "stop the program and help me
> > debug it" behavior, such as a crash plus core dump. I would
> > love it if such behavior was somehow mandated by the
> > standard, but I am also willing to live with it as a QOI
> > issue. I am not in favor of throwing exceptions for these
> > kinds of conditions because not only do they not help me
> > debug the program, they can make the problem worse by
> > masking the bug.
> Sometimes, though, it is necessary for it to throw an
> exception rather than abort. This would be, for example, a
> program that accepts foreign "plugins". The application could
> attempt to sandbox the plugin by catching any exceptions it
> throws and aborting the plugin, not the application.
The obvious answer is that a robust program can't accept a
foriegn plug in:-). Regretfully, it's not very far from the
truth -- my bank won't allow the program which maintains the
accounts to run a plug in I've written, for example.
For those that can, it's a real problem. Because any sort of
memory violation in the plug-in crashes the application. Or
worse.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/12/2005 2:04:57 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> > imho in the context of safe programming the lack of a finally statement
> > should be considered as advantage of c++ to languages like java. raii
> > is inherent in c++ design and a finally statement encourages
> > programming orthogonal to that concept, especially beginners for whom
> > finally is a concept much easier to understand upfront. why allow for
> > circumventing the 'native' c++ way of handling resources with finally
> > for those few cases where it is just awkward to work around without it?
>
> Because often a change to the state of the program can be comfortably
> seen as a resource. Part of the reason for which today's programs behave
> so badly in face of errors is that we don't yet understand what
> programming with error entails, and what language facilities we really
> need. The on_scope_xxx features are, I believe, one step forward. (A
> future step would be a static analysis that inserts them automatically.)
>
> For example, say in a function I need to add an element to a container,
> and then do a few other things, some of which might fail.
>
> void Widget::foo(const bool condition) {
> cont.push_back(elem);
> if (condition) ++counter;
> database.insert(serialize(elem));
> }
>
> How would you express things like undoing adding an element to a
> container with RAII? You'd have to implement VectorPopper. How about the
> conditional increment? You'd have to implement ConditionalDecrementer.
> With on_scope_failure, it's all very easy:
>
> void Widget::foo(const bool condition) {
> cont.push_back(elem);
> on_scope_failure { cont.pop_back(); }
> if (condition) ++counter;
> on_scope_failure { if (condition) --counter; }
> database.insert(serialize(elem));
> }
>
> > afair bjarne stroustrup explains the decision to omit finally in c++
> > and the benefits of raii in 'the design and evolution of c++'.
> > reintroducing finally or variations of it now for the discussed reasons
> > seems contraproductive. why not tackle the problems of current
> > solutions like scopeguards by improving the language to fit such
> > concepts?
>
> RAII is good, and on_scope_xxx does not compete with it.
i like the very natural approach of error handling with your proposed
scope exit handlers. on_scope_failure gives a hook to the required
rollback on occuring exceptions. that being said, while those scope
exit handlers can be usefully applied in addition to raii i still
believe that on_scope_exit does compete in the sense that it might lure
(especially less skilled) developers to do resource management through
sloppy finally{} like cleanup procedures. at least i referenced
bjarne's design document to stress that there seemed to have been
thought about the _danger_ of introducing a block exit handler (your
proposed on_scope_exit, back then plain finally{}) into c++, contrary
to the surely also applying benefits.
> IMHO final is
> fatally flawed because it forces you to change the code *preceding* it,
> thus adding complexity to it and fostering idiomatic uses that are
> coarse-grained and clunky. (Same shortcoming applies to "catch" btw;
> both "catch" and "finally" are flawed because they don't allow
> implementing transactional code easily.) Think of implementing the
> function above in C++:
>
> void Widget::foo(const bool condition) {
> try {
> cont.push_back(elem);
> if (condition) ++counter;
> try {
> database.insert(serialize(elem));
> } catch (...) { if (condition) --counter; throw; }
> } catch (...) { cont.pop_back(); throw; }
> }
>
> You see, the fact that catch (or finally) forces a change (i.e.,
> indentation) on the *normal path* code has fatal repercussions in
> complexity when you try to scale up. The linearized alternative ain't
> nicer at all:
>
> void Widget::foo(const bool condition) {
> int whereAmI = 0;
> try {
> cont.push_back(elem);
> ++whereAmI;
> if (condition) ++counter;
> ++whereAmI;
> database.insert(serialize(elem));
> } catch (...) {
> switch (whereAmI) {
> case 2:
> if (condition) --counter;
> case 1:
> cont.pop_back();
> }
> throw;
> }
> }
>
imho error handling such transactional procedures with your scopeguard
idiom or rollback functors lacks one thing that you already addressed:
the necessity of defining kludgy raii classes like IntDecrementer etc.
with a boost::lambda like library with containable lambda expressions
and rollback_t as container of those lambdas that can be executed on
request, your example could be expressed as follows:
void Widget::foo(const bool condition) {
rollback_t rollback;
try {
cont.push_back(elem);
rollback( (_1.pop_back())(cont) );
if (condition) ++counter;
rollback( if_then(_1, --_2)(condition, counter) );
database.insert(serialize(elem));
}
catch (...) { rollback(); throw; }
}
unfortunately besides the unnatural syntax a boost::lambda like
approach yields a lot of problems for more complicated expressions.
now in a hypothetical c++ that supports first class lambda expressions
the code translates to the following:
void Widget::foo(const bool condition) {
rollback_t rollback;
try {
cont.push_back(elem);
rollback( { cont.pop_back(); } );
if (condition) ++counter;
rollback( { if (condition) --counter; } );
database.insert(serialize(elem));
}
catch (...) { rollback(); throw; }
}
here no kludgy definitions are necessary because they can be
implemented inline.
> > for example i could imagine that my favourite missing feature in c++,
> > namely lambda definitions with automatic scope variable forwarding
> > would allow to implement very convenient scope guards. unfortunately
> > such a feature seems to stay being my favorite _missing_ feature for a
> > long time still... :o)
>
> Finding the "principal abstraction" is indeed a great approach. What's
> needed for on_scope_xxx are lexically nested functions coupled with the
> ability to hook into a scope's exit. Lambdas offer the first thing, and
> (with proper source transformations) catch/finally offer part of the
> second thing.
in all given examples it was on_scope_failure that you exploited to
recover from error conditions, which requires no finally block. while i
really miss such a feature, i cannot see the dire need for
on_scope_exit, where workarounds are imho sufficient in those few cases
where it is necessary to hook at scope exit in both success and error
branches.
>
> What matters is that we know what we're looking for when it comes about
> error handling. Because destructors, try, catch, and finally - those are
> not enough.
it is my believe that a first class lambda would add a lot of diverse
possibilities to the language, also at the error handling domain.
ultimately i would like to see the following:
void Widget::foo(const bool condition) {
transaction {
cont.push_back(elem);
if (condition) ++counter;
database.insert(serialize(elem));
}
}
but then, i seem to be dreaming again... :-)
-- peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
peter
|
12/12/2005 4:54:38 PM
|
|
Torsten Robitzki wrote:
> Peter Most wrote:
>
>> The application I was working on was multithreaded, where the threads
>> where quite independent from each other, meaning if one thread crashes
>> then the program could and should continue to work with the other
>> threads. But the usual behavior for threads makes this very difficult,
>> because if one thread crashes then the complete process is crashing. In
>> this scenario I prefer a thrown exception which I can catch and log in a
>> toplevel handler and then let the tread die gracefully so that the other
>> threads can still continue.
>
> Usually the threads share all the same address space. If the threads are
> really that independent you should use processes without shared memory
> because on modern architectures the hardware will guaranty that on
> process can not interfere with an other.
>
Thanks for the tip ;-) But the threads weren't *completely* independent but
only *quite* independent and still had to access some shared data. This
data were mostly just read and not modified from these threads, so the
danger of corrupting data was minimal. Because it was a kind of plugin
architecture, it was to the customer more important that the other
threads/plugins are still working.
>From this experience it would be great if C++ would check everything and
throws an exception if something is wrong. This is obviously not going to
happen because of speed concerns. But maybe it would be possible to "aid"
an developer in writing very robust frameworks with some standard way of
obtaining a call stack or maybe a hook which gets called in the case of an
illegal memory access. This could be solved in the library and would give
the "lead developer" the possibility to harden the framework against errors
from "ignorant" developers.
After reading and thinking about the discussion here, I now think that this
is probably the more sensible way and this also means that *I* can start
developing a solution and maybe make it even into the Boost library.
Are here some developers who already tried or are trying such an approach
and can give me some pointers/hints ?
>> That's a point I still don't quite understand. If I access the vector
>> with a wrong index and an exception is thrown, how can it be that the
>> program is continuing with wrong data?
>
> Obviously the index is already wrong. So what is the correct value and
> was it the only wrong data?
>
Ah, Yes of course. Sorry, sometimes you don't see the obvious. ;-)
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/12/2005 4:56:09 PM
|
|
Dnia 7 Dec 2005 13:53:36 -0500, Peter Most skrobie:
> > rather see the solution in making appropriate high-level environment in
> > which the program in C++ runs so that every invalid access will end up
> > with an exception. Cheching bounds in [] can be added by an implementation
> > as well, on a direct request.
> >
> > This change in Standard terms would change also existing programs, which
> > is not what "non-beginners" want.
> >
> But would it change existing programs for the worse?
I think it would. If a programmer expected that the index will be verified, it
would use at() explicitly. So it is stated that using [] he already ensured
somehow that the index is correct and does not expect that it will be verified
again. The index verification may cost nothing. May.
> > This thought is still worth taking into consideration. But still the only
> > way that leads to a safe usage of C++ is learning. So if a developer is
> > assigned to a work in a production environment, there must be someone to
> > look at this code and detect such problematic usages. I heard, for
> > example, that inability to overload operators in Java is its advantage.
> > Why? Because it prevents immature developers from doing stupid things.
> I tried to find examples of such "stupid things" and couldn't find a good
> example.
So didn't I, moreover, I doubt that any reasonable example could be find for
that. But this does not stop people telling that.
> The best I could find was some example where a developer overloads
> the '+=' for a container but actually removes an element (or something like
> that). The argument goes on to explain that with methods this doesn't
> happen, completely ignoring that a developer could write an 'add' method
> which also removes an element or formats your harddrive.
> So if an immature developer would overload the '+=' operator in such a way,
> who can say if he wouldn't write the 'add' in a similar way?
Yes, that's right. Invalid use of operator overloading is not much different
of invalid method naming. Also, if the software is being written according to
a specified SPP, every new method is reviewed, so wrong names will be caught
regardless if they are method names or operators.
> The idea was to provide 2 sets of features, like the vector example. One
> safe (default) feature for the beginner i.e. operator[] with range check
> and a second set i.e. at() without range check.
I think the implementation can provide such a behavior on request now yet. The
[] operator is just not guaranteed to verify the index and at() is guaranteed.
This does not force [] operator using invalid index without verification; it
can be reported failure in some other way.
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethourhs(O)wp.pl>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"I am allergic to Java because programming in Java reminds me casting spells"
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sektor
|
12/12/2005 4:56:31 PM
|
|
Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
> message news:dn4o7b$s62$1@kujawiak.man.lodz.pl...
> > For a complicated language (that is, a language that provides a lot of
> > capabilities and programming paradigms) there is only one way to use it
> > correctly: learning. And for "ignorant" developers there's no hope.
> Hardening
> > C++ is not a help for that.
> I must say I disagree with this somewhat fatalistic viewpoint. Complexity
> doesn't necessarilly come from power.
Generally I agree with you; the needs to spend a lot of time with learning is
provided not only by the number of language features, but also by various not
necessary complications.
> It often comes from:
> 1) inconsistency
> 2) a poor match of language design with the paradigm used
> 3) attempts to retain backwards compatibility with obsolete features
> 4) poor understanding of what the problem is
> 5) adherance to ideas that are conventionally assumed to be true, but are
> not
> 6) features that are inconsistent with intuition
> Anyone can design something complicated. Genius is in discovering the
> underlying simplicity and designing to that. A good test of genius in design
> is when, after it is introduced, everyone else slaps their head and thinks
> "of course that's the way it should be, it's obvious."
It is very hard to design the features this way that everyone will take them
as intiutive. This rather can be done by refinement of these features while
they are in usage.
I think C++ was designed best as it was possible regarding all limitations a
language deriving from both C and old-fashion C++ (ARM) should be subject to.
> For example, consider the evolution of the design of guns. They've gotten
> far more capable, reliable, flexible, etc., but are much simpler and safer
> to use. The revolutionary improvements made are so obvious to us now, we
> wonder why nobody thought of it before the genius that did, and we find it
> difficult to imagine making a gun any other way.
Things that most possibly simple and simultaneously most possibly reliable do
not happen often. Regarding Kalashnikov, I think it is rather exception than a
rule. But closer to sofware engineering a good example of simplicity connected
with reliability could be Tcl language. I think it is also an exception,
however.
> Back to C++, and for an example of an inconsistency, consider this example
> from C++ 98 13.4-5:
> -------------------
> struct X {
> int f(int);
> };
> int (X::*p1)(int) = &X::f; // OK
> int (X::*p5)(int) = &(X::f); // error: wrong syntax for pointer to member
> -----------------------------
> This is the only place in C++ where parenthesizing an expression changes its
> semantic meaning other than operator precedence. It's inconsistent, it's
> buried in the spec, and it requires a special kludge in the compiler
> implementation to make it work. I've never found an explanation for it.
So didn't I; gcc telss me just that parentheses must not be placed here. I
believe you give it as an example of incosistency; this is nit a problematic
either, however there are better examples of "weird" meaning of parentheses.
They can be used for both separating expressions and for function calls (and
declarations, simultaneously). For example, if you have "()", there is no
difference between declaration and function call; that's why you cannot
declare an object with default constructor to have (). This is also an
inconsistency, but the only way to prevent it was to force using always
(void).
> Sure, this is very obscure, and even the C++ experts don't know it's there.
> But there are some that do trip people up:
> 1) the well-known template > angle bracket tokenizing problems
> 2) inconsistent behavior between std::string's and quoted string literals
What an inconsistent behavior?
> 3) inconsistent behavior between core arrays, and std::vector
Idem
> 4) inconsistent and counterintuitive name lookup rules for dependent and
> non-dependent names
Better idea?
> 5) two level name lookup
> Are these inconsistencies necessary to get the power? I don't believe so.
I don't think it was better to be done with namespaces as they are in C++.
Also, I think using parameters in <> and arguments in () is a good thing, much
better than any other solution I saw, including templ!(par).
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethourhs(O)wp.pl>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"I am allergic to Java because programming in Java reminds me casting spells"
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sektor
|
12/12/2005 4:56:52 PM
|
|
kanze wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>
>>kanze wrote:
>>
>>>Realistically, all programs must be able to live with a
>>>crash. At least on my system, dereferencing an out of
>>>bounds pointer will cause a crash, as will accessing through
>>>a mis-aligned pointer. And there's really no way to catch
>>>the first of these.
>
>
>>No way an illegal pointer access will cause a crash.
>
>
> It might, and it might not. My point is that there is no way
> you can verify a pointer before accessing it, to be sure that it
> will not crash. So a program has to be able to live with a
> crash.
>
>
>>What if the pointer points to some memory that is within the
>>right address space, but of the wrong type?
>
>
> I didn't say that all errors will cause a crash.
I can't read any other way your statement that you yourself quoted
above: "At least on my system, dereferencing an out of bounds pointer
will cause a crash, as will accessing through a mis-aligned pointer."
"Will" means "will", not "might". By the way, I'm guilty of a similar
semantic mistake: "No way an illegal pointer access will cause a crash."
I meant: "No way an illegal pointer access will always cause a crash." :o)
>>Speaking for me at least, I'm interested in making behavior
>>defined for the "usual" case and allow unsafe performance with
>>extra syntactic effort.
>
>
> I agree, provided the usual case is defined to be something
> useful. Currently, the standard doesn't really have to concept
> of "guaranteed to crash" (except for assert() and, according to
> how you look at it, abort()).
I think the idea that emerges here is that operator[] should be
guaranteed to invoke assert().
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 7:31:29 PM
|
|
Alf P. Steinbach wrote:
> * Andrei Alexandrescu (See Website For Email):
>
>>void foo() {
>> string s1 = "Hello";
>> // At this point, s1 has been fully constructed
>> on_scope_exit { ... can use s1, can't use s2 ... }
>> string s2 = ", world!";
>> on_scope_exit { ... can use s1 and s2 ... }
>>}
>
>
> OK, a kind of (conceptually, at least) dynamic "registration" or
> "activation", as with try blocks: an on_scope_exit will not have effect
> unless the execution has passed over it, and the handlers are executed
> in LIFO order.
Correct. However, it's not really "dynamic". For example, you can't
conditionally register an on_scope_xxx. The behavior is attained simply
by having the compiler shuffle code around.
> Is there a proposal somewhere?
No.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 7:31:52 PM
|
|
kanze wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>>First, of course, the claim that Java has no undefined
>>>behavior is not true. It very definitly has undefined
>>>behavior, explicitly in the case of threading, and in
>>>practice, also because the specification isn't always as
>>>precise as one would like.
>
>
>>Wrong. Check Java 1.5. It defines behavior of even incorrect
>>threaded programs.
>
>
> The last time I looked was Java 1.2. But in practice, this
> rather surprises me; totally defining behavior in a threaded
> context can be very, very costly -- it could potentially require
> a fence between every memory access to ensure order.
No. It was only costly in terms of human effort: it took five years to
develop. :o) Other than that, it's pretty performant.
>>Undefined behavior in Java has a very different meaning.
>
> It means that the program doesn't work the way it is supposed
> to.
>
>>It does NOT mean memory errors.
>
> It does NOT mean that you notice the error immediatly. Which
> increases the probability that you unwittingly use a wrong
> result.
I didn't think I have to argue with you that memory errors are nonlocal
and hard to reproduce, which makes them orders of magnitude harder to
spot than all other errors. Are you now putting memory errors in the
same category as all others?
> The problem I have with undefined behavior in C++ isn't that it
> might cause a core dump. The problem I have with it is that it
> might not cause a core dump.
....but instead some code affecting data/code in a totally different module.
Look, in Java, if I have an out-of-bounds access, (1) there's always an
exception being thrown, (2) I know for sure there are two things that I
need to start looking at: (a) the array, (b) the index. There's no
chance that the array or the index were obliterated by some loose
pointer to double in some function I called five stack frames ago.
Again, do we have to discuss the advantages of memory safety??? It's
clear C++ strives to attain it, and it gives up on it when it might
reduce efficiency. That's all.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 7:32:43 PM
|
|
James Kanze wrote:
>
>
> The issues are, IMHO, orthogonal, and C++ would benefit by
> having some means of saying, now that I've overriden this
> virtual function, I don't want anyone else overriding my
> definition. A way to turn off virtuality, in sum, once it is
> there. But the reason this is useful is precisely because a
> virtual function always represents a certain risk. You take the
> risk when it pays something important in return, but above all,
> when you take the risk, you program according to the known
> presence of that risk; when you analyse your code, you know that
> you cannot count on a particular implementation of the virtual
> function, but only on it's meeting the contract that you have
> programmatically enforced in the non-virtual function which
> calls it.
>
>
...........
> --
> James Kanze mailto: james.kanze@free.fr
> Conseils en informatique orient�e objet/
> Beratung in objektorientierter Datenverarbeitung
> 9 pl. Pierre S�mard, 78210 St.-Cyr-l'�cole, France +33 (0)1 30 23 00 34
Of course, if you want to make your make your class FINAL, there
is a way, invented by Stroustrup - inherit from a VIRTUAL class with
a private constructor
[can be empty, of course, except for the virtual destructor,
and a friend declaration to your class].
This disallows derivation, since any derived class would have to call
the base constructor directly [because of the virtual], but it can't,
since
friendship is NOT inherited.
This doesn't allow individual functions to be declared final,
of course, but does add some restrictions, as needed.
Ihor Kinal
ikinal@ieee.org
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
ikinal
|
12/12/2005 7:33:26 PM
|
|
Peter Most wrote:
> >From this experience it would be great if C++ would check everything and
> throws an exception if something is wrong. This is obviously not going to
> happen because of speed concerns. But maybe it would be possible to "aid"
> an developer in writing very robust frameworks with some standard way of
> obtaining a call stack or maybe a hook which gets called in the case of an
> illegal memory access. This could be solved in the library and would give
> the "lead developer" the possibility to harden the framework against errors
> from "ignorant" developers.
>
> After reading and thinking about the discussion here, I now think that this
> is probably the more sensible way and this also means that *I* can start
> developing a solution and maybe make it even into the Boost library.
>
> Are here some developers who already tried or are trying such an approach
> and can give me some pointers/hints ?
Not a pointer or a hint, but a question. Suppose you have a
checked_vector type that provides such a hook on out of bounds access.
How will the lead developer use the hook? What code can he write that
will harden the framework against errors from ignorant developers?
In other words, an ignorant developer uses the vector in a buggy way.
When the hook is called, all that is known is that an out of bounds
access occurred. What can the hook do to harden the framework against
an error that a) already occurred and b) is built-in to the code (and
so will occur again in the future)?
Above you wrote:
> >From this experience it would be great if C++ would check everything and
> throws an exception if something is wrong. This is obviously not going to
> happen because of speed concerns.
It's not speed concerns that are going to prevent this from happening;
it's that it's a bad idea. If you throw an exception whenever anything
goes wrong, exception safety becomes impossible.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/12/2005 7:50:15 PM
|
|
Andrei Alexandrescu <SeeWebsiteForEmail@moderncppdesign.com> writes:
> Perl has an interesting module [...] that allows you to insert code
> to be executed upon a scope exit, *within the context of the
> caller*. The sheer fact that Perl allows one to implement such a
> module raised my opinion of Perl greatly.
Adding to the roster, Common Lisp has the special operator
UNWIND-PROTECT:
http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm
> 1. on_scope_exit { ... code .. }
This is unwind-protect, as above.
Usage:
(unwind-protect
(do-something-dangerous)
(report-having-attempted-it))
> 2. on_scope_success { ... code ... }
This one sounds too trivial, but here's an attempt built atop PROGN:
(defmacro on-success (protected-form &rest after-forms)
`(progn
,protected-form
,@after-forms))
Usage:
(on-success
(do-something-dangerous)
(do-something-to-celebrate))
> 3. on_scope_failure { ... code ... }
This one can be built atop unwind-protect:
(defmacro with-transaction (protected-form &rest cleanup-forms)
(let ((gsuccessfulp (gensym "successfulp-")))
`(let ((,gsuccessfulp nil))
(unwind-protect
(multiple-value-prog1
,protected-form
(setq ,gsuccessfulp t))
(unless ,gsuccessfulp
,@cleanup-forms)))))
Usage:
(with-transaction
(do-something-dangerous)
(do-something-to-mourn))
--
Steven E. Harris
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Steven
|
12/12/2005 7:51:48 PM
|
|
Peter Most wrote:
>From this experience it would be great if C++ would check everything and
> throws an exception if something is wrong. This is obviously not going to
> happen because of speed concerns. But maybe it would be possible to "aid"
> an developer in writing very robust frameworks with some standard way of
> obtaining a call stack or maybe a hook which gets called in the case of an
> illegal memory access. This could be solved in the library and would give
> the "lead developer" the possibility to harden the framework against errors
> from "ignorant" developers.
Instead of turning c++ in Just Another Scripting Language ;-) You should
consider to use a c++ / Java/Python combination where you write the
speed critical parts with experienced developers in C++. The application
level part could than be written in a more constrained languages where
simple errors might get catched during runtime, like Java or Python.
> After reading and thinking about the discussion here, I now think that this
> is probably the more sensible way and this also means that *I* can start
> developing a solution and maybe make it even into the Boost library.
Good luck ;-)
regards
Torsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Torsten
|
12/12/2005 7:52:39 PM
|
|
peter steiner wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>RAII is good, and on_scope_xxx does not compete with it.
>
>
> i like the very natural approach of error handling with your proposed
> scope exit handlers. on_scope_failure gives a hook to the required
> rollback on occuring exceptions. that being said, while those scope
> exit handlers can be usefully applied in addition to raii i still
> believe that on_scope_exit does compete in the sense that it might lure
> (especially less skilled) developers to do resource management through
> sloppy finally{} like cleanup procedures.
First and foremost, on_scope_xxx are fundamentally different from
finally because they don't require changes on the normal code path, so
they scale much better. Second, today's sloppy programmers don't manage
their resources properly, and on_scope_xxx will be a step forward for them.
Third, define "sloppy". We are incredibly sloppy *today*. Look at most
applications' behavior when an error comes about. We have no general
good practices for error handling today, and I assert that that's also
because of lack of language support. As I've shown, today there is no
reasonably comfortable way of writing an exception-safe function in the
general case. You either write tons of little RAII classes, write try
statements all over, or write incorrect code. None of these is
reasonable, and there's plenty of evidence for that.
> at least i referenced
> bjarne's design document to stress that there seemed to have been
> thought about the _danger_ of introducing a block exit handler (your
> proposed on_scope_exit, back then plain finally{}) into c++, contrary
> to the surely also applying benefits.
I've shown "finally" sucks. It doesn't scale up and doesn't provide a
simple intellectual framework for writing correct code. Plus, I repeat,
at the time C++ was being created, and even today, we don't know what
we're looking for when it comes about error handling. So I'm not that
much sensitized by the argument by authority. :o)
> imho error handling such transactional procedures with your scopeguard
> idiom or rollback functors lacks one thing that you already addressed:
> the necessity of defining kludgy raii classes like IntDecrementer etc.
ScopeGuard is a kludge invented in absence of on_scope_xxx. We've even
written a macro, ON_BLOCK_EXIT, that awkwardly emulates on_scope_exit.
ScopeGuard is limited and awkward because it lacks access to the current
scope; often you need to stop doing what you're doing just to write some
little function that doesn't help anywhere else.
> with a boost::lambda like library with containable lambda expressions
> and rollback_t as container of those lambdas that can be executed on
> request, your example could be expressed as follows:
>
> void Widget::foo(const bool condition) {
> rollback_t rollback;
> try {
> cont.push_back(elem);
> rollback( (_1.pop_back())(cont) );
> if (condition) ++counter;
> rollback( if_then(_1, --_2)(condition, counter) );
> database.insert(serialize(elem));
> }
> catch (...) { rollback(); throw; }
> }
:-& :-&
> now in a hypothetical c++ that supports first class lambda expressions
> the code translates to the following:
>
> void Widget::foo(const bool condition) {
> rollback_t rollback;
> try {
> cont.push_back(elem);
> rollback( { cont.pop_back(); } );
> if (condition) ++counter;
> rollback( { if (condition) --counter; } );
> database.insert(serialize(elem));
> }
> catch (...) { rollback(); throw; }
> }
>
> here no kludgy definitions are necessary because they can be
> implemented inline.
That's more like it, and I suppose the call to rollback() could be put
in a destructor (guarded by a conditional). By and large, I think a
language feature will be used if it fosters writing good code, easily.
Today we can't write transactional code easily. Lambdas make it slightly
easier. What we want is to understand and develop mechanisms that allow
implementing the linear syntax that I've shown. That syntax is the most
intellectually solid and easiest to understand. In your syntax there is
too much mechanism, and that mechanism hides the abstraction.
> in all given examples it was on_scope_failure that you exploited to
> recover from error conditions, which requires no finally block. while i
> really miss such a feature, i cannot see the dire need for
> on_scope_exit, where workarounds are imho sufficient in those few cases
> where it is necessary to hook at scope exit in both success and error
> branches.
"Used", not "exploited" :o). on_scope_exit is there for completeness. It
would be odd to have on_scope_failure and on_scope_success, but no
on_scope_exit. I've given examples that take action on failure, but
there are lots with the other two.
* I want to restore the cursor to normal when exiting this scope.
* I want to delete this file off disk if I'm exiting the scope with
success, otherwise leave it there so it can be consulted.
* I want to set this flag to "true" if I exit this scope successfully.
It would appear that on_scope_success is not really necessary because
the scope's successful exit is often reached naturally by virtue of
control flow. But that's not in sync with a variety of control flow
statements, such as "return", "continue", "break", and, ahem, "goto".
Therefore, code that uses on_scope_success is much easier to read
because it provides a strong guarantee. Also, I've noticed that placing
the "I want this to be done in case..." code earlier rather than later
makes much more sense. One of the so far not understood problems with
try/catch/finally are that it places error handling code in the wrong place.
> it is my believe that a first class lambda would add a lot of diverse
> possibilities to the language, also at the error handling domain.
True.
> ultimately i would like to see the following:
>
> void Widget::foo(const bool condition) {
> transaction {
> cont.push_back(elem);
> if (condition) ++counter;
> database.insert(serialize(elem));
> }
> }
>
> but then, i seem to be dreaming again... :-)
You aren't. I'm glad you are seeing that opportunity. That's the second
step I mentioned after on_scope_xxx gain acceptance: a static analysis
that inserts them automatically. If the compiler knows how to undo every
step in a multi-step operation, it can automatically transform the
operation into a transaction. If it can't (e.g., an I/O operation in the
path), the compiler can inform the programmer exactly what prevents
creating the transaction.
A nice research topic :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/12/2005 7:53:01 PM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134384601.049979.62170@g44g2000cwa.googlegroups.com...
> Ignoring the error and trying to continue is probably acceptable
> in an "entertainment" system. On the other hand, any time the
> system is doing anything important, it's generally better to not
> do it, than to do it wrong. (On a locomotive brake system I
> worked on, the first thing they did when a error was detected
> was cut the power to the processor -- they weren't taking any
> risk of a software error trying to override the manual
> overrides.)
I think we're in complete agreement on this issue.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:00:32 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Thorsten Ottosen wrote:
>
>> Andrei Alexandrescu (See Website For Email) wrote:
>>
>>> kanze wrote:
>>
>>
>>>> The critical applications I've worked on have made
>>>> extensive use of programming by contract, with non-virtual
>>>> public functions enforcing the contract before calling virtual
>>>> private functions -- a technique forbidden in Java, where an
>>>> "interface" can have no implementation code whatever, and
>>>> private functions cannot be virtual.
>>>
>>>
>>>
>>> That's entirely obscure. The same behavior can be achieved through
a ton
>>> of other techniques.
>>>
>>> Abstract classes, dual interfaces, forwarding, package-level hiding,
>>> inner classes,... come to mind.
>>
>>
>> what is dual interfaces?
>>
>> Anyway, all of those things seems like poor hacks. Abstract classes
>> usually won't work because you don't have multiple inheritance.
>
>
>
> Dual interfaces mean, there's one interface for client code and one
> private interface for implementation code.
>
> So... shall we now say that C++ is safe because it allows multiple
> inheritance?!?
No, but we shall say that there is not tons of other techniques to
achieve the same. Not even one.
Whenever flexibility in the language forces you to do repetitive and
redundant tasks, it will affect correctness of the program because of
increased code-size and extra complexity of the code.
-Thorsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thorsten
|
12/13/2005 5:01:16 PM
|
|
"Felipe Magno de Almeida" <felipe.m.almeida@gmail.com> wrote in message
news:1134379149.802009.10820@f14g2000cwb.googlegroups.com...
> Note that if char_traits were declared virtual, it would make
> my_char_traits virtual. In this example, it wouldnt really hurt
> (ignoring performance), but there are times where it does hurt. And if
> it was declared final, I couldnt derive from it, and reuse its static
> functions (or could I?), without having to write all functions and have
> them forward to the right ones.
Static functions aren't virtual anyway, so I don't think this is an issue.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:03:49 PM
|
|
* Andrei Alexandrescu (See Website For Email):
>
> ScopeGuard is a kludge invented in absence of on_scope_xxx. We've even
> written a macro, ON_BLOCK_EXIT, that awkwardly emulates on_scope_exit.
Could you elaborate on how that macro works for the case of
return 42;
in the scope's code?
ON_SCOPE_FAILURE can readily be implemented as a set of macros (as mentioned,
I did that just to see), with some restrictions (mainly, the set of exception
types supported), but it seems that ON_SCOPE_EXIT could not allow free inline
code more than ScopeGuard does, i.e. at most it could allow binding a function
plus its arguments, to be called on block exit via a destructor.
Do you perhaps require using special macros for 'return', or simply not
support 'return' (not to mention 'goto')?
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/13/2005 5:04:31 PM
|
|
"ikinal" <ikinal@ieee.org> wrote in message
news:1134406429.453633.153010@z14g2000cwz.googlegroups.com...
> Of course, if you want to make your make your class FINAL, there
> is a way, invented by Stroustrup - inherit from a VIRTUAL class with
> a private constructor
> [can be empty, of course, except for the virtual destructor,
> and a friend declaration to your class].
> This disallows derivation, since any derived class would have to call
> the base constructor directly [because of the virtual], but it can't,
> since
> friendship is NOT inherited.
-------- C++ ---------------
class ANoDerive
{
ANoDerive() { }
~ANoDerive() { }
friend class A;
};
class A : virtual ANoDerive
{
...
};
----------- D --------------
final class A
{
...
}
---------------------------
Me, I'd rather just put 'final' in front of the class declaration.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:05:05 PM
|
|
Bob Bell wrote:
> Peter Most wrote:
>
>>>From this experience it would be great if C++ would check everything and
>>throws an exception if something is wrong. This is obviously not going to
>>happen because of speed concerns. But maybe it would be possible to "aid"
>>an developer in writing very robust frameworks with some standard way of
>>obtaining a call stack or maybe a hook which gets called in the case of an
>>illegal memory access. This could be solved in the library and would give
>>the "lead developer" the possibility to harden the framework against errors
>>from "ignorant" developers.
>>
>>After reading and thinking about the discussion here, I now think that this
>>is probably the more sensible way and this also means that *I* can start
>>developing a solution and maybe make it even into the Boost library.
>>
>>Are here some developers who already tried or are trying such an approach
>>and can give me some pointers/hints ?
>
>
> Not a pointer or a hint, but a question. Suppose you have a
> checked_vector type that provides such a hook on out of bounds access.
> How will the lead developer use the hook? What code can he write that
> will harden the framework against errors from ignorant developers?
>
> In other words, an ignorant developer uses the vector in a buggy way.
> When the hook is called, all that is known is that an out of bounds
> access occurred. What can the hook do to harden the framework against
> an error that a) already occurred and b) is built-in to the code (and
> so will occur again in the future)?
The problem is the following. With an unchecked operator[], there is the
risk that the error will go undetected for the longest time, having the
erroneous program obliterating random locations in memory (including
over code and data owned by correct modules). With a checked operator[],
the "ignorant developer" will find out about the wrong access as soon as
it is causally possible.
I can't believe it takes so much effort to get this simple point across.
>>>From this experience it would be great if C++ would check everything and
>>throws an exception if something is wrong. This is obviously not going to
>>happen because of speed concerns.
>
> It's not speed concerns that are going to prevent this from happening;
> it's that it's a bad idea. If you throw an exception whenever anything
> goes wrong, exception safety becomes impossible.
Exceptions are defined behavior and hard errors. That's better than
undefined behavior and soft errors. It can be discussed whether stopping
execution is more appropriate. As of now, I'm undecided. Throwing might
not be a bad idea for a large category of applications.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/13/2005 5:06:09 PM
|
|
On 2005-12-12 19:31, Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
:
>> I agree, provided the usual case is defined to be something
>> useful. Currently, the standard doesn't really have to concept
>> of "guaranteed to crash" (except for assert() and, according to
>> how you look at it, abort()).
>
> I think the idea that emerges here is that operator[] should be
> guaranteed to invoke assert().
I think there should be a hook, something like std::unexpected().
(Or ideally, a template policy parameter--not possible with
std::vector without breaking compatibility, I know.)
So the programmer can decide to do what he thinks is most appropriate
for the application. E.g. those who want an exception can throw an
exception.
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/13/2005 5:06:32 PM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134393562.076928.13620@g47g2000cwa.googlegroups.com...
> The obvious answer is that a robust program can't accept a
> foriegn plug in:-).
That's correct. A program that does accept plugins must accept some level of
risk.
> Regretfully, it's not very far from the
> truth -- my bank won't allow the program which maintains the
> accounts to run a plug in I've written, for example.
And correctly so.
> For those that can, it's a real problem. Because any sort of
> memory violation in the plug-in crashes the application. Or
> worse.
Yup.
But we should also consider that an operating system can be thought of as a
program that accepts plugins ("user programs"). The OS would be nearly
unusable if you had to reboot it every time a user program failed. An OS has
some aids, though, in the hardware memory protection.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:06:54 PM
|
|
Steven E. Harris wrote:
> Andrei Alexandrescu <SeeWebsiteForEmail@moderncppdesign.com> writes:
>
>
>>Perl has an interesting module [...] that allows you to insert code
>>to be executed upon a scope exit, *within the context of the
>>caller*. The sheer fact that Perl allows one to implement such a
>>module raised my opinion of Perl greatly.
>
>
> Adding to the roster, Common Lisp has the special operator
> UNWIND-PROTECT:
>
> http://www.lispworks.com/documentation/HyperSpec/Body/s_unwind.htm
Yah, I'd known about unwind-protect, thanks for bringing it to the
discussion!
>>1. on_scope_exit { ... code .. }
>
>
> This is unwind-protect, as above.
>
> Usage:
>
> (unwind-protect
> (do-something-dangerous)
> (report-having-attempted-it))
It's not the same, because the "do-something-dangerous" is included
within the construct (as in "finally"), not apart from it.
unwind-protect is more like "finally": it's hard to scale its usage up -
the parens are gonna kill ya :o).
I'm not sure if it's possible to write a Scheme macro that transforms:
(on-scope-exit (report))
(do-something-dangerous)
into
(unwind-protect
(do-something-dangerous)
(report))
I don't know whether Scheme macros can modify the S-expression
surrounding them. (I don't think so.) You can, however, implement with a
macro:
(on-scope-exit (report)
(do-something-dangerous)
) ;; um ehm ho hum
You see, I've come to think that those extra parens make or break the
entire idiom. (In the C++ case, it would be an extra scope). If you must
include normal code *within* the on_scope_xxx construct, then you can't
scale up.
Oh, speaking of which. What stroke of genius has had the C# creators
introduce "using" as:
using (R r1 = new R()) {
r1.F();
r1.G();
}
instead of:
using (R r1 = new R());
r1.F();
r1.G();
???
So let's recap: we have C++ that allows us to rollback scoped changes as
long as they are self-contained (e.g., a string that allocates memory
and then deallocates it). But C++ makes it awkward to rollback changes
that aren't self-contained (i.e., have to do more with the scope as is
the case for most mutating functions).
We have Java that allows you to rollback scoped changes at the cost of
gratuitouslty indenting normal code. In addition, Java has no means of
automating rollback of self-contained scope changes (i.e., destructors).
We have C# that hoped to say one more word on the matter with the
IDisposable interface and the companion "using" construct. It's not as
good as C++ destructors because it simply takes the gaffe of "finally"
to a whole new level.
We have LISP that has unwind-protect - which is pretty much a "finally"
no matter how you macroize it.
We have D, which does what C++ does (destructors) and what Java does
(finally). It doesn't bring any net news to the table in the particular
matter of writing exception-safe code, although I have to add on a
related note that D implements contracts "so wrong it hurts."
Hmmm... Not a very pretty picture we have here :o).
Sigh.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/13/2005 5:07:16 PM
|
|
"Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
message news:dnk4g2$q1u$1@kujawiak.man.lodz.pl...
> Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
> > Sure, this is very obscure, and even the C++ experts don't know it's
there.
> > But there are some that do trip people up:
>
> > 1) the well-known template > angle bracket tokenizing problems
> > 2) inconsistent behavior between std::string's and quoted string
literals
>
> What an inconsistent behavior?
"hello" + "world"
doesn't work.
> > 3) inconsistent behavior between core arrays, and std::vector
>
> Idem
int array[] = { 1, 2, 3 };
doesn't work for std::vector.
> > 4) inconsistent and counterintuitive name lookup rules for dependent and
> > non-dependent names
>
> Better idea?
Sure. Have them work like functions do. The function arguments are looked up
in the scope of the point of calling, the function body names are looked up
in the point of the function definition. This is how D templates work, and
it works intuitively to the point that the issue *has never even come up*.
It works like one would expect it to, it requires no complicated
explanation, and it never occurs to anyone it should be any other way. There
aren't any crazy kludges like local names cannot have the same name as a
template parameter, or that template base class members aren't found.
> Also, I think using parameters in <> and arguments in () is a good thing,
much
> better than any other solution I saw, including templ!(par).
I presume you have an aesthetic only objection to templ!(par), because there
are no technical problems with it. It has no syntactical or lexical
ambiguities, and is easy to parse. Aesthetics are also a function of what
one is used to, people who try the !() for a while rapidly become accustomed
to it, and it feels natural.
a<b>, on the other hand, always looks to me like some comparison operation
that has gone awry, especially when >> appears as well.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:08:12 PM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134391373.061982.66110@f14g2000cwb.googlegroups.com...
> The idea that all that is necessary for a function to be
> overriden is to declare it virtual, however, is false.
Correct, but I didn't make such a claim.
> > I have, however, many times had the "I shoulda made it
> > virtual" bug, and those bugs are particularly hard to track
> > down.
>
> Funny, but I've never had a bug because I should have declared a
> function virtual, and didn't. Which functions, if any, can be
> overridden is part of the basic design.
The way most programmers code is that "basic design" and coding are
interleaved and an ongoing process. Virtual is often left off of functions
for efficiency reasons. I know I code in a stepwise refinement process.
> > Furthermore, it's the responsibility of the writer of the
> > derived class to 'fit in' with the base class design.
>
> It's the responsibility of the author of the derived class to
> meet the documented contract. If you know how to specify a
> documented contract which can be met with different
> implementations, you do so, and you declare the function
> virtual. But you have to be much more careful with contracts
> when dealing with virtual functions -- when calling the
> function, you don't really know what it does.
You're still coming at it from the idea that the base class, once designed,
is immutable. Maybe you are good enough to get base class design right the
first time, but I am not.
> That doesn't mean that virtual functions are bad -- I use them
> all the time. But it does mean that making a function virtual
> should be a conscious decision on the part of the base class
> designer.
The bugs come in when the design gets changed - and there's no way to tell
by inspection of a C++ class whether its methods are virtual or not, without
carefully examining each of its ancestors. THAT is the source of error.
> > I also know of no cases where one *intentionally* overrides a
> > non-virtual member function with another function. C++'s
> > method does not prevent this kind of error, so how is it
> > safer?
>
> I don't understand what you are asking. You can't override a
> non-virtual member function, intentionally or otherwise.
Sure you can:
struct A { int foo(); };
struct B : A { int foo(); };
is perfectly legal C++.
> On the
> other hand, it's not that rare to provide non-virtual functions
> in the derived class with the same name as non-virtual functions
> in the base class, e.g. to provide looser pre-conditions, for
> the user who knows he is dealing with a derived class.
So, you are saying that in your experience it *never happens* that one
overrode a method thinking it was virtual, and yet it was not? Let me
reiterate that it is not possible, by inspection of class X, to determine if
methods are virtual or not (one must examine all of X's base classes).
It certainly has happened in my experience, and obviously in many other
peoples' experience, otherwise the virtual destructor problem would not get
such attention.
> Barring that, the C++ solution
> favors safety, requiring an explicit mark when the added
> flexibility is desired;
That's just it, it does not require an explicit mark (since virtualness is
inherited). Furthermore, one can imagine one is overriding a virtual
function, but actually not be since one forgot a 'virtual' somewhere in the
inheritance heirarchy. I fail to see how this design favors safety.
> the Java solution favors flexibility,
> requiring an explicit mark when the added safety is required.
That's the first time I've ever seen one post that Java favors flexibility
<g>.
> My experience is that Java programmers tend to forget the final.
> The code works anyway, so who cares. At least, the code works
> for now, as long as no one actually does override the function.
So, Java is less safe than C++ because one can inadvertantly override a
virtual function? I am just not getting why this is less safe than C++, wher
e one can override it inadvertantly in C++ as well as inadvertantly
overriding non-virtual functions.
> On the other hand, a C++ programmer cannot forget the virtual
> where it is needed, because the code won't work.
Oh, he certainly can forget it, and it isn't true that the "code won't
work." What'll happen is that there will be a bug in the code, a hard to
find bug, and a bug that may not manifest itself unless you've got a very
thorough test suite. It can also result in pointer bugs, which may persist
for years without being discovered.
> (The one
> exception is the destructor, where the code may appear to work
> until the class is used in a multiple inheritance lattice, or a
> derived class has a non-trivial destructor. But a good compiler
> will warn if a class has virtual functions, but a non-virtual
> destructor.)
The existence of warnings that a "good compiler" will emit is indicative of
a significant deficiency in the language design.
> > I'm not sure what you're saying there, but the problem in C++
> > comes from overriding a non-virtual function,
> You can't override a non-virtual function. You'll have to
> explain what you mean here.
See above.
> > and the fact that doing so is almost always inadvertent and a
> > bug, exacerbated by:
>
> > 1) virtualness is inherited, meaning you can't look at a
> > function and tell if it is virtual or not without examining
> > all its base classes
>
> That is a bit of a problem. All of the coding guidelines I'm
> familiar with require using the virtual keyword in the derived
> class, but of course, it's easy to forget, and may confuse
> programmers deriving from the derived class.
This is like the warning problem above, using coding guidelines in this
manner is indicative of a deficiency in the language. C++ would be better if
it required the virtual keyword when overriding a virtual function, and if
it disallowed the virtual keyword if overriding a non-virtual function.
> > 2) programmers tend to pull the 'virtual' keyword off of
> > functions in classes that nobody derives from at the moment,
> > for efficiency reasons.
>
> If a programmer insists on being stupid, there's not much you
> can do about it. All I can say is that I've never encountered
> one that stupid.
I do it. Call me stupid, but my compilers run faster than anyone else's.
> Whether a function is virtual or not is part of the contract of
> the class. How on earth does a programmer of the base class
> know what the derived classes may or may not do? If the design
> says that a function may be overridden, then he must make it
> virtual. If the design says that it cannot be overridden, or
> the function is part of the implementation, then it may not be
> virtual.
You put a lot of faith in a design document, and assume that it is correctly
followed. I don't, I put my faith in what the language does or does not
guarantee, and I look at the actual code. Therefore, I need a language where
the design can be expressed unambiguously in the code. I want to be able to
look at the code, and visually verify it against the design document. I
can't do that with C++ for function virtualness (or for several other
characteristics in the language; macros being the most obvious example).
> > Then, later, the class does get derived from, the virtual
> > isn't put back on, and voila, a difficult to find bug.
> If that's the way you run a project, then it really doesn't
> matter what the default is, because the program isn't going to
> work correctly anyway. In the places I've worked, a programmer
> doesn't change an interface on a whim.
> I must say, too, that your experience is far different than
> mine. In the code I've worked on, it has been very rare that a
> virtual function wasn't pure virtual in the base class, or that
> an existing definition of a virtual function in a derived class
> was further overridden.
I agree, our experiences are different. But what I am inferring from your
comments is you rely on a strictly imposed methodology in order to get
safety out of C++. One of the design goals for D is to make it easier to
manage code by making it more inspectable, and hence make it less necessary
to rely on a rigidly imposed (and hence expensive) methodology.
To that end, for methods, by inspection of the method, and only that method,
you can determine:
1) if it is direct or virtual
2) if it can be overridden or cannot be
It isn't necessary to assume that the comments or design document are either
correct or adhered to.
> > > > Adding in the 'final's would be an optimization.
> > > Preventing errors is an optimization? Since when?
> > From the perspective that if you take a working, bug free
> > program that uses 'final', and remove all the 'final'
> > keywords, you will not introduce a bug.
> First, that's potentially false for some idioms.
I don't see how. Example, please?
>You don't introduce an immediate bug, but you introduce serious
> maintenance problems.
If there are serious maintenance problems if you don't use 'final', then you
are implying that those very same *serious* problems exist in C++, as there
is no way to express 'final'. So why is C++ safer?
> The presence or absense of final is a design decision, just like
> the presence or absense of virtual. I've never heard of anyone
> removing a virtual because a program was too slow (although I
> guess in certain domains it could happen).
You have now! I make a living writing programs that run faster than anyone
else's. I use a lot dirtier tricks than that one <g>. Also, in the normal
process of providing tech support for compilers, I see a lot of diverse code
come across my desk. In the process of designing a language, I'm a bit of a
pragmatist in trying to adapt to the way diverse programmers actually work
rather than just how the top 2% work.
Much of the motivation for inspectable code comes from my discussions with
programming managers. Many of them have corporate coding styles which ban
certain C++ constructs because they make the code uninspectable. They want
to be able to look at a piece of code and determine what it does without
having to examine every #include'd header, believing that this reduces costs
and improves reliability. This motivation is also why many projects stick
with C.
Java is much more inspectable, and this shouldn't be discounted when
wondering why a corporate project chooses Java over C++.
> (although anything which makes the code more
> predictable and readable indirectly makes programs safer).
I just argued in favor of that <g>.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/13/2005 5:12:05 PM
|
|
Bob Bell wrote:
> Not a pointer or a hint, but a question. Suppose you have a
> checked_vector type that provides such a hook on out of bounds access.
> How will the lead developer use the hook? What code can he write that
> will harden the framework against errors from ignorant developers?
>
> In other words, an ignorant developer uses the vector in a buggy way.
> When the hook is called, all that is known is that an out of bounds
> access occurred. What can the hook do to harden the framework against
> an error that a) already occurred and b) is built-in to the code (and
> so will occur again in the future)?
Your are right, this can't prevent the error from happening again, but it
can do something more sensible then simply crashing, like logging the error
and then end the thread gracefully. From what I've read here, I can say
that I wouldn't want to change the semantics of at() and operator[]
anymore :-O, because I also believe now, that throwing an exception only
invites even more bad practices from "ignorant" developers.
So maybe a different approach solves the problem. When we look at possible
error handling strategies we basically have:
1) Functions returning an error indication.
2) Functions throwing exceptions.
Both ways invite you to continue or at least they are not preventing it.
But for fatal errors which *must* not continue, like an invalid index, we
could call an error handler which, if it's not altered, calls abort(). The
inadequacy of this approach led to exception handling, but for this kind of
errors it could be a good solution.
I doubt/hope that not even an "evil ignorant" developer would rather
write an on_out_of_range_handler() then try to think what is wrong. But
even if he would, since there is something like a set_out_of_range_handler()
function, the lead developer could search for any occurrences of it.
Kind regards Peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
12/13/2005 5:14:13 PM
|
|
kanze wrote:
>
> If you can guarantee that it only hiccups, fine. If you don't
> know the state, however, you can't guarantee that. And privacy
> laws insist that it is better to cut the communication than to
> accidentally have it audible to others.
>
> I've worked on fixed line telephone systems, so in this case, I
> can speak from actual experience. In case of doubt, you crash.
> The hardware is designed to maintain existing communications
> during reboot, and it is better to not allow new connections
> than to run the risk of accidentally silently connecting two
> conversations.
Then France is in better shape than the US. Five or so years ago a
friend of mine surfed a glitch in the phone system for ~8 hours--he
would be attached to a random telephone call until that call ended,
then the system would attach him to a new random call. And while it
was a fairly novel experience (he mediated an argument between a couple
or two), it's not something that should be possible to occur. So I
agree that more attention should be paid to verifiably correct behavior
for systems that the user expects to "just work," which seems to be a
larger portion of the software market every day. And having also
worked on phone systems, my experience has been that users seem to be
more tolerant of an occasional dropped connection than they are of
unpredictable behavior--the former has a simple and fast remedy
(redial), while the latter can be confusing and is likely to result in
concern about privacy, billing issues, and customer service calls to
explain the errnat behavior.
Sean
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sean
|
12/13/2005 5:14:57 PM
|
|
kanze wrote:
> Walter Bright wrote:
>
> > Furthermore, it's the responsibility of the writer of the
> > derived class to 'fit in' with the base class design.
>
> It's the responsibility of the author of the derived class to
> meet the documented contract. If you know how to specify a
> documented contract which can be met with different
> implementations, you do so, and you declare the function
> virtual. But you have to be much more careful with contracts
> when dealing with virtual functions -- when calling the
> function, you don't really know what it does.
I'm not sure I see your point. If the programmers of the base and
derived classes are supplying and meeting contracts, what does it
matter whether the default behavior is virtual or non-virtual
inheritance? Is it merely the expectation that some programmers will
not pay the proper attention to detail? And assuming it is, I would
argue that neither method is better than the other--both will cause
different problems that are difficult to track down.
> That doesn't mean that virtual functions are bad -- I use them
> all the time. But it does mean that making a function virtual
> should be a conscious decision on the part of the base class
> designer.
I do see what you're driving at, but this seems mostly a matter of
experience to me. I can't think of many classes I've designed which
would have broken in odd ways if more of the functions had been
virtual. I suppose this is because I tend not to use function calls to
determine properties of implementation details that I intend to
manipulate directly in base class code?
A more salient issue to me is that since all visible functionality is a
part of an object's interface, consistent behavior should be expected
regardless of whether the function is virtual or non-virtual. With
this in mind, my experience with the C++ model has been that the
behavior of same-named non-virtual functions may differ greatly between
base and derived classes (because less attention is paid to ensuring
consistency for non-virtual functions), and this seems to violate the
"is a" expectation. Sure, manipulating each class is "safe" given the
C++ model, but it can also lead to confusing behavior for the
uncautious user. I hope that defaulting to virtual inheritance may
inspire programmers to take a bit more care with preserving interface
consistency through an inheritance tree.
> > If the base class designer creates a method that shouldn't be
> > overridden, he tags it with 'final'. And as you say, there's
> > no way in C++ to tag a function as "do not override", though I
> > don't agree it is orthogonal.
>
> Perhaps the safest solution is to require every function to be
> marked one way or the other:-). Barring that, the C++ solution
> favors safety, requiring an explicit mark when the added
> flexibility is desired; the Java solution favors flexibility,
> requiring an explicit mark when the added safety is required.
It's worth noting that overriding private functions is now allowed in
D, so the chance of accidentally (or intentionally) altering
implementation-level behavior is not possible, assuming typical coding
practices.
> My experience is that Java programmers tend to forget the final.
> The code works anyway, so who cares. At least, the code works
> for now, as long as no one actually does override the function.
I actually like this, as it makes it more likely I'll be able to
override behavior the designer didn't think to allow explicitly. This
is a sometimes refreshing change from the C++ world where extending
class functionality is often not possible to do safely, simply because
the designer didn't think to add the virtual qualifer to the dtor or to
some speficic method. But I do agree that it can lead to confusing
behavior for the unwary.
> > I'm not sure what you're saying there, but the problem in C++
> > comes from overriding a non-virtual function,
>
> You can't override a non-virtual function. You'll have to
> explain what you mean here.
I think he was referring to what I mentioned above about interface
consistency.
> > Then, later, the class does get derived from, the virtual
> > isn't put back on, and voila, a difficult to find bug.
>
> If that's the way you run a project, then it really doesn't
> matter what the default is, because the program isn't going to
> work correctly anyway. In the places I've worked, a programmer
> doesn't change an interface on a whim.
You're lucky then. Some of the places I've worked haven't had much
enforcement of coding standards.
> > You and I definitely have a different perspective on how to
> > write OOP classes. I view overriding safely as being the
> > responsibility of the derived class designer, where as you
> > think (I infer) of it as the base class designer's
> > responsibility. I don't understand that perspective, however,
> > as the base class designer cannot know in advance what a
> > derived class' needs and purposes are.
>
> The base class defines a contract. If the base class doesn't
> know what is or is not permitted in a derived class, and
> document it, no one can use it.
I suppose I'm with Walter on this one. It's easy to design a class
without concern for what behavior should and should not be overridden,
but it's difficult to derive from a class without paying some attention
to these issues. That places much of the responsibility on the derived
class designer IMO.
Sean
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sean
|
12/13/2005 5:15:37 PM
|
|
Walter Bright wrote:
> The problem with that approach is that a non-virtual destructor
> *implies* one is not meant to derive from the class, but it is
> implication only.
I disagree. All that a non-virtual destructor implies ist that one is
not meant to delete objects through the base class.
--
Gerhard Menzl
#dogma int main ()
Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
12/13/2005 6:35:22 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> peter steiner wrote:
> > Andrei Alexandrescu (See Website For Email) wrote:
> >>RAII is good, and on_scope_xxx does not compete with it.
> >
> >
> > i like the very natural approach of error handling with your proposed
> > scope exit handlers. on_scope_failure gives a hook to the required
> > rollback on occuring exceptions. that being said, while those scope
> > exit handlers can be usefully applied in addition to raii i still
> > believe that on_scope_exit does compete in the sense that it might lure
> > (especially less skilled) developers to do resource management through
> > sloppy finally{} like cleanup procedures.
>
> First and foremost, on_scope_xxx are fundamentally different from
> finally because they don't require changes on the normal code path, so
> they scale much better. Second, today's sloppy programmers don't manage
> their resources properly, and on_scope_xxx will be a step forward for them.
>
> Third, define "sloppy". We are incredibly sloppy *today*.
agreed. i meant sloppy resource management in addition to sloppy error
handling (an additional problem in many language, contrary to c++ where
this is solved with raii). that is how i understood the point of the
quoted argument, but the argument does not justify either position in
this discussion anyways.
> Look at most
> applications' behavior when an error comes about. We have no general
> good practices for error handling today, and I assert that that's also
> because of lack of language support. As I've shown, today there is no
> reasonably comfortable way of writing an exception-safe function in the
> general case. You either write tons of little RAII classes, write try
> statements all over, or write incorrect code. None of these is
> reasonable, and there's plenty of evidence for that.
>
> > at least i referenced
> > bjarne's design document to stress that there seemed to have been
> > thought about the _danger_ of introducing a block exit handler (your
> > proposed on_scope_exit, back then plain finally{}) into c++, contrary
> > to the surely also applying benefits.
>
> I've shown "finally" sucks. It doesn't scale up and doesn't provide a
> simple intellectual framework for writing correct code. Plus, I repeat,
> at the time C++ was being created, and even today, we don't know what
> we're looking for when it comes about error handling. So I'm not that
> much sensitized by the argument by authority. :o)
naturally, as most would consider yourself an authority too... :-)
>
> > imho error handling such transactional procedures with your scopeguard
> > idiom or rollback functors lacks one thing that you already addressed:
> > the necessity of defining kludgy raii classes like IntDecrementer etc.
>
> ScopeGuard is a kludge invented in absence of on_scope_xxx. We've even
> written a macro, ON_BLOCK_EXIT, that awkwardly emulates on_scope_exit.
> ScopeGuard is limited and awkward because it lacks access to the current
> scope; often you need to stop doing what you're doing just to write some
> little function that doesn't help anywhere else.
>
> > with a boost::lambda like library with containable lambda expressions
> > and rollback_t as container of those lambdas that can be executed on
> > request, your example could be expressed as follows:
> >
> > void Widget::foo(const bool condition) {
> > rollback_t rollback;
> > try {
> > cont.push_back(elem);
> > rollback( (_1.pop_back())(cont) );
> > if (condition) ++counter;
> > rollback( if_then(_1, --_2)(condition, counter) );
> > database.insert(serialize(elem));
> > }
> > catch (...) { rollback(); throw; }
> > }
>
> :-& :-&
>
> > now in a hypothetical c++ that supports first class lambda expressions
> > the code translates to the following:
> >
> > void Widget::foo(const bool condition) {
> > rollback_t rollback;
> > try {
> > cont.push_back(elem);
> > rollback( { cont.pop_back(); } );
> > if (condition) ++counter;
> > rollback( { if (condition) --counter; } );
> > database.insert(serialize(elem));
> > }
> > catch (...) { rollback(); throw; }
> > }
> >
> > here no kludgy definitions are necessary because they can be
> > implemented inline.
>
> That's more like it, and I suppose the call to rollback() could be put
> in a destructor (guarded by a conditional). By and large, I think a
> language feature will be used if it fosters writing good code, easily.
> Today we can't write transactional code easily. Lambdas make it slightly
> easier. What we want is to understand and develop mechanisms that allow
> implementing the linear syntax that I've shown. That syntax is the most
> intellectually solid and easiest to understand. In your syntax there is
> too much mechanism, and that mechanism hides the abstraction.
you are right. your syntax is very sound, complicating the issue just
distracts from the purpose.
implementing the beast ontop of an existing parsers though looks like a
daunting task.
>
> > in all given examples it was on_scope_failure that you exploited to
> > recover from error conditions, which requires no finally block. while i
> > really miss such a feature, i cannot see the dire need for
> > on_scope_exit, where workarounds are imho sufficient in those few cases
> > where it is necessary to hook at scope exit in both success and error
> > branches.
>
> "Used", not "exploited" :o). on_scope_exit is there for completeness. It
> would be odd to have on_scope_failure and on_scope_success, but no
> on_scope_exit. I've given examples that take action on failure, but
> there are lots with the other two.
>
> * I want to restore the cursor to normal when exiting this scope.
>
> * I want to delete this file off disk if I'm exiting the scope with
> success, otherwise leave it there so it can be consulted.
>
> * I want to set this flag to "true" if I exit this scope successfully.
>
> It would appear that on_scope_success is not really necessary because
> the scope's successful exit is often reached naturally by virtue of
> control flow. But that's not in sync with a variety of control flow
> statements, such as "return", "continue", "break", and, ahem, "goto".
im still not convinced that on_scope_success and on_scope_exit are that
much an improvement. the complexity of different control flows is the
reason why i already avoid these statements whenever possible. a scope
with a single point of exit is generally both more comprehensible and
failsafe.
your exit handlers clearly resolve the second issue, as it is possible
to write safe code with them without worrying where and when scope is
left. the first issue still remains: it is very difficult to track what
happens when scope is left if hooks are scattered all around a more
complex procedure. the logical argument would be that complex code is
usually a sign of missing code modularity, but imho complex code is
still bound to occur.
this issue does not apply to on_scope_failure, at least in the way you
have used it to implement rollback. here it is not necessary to track
the hooks at first because it always resolves to reverting the
modifications already executed.
> Therefore, code that uses on_scope_success is much easier to read
> because it provides a strong guarantee. Also, I've noticed that placing
> the "I want this to be done in case..." code earlier rather than later
> makes much more sense.
i totally agree that this is the case while developing new code. are
you sure this is still true when maintaining code some time afterwards?
but well, you can never have the good of both sides. i can imagine that
the opportunities arising from inline exit handlers are well worth the
problems that i see from the perspective of current development
practise.
> One of the so far not understood problems with
> try/catch/finally are that it places error handling code in the wrong place.
>
> > it is my believe that a first class lambda would add a lot of diverse
> > possibilities to the language, also at the error handling domain.
>
> True.
>
> > ultimately i would like to see the following:
> >
> > void Widget::foo(const bool condition) {
> > transaction {
> > cont.push_back(elem);
> > if (condition) ++counter;
> > database.insert(serialize(elem));
> > }
> > }
> >
> > but then, i seem to be dreaming again... :-)
>
> You aren't. I'm glad you are seeing that opportunity. That's the second
> step I mentioned after on_scope_xxx gain acceptance: a static analysis
> that inserts them automatically. If the compiler knows how to undo every
> step in a multi-step operation, it can automatically transform the
> operation into a transaction. If it can't (e.g., an I/O operation in the
> path), the compiler can inform the programmer exactly what prevents
> creating the transaction.
>
> A nice research topic :o).
does that imply we can expect more about these pearls from your
direction in the future? :-)
-- peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
peter
|
12/13/2005 10:53:46 PM
|
|
Alf P. Steinbach wrote:
> * Andrei Alexandrescu (See Website For Email):
>
>>ScopeGuard is a kludge invented in absence of on_scope_xxx. We've even
>>written a macro, ON_BLOCK_EXIT, that awkwardly emulates on_scope_exit.
>
> Could you elaborate on how that macro works for the case of
>
> return 42;
>
> in the scope's code?
http://erdani.org/publications/cuj-12-2000.html
> ON_SCOPE_FAILURE can readily be implemented as a set of macros (as mentioned,
> I did that just to see), with some restrictions (mainly, the set of exception
> types supported), but it seems that ON_SCOPE_EXIT could not allow free inline
> code more than ScopeGuard does, i.e. at most it could allow binding a function
> plus its arguments, to be called on block exit via a destructor.
>
> Do you perhaps require using special macros for 'return', or simply not
> support 'return' (not to mention 'goto')?
I think there's again a misunderstanding somewhere. I don't like
ON_SCOPE_FAILURE for reasons I mentioned, so saying again that it's good
"with some restrictions" won't help any. I don't like my own
"ON_BLOCK_EXIT" because it's implemented on top of ScopeGuard, which I
don't like either, because it doesn't have access to the current scope.
Yes, ON_BLOCK_EXIT does not support inline code, and I never claimed it
did. And that's that.
Again, ScopeGuard is a lame paliative for the highly desirable
on_scope_xxx feature. Its usefulness comes from us needing and lacking
the true on_scope_xxx feature.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/13/2005 10:54:11 PM
|
|
Walter Bright wrote:
> To that end, for methods, by inspection of the method, and only that method,
> you can determine:
>
> 1) if it is direct or virtual
> 2) if it can be overridden or cannot be
>
> It isn't necessary to assume that the comments or design document are either
> correct or adhered to.
That convinced me that default virtual, manifest final is objectively
the better approach. Figuring out a lot by looking at a little is very
useful in projects of all sizes (including on Mars - hello James! -
where programmers are perfect, projects have documentation that's always
up to date and actually read in time, invalid pointer dereferences
result in superb crashes, and multiple inheritance of implementation
leads to wonderful safety).
I thought for a while about making some similar claim about the way
overriding works in C++, but I couldn't.
By the way, Walter, you are making a little terminology mistake that
will cost you later. You are saying that:
struct A { int foo(); };
struct B : A { int foo(); };
is overriding, when it actually is "name hiding in a misguided attempt
to override". So it's not called "overriding". In a typical Usenet
manner, you're not being explained that right away; you are left to
expand on your mistake, and only after a few more posts will you be
ridiculed :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/13/2005 10:59:25 PM
|
|
Peter Most wrote:
> Bob Bell wrote:
>
> > Not a pointer or a hint, but a question. Suppose you have a
> > checked_vector type that provides such a hook on out of bounds access.
> > How will the lead developer use the hook? What code can he write that
> > will harden the framework against errors from ignorant developers?
> >
> > In other words, an ignorant developer uses the vector in a buggy way.
> > When the hook is called, all that is known is that an out of bounds
> > access occurred. What can the hook do to harden the framework against
> > an error that a) already occurred and b) is built-in to the code (and
> > so will occur again in the future)?
>
> Your are right, this can't prevent the error from happening again, but it
> can do something more sensible then simply crashing, like logging the error
> and then end the thread gracefully.
Once a bug has occurred, there's no way you can count on anything
ending gracefully. It may be possible to do in many situations, but you
still need to be prepared for more drastic ends.
> From what I've read here, I can say
> that I wouldn't want to change the semantics of at() and operator[]
> anymore :-O, because I also believe now, that throwing an exception only
> invites even more bad practices from "ignorant" developers.
It does't require an ignorant developer to make exceptions a bad
choice. It's quite legitimate in many contexts to have a catch (...) in
the code (for example, at a module, language or OS boundary). That can
make an exception thrown because of a bug go unnoticed.
Sometimes it's perfectly valid to swallow and ignore an exception, and
in fact the design of exceptions in C++ lets me make that choice if
it's appropriate. However, I don't think it's _ever_ appropriate to
ignore a bug.
> So maybe a different approach solves the problem. When we look at possible
> error handling strategies we basically have:
> 1) Functions returning an error indication.
> 2) Functions throwing exceptions.
> Both ways invite you to continue or at least they are not preventing it.
> But for fatal errors which *must* not continue, like an invalid index, we
> could call an error handler which, if it's not altered, calls abort(). The
> inadequacy of this approach led to exception handling, but for this kind of
> errors it could be a good solution.
The problem is that we've been using the word "error" for two different
concepts: run time failures, which a program anticipates and can be
expected to recover from, and programmer errors, which by definition a
program cannot be expected to recover from.
Exceptions and error codes are useful ways of reporting run time
failures, but are a terrible choice for reporting programmer errors.
> I doubt/hope that not even an "evil ignorant" developer would rather
> write an on_out_of_range_handler() then try to think what is wrong. But
> even if he would, since there is something like a set_out_of_range_handler()
> function, the lead developer could search for any occurrences of it.
On the other hand, if there is no on_out_of_range_handler(), then we
don't have to worry about ignorant developers trying to customize it
inappropriately.
I agree, though, that it would be great if the standard mandated more
conditions under which programmer errors are detected. I think some
kind of customizable assertion facility would be very useful --
provided that it couldn't be customized so that it allows a program to
continue, similar to set_terminate().
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/13/2005 11:03:46 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
[...]
> I believe three constructs would simplify writing robust code:
> 1. on_scope_exit { ... code .. }
> Executes code upon the current scope's exit. Several
> on_scope_exit blocks in a scope are executed LIFO.
> 2. on_scope_success { ... code ... }
> Executes code if the current scope is exited normally (no
> exception engendered).
> 3. on_scope_failure { ... code ... }
> Executes code if the current scope is exited via an exception.
> With such language constructs, writing robust programs would
> be much easier. Programmers can easily change the state of
> the program and leave safeguards behind them to ensure things
> are properly cleaned up in case something fails down the road.
> Little RAII classes built just for the sake of undoing stuff
> (I've seen "IntDecrementer" in my time...) would not be
> necessary anymore. Such a construct is so useful, it makes
> ScopeGuard very popular in spite of its awkwardness.
My informal __local_context class proposals have generally
included syntactical sugar for a class with nothing but a
destructor. I'd usually called it cleanup, but I think I like
on_scope_exit better (and it is less likely to conflict with an
existing symbol, if it is made a keyword). An advanced Google
search in this group for articles containing __local_context,
lambda and cleanup, with me as author, should turn it up. (The
URL I've got is
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/22e80cc27bdb2db7/f7b9617322a89809?lnk=st&q=lambda+cleanup+group%3Acomp.lang.c%2B%2B.moderated+author%3Akanze&rnum=2&hl=en#f7b9617322a89809
But given it's length, I wouldn't count on it getting through.)
I've raised the suggestion several times, but no one has ever
seemed interested. And I've never found the time to write it up
into a formal proposal myself.
I'm not sure how to handle the other two. Perhaps the same
technique, but with an implicit if on uncaught_exception(). But
I agree that they would be nice -- especially for checking
post-conditions (with different post-conditions in case of an
exception).
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/13/2005 11:04:29 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
I get the feeling that we actually agree on most points, and
that we're actually just arguing about nits:-).
> > Andrei Alexandrescu (See Website For Email) wrote:
> >>>First, of course, the claim that Java has no undefined
> >>>behavior is not true. It very definitly has undefined
> >>>behavior, explicitly in the case of threading, and in
> >>>practice, also because the specification isn't always as
> >>>precise as one would like.
> >>Wrong. Check Java 1.5. It defines behavior of even incorrect
> >>threaded programs.
> > The last time I looked was Java 1.2. But in practice, this
> > rather surprises me; totally defining behavior in a threaded
> > context can be very, very costly -- it could potentially
> > require a fence between every memory access to ensure order.
> No. It was only costly in terms of human effort: it took five
> years to develop. :o) Other than that, it's pretty performant.
I'll have to look at it. But I wonder -- what is the defined
behavior of writing a long in one thread, and reading it in
another, without any synchronization? The only way I can think
of giving this defined behavior is using some sort of fence or
barrier -- or maybe even a full lock. And that is expensive.
> >>Undefined behavior in Java has a very different meaning.
> > It means that the program doesn't work the way it is
> > supposed to.
> >>It does NOT mean memory errors.
> > It does NOT mean that you notice the error immediatly.
> > Which increases the probability that you unwittingly use a
> > wrong result.
> I didn't think I have to argue with you that memory errors are
> nonlocal and hard to reproduce, which makes them orders of
> magnitude harder to spot than all other errors.
I'm not sure. Threading errors can be bitches as well.
> Are you now putting memory errors in the same category as all
> others?
In a way. An error is an error. Some are harder to track down
than others, and memory errors are often in this case. But on
the whole, a program is correct, or it isn't. I tend to take a
very black and white view about this. (And I have a very low
tolerance for program errors. Regardless of where they come
from.)
> > The problem I have with undefined behavior in C++ isn't that
> > it might cause a core dump. The problem I have with it is
> > that it might not cause a core dump.
> ...but instead some code affecting data/code in a totally
> different module.
Quite. And even then, not necessarily with a crash.
Note, however, that the Therac-25 error was not a memory error,
and it did not result in a program crash. Memory errors aren't
the only ones we have to watch out for.
Preconditions on the critical functions could have prevented the
deaths in the Therac-25 case.
> Look, in Java, if I have an out-of-bounds access, (1) there's
> always an exception being thrown, (2) I know for sure there
> are two things that I need to start looking at: (a) the array,
> (b) the index. There's no chance that the array or the index
> were obliterated by some loose pointer to double in some
> function I called five stack frames ago.
Note that this argument is independant of whether we raise an
exception, or core dump. Almost -- by the time we catch the
exception, the context causing the incorrect index may have been
lost.
But if I'm not mistaken, you're point is more to the effect that
we can pretty much exclude a bad pointer elsewhere as the cause
of the bad index. Which is true, as far as it goes. But I'm
not sure that I see the relevance. I certainly don't see it
with regards to the points we disagree on: I think we're both on
record as favoring both bounds checking and garbage collection.
(I happen to think that garbage collection is even more
important than bounds checking -- if you can speak of "more
important" when talking about two things that are both
essential.)
> Again, do we have to discuss the advantages of memory
> safety??? It's clear C++ strives to attain it, and it gives
> up on it when it might reduce efficiency. That's all.
Do we have to discuss the advantages of any added safety?
Unless it really does have prohibitive costs, which is rarely
the case. And while performance has occasionnally been given as
the reason, much of the time, I think that simply inertia and C
compatibility and traditions are as big a factor. I've started
using garbage collection regularly with C++, for example, and
I've yet to encounter a measurable slow-down.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/13/2005 11:04:51 PM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> kanze wrote:
> > Andrei Alexandrescu (See Website For Email) wrote:
> >>kanze wrote:
> >>>Realistically, all programs must be able to live with a
> >>>crash. At least on my system, dereferencing an out of
> >>>bounds pointer will cause a crash, as will accessing
> >>>through a mis-aligned pointer. And there's really no way
> >>>to catch the first of these.
> >>No way an illegal pointer access will cause a crash.
> > It might, and it might not. My point is that there is no
> > way you can verify a pointer before accessing it, to be sure
> > that it will not crash. So a program has to be able to live
> > with a crash.
> >>What if the pointer points to some memory that is within the
> >>right address space, but of the wrong type?
> > I didn't say that all errors will cause a crash.
> I can't read any other way your statement that you yourself
> quoted above: "At least on my system, dereferencing an out of
> bounds pointer will cause a crash, as will accessing through a
> mis-aligned pointer."
> "Will" means "will", not "might". By the way, I'm guilty of a
> similar semantic mistake: "No way an illegal pointer access
> will cause a crash." I meant: "No way an illegal pointer
> access will always cause a crash." :o)
Yes. I was responding in a specific context, and wasn't being
very careful with my words. My point was that dereferencing an
out of bounds pointer can cause a crash, *and* there is no way
to test before the dererence that the pointer is out of bounds.
My response was in reaction to the statement: what if the
program isn't able to live with a crash. That's all -- the
statement has no real meaning outside of that context.
> >>Speaking for me at least, I'm interested in making behavior
> >>defined for the "usual" case and allow unsafe performance
> >>with extra syntactic effort.
> > I agree, provided the usual case is defined to be something
> > useful. Currently, the standard doesn't really have to
> > concept of "guaranteed to crash" (except for assert() and,
> > according to how you look at it, abort()).
> I think the idea that emerges here is that operator[] should
> be guaranteed to invoke assert().
An assert which cannot be turned off by simply defining NDEBUG,
yes.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/13/2005 11:05:42 PM
|
|
Thorsten Ottosen wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>>So... shall we now say that C++ is safe because it allows multiple
>>inheritance?!?
>
> No, but we shall say that there is not tons of other techniques to
> achieve the same. Not even one.
Ehm. Somehow I don't remember of a situation in which I told myself,
"Gosh! Good thing we have inheritance of implementation. Otherwise this
whole project would fall apart due to its lack of safety!" Do you? Does
anyone?
> Whenever flexibility in the language forces you to do repetitive and
> redundant tasks, it will affect correctness of the program because of
> increased code-size and extra complexity of the code.
Of course. The sky is blue, motherhood is good, and humans make errors
on repetitive and redundant tasks. How does that relate to the issue
being discussed?
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/13/2005 11:08:01 PM
|
|
Dnia 13 Dec 2005 12:08:12 -0500, Walter Bright skrobie:
> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
> message news:dnk4g2$q1u$1@kujawiak.man.lodz.pl...
> > Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
> > > Sure, this is very obscure, and even the C++ experts don't know it's
> there.
> > > But there are some that do trip people up:
> >
> > > 1) the well-known template > angle bracket tokenizing problems
> > > 2) inconsistent behavior between std::string's and quoted string
> literals
> >
> > What an inconsistent behavior?
> "hello" + "world"
> doesn't work.
It depends if you criticize a language that was created from scratch, or you
criticize a language, which had to be most possibly compatible with C
language.
Another thing is where it is a problem. Of course, you cannot do:
"hello" + "world"
but you can do:
"hello" "world"
Also, you can't do:
hl + "world"
if hl is of const char* type, but you can use std::string type for hl and it
works.
What else is a problem?
> > > 3) inconsistent behavior between core arrays, and std::vector
> >
> > Idem
> int array[] = { 1, 2, 3 };
> doesn't work for std::vector.
That's only because std::vector has no such constructor (or something
similar). boost::array has:
boost::array<int,4> a = { 1, 2, 3 };
> > > 4) inconsistent and counterintuitive name lookup rules for dependent and
> > > non-dependent names
> >
> > Better idea?
> Sure. Have them work like functions do. The function arguments are looked up
> in the scope of the point of calling, the function body names are looked up
> in the point of the function definition. This is how D templates work, and
> it works intuitively to the point that the issue *has never even come up*.
> It works like one would expect it to, it requires no complicated
> explanation, and it never occurs to anyone it should be any other way. There
> aren't any crazy kludges like local names cannot have the same name as a
> template parameter, or that template base class members aren't found.
Show me an example. I do not exactly understand what you mean.
> > Also, I think using parameters in <> and arguments in () is a good thing,
> much
> > better than any other solution I saw, including templ!(par).
> I presume you have an aesthetic only objection to templ!(par), because there
> are no technical problems with it. It has no syntactical or lexical
> ambiguities, and is easy to parse. Aesthetics are also a function of what
> one is used to, people who try the !() for a while rapidly become accustomed
> to it, and it feels natural.
If I were a compiler creator, maybe this wouldn't bother me. But as long as I
am a user of compilers, this looks wrong to me. The ! here does not mean
anything significant for me, as well as passing parameters (not arguments)
still in parentheses looks too similar for passing arguments.
Consider: std::min<unsigned>( a, b );
> a<b>, on the other hand, always looks to me like some comparison operation
> that has gone awry, especially when >> appears as well.
'>>' won't appear. And a<b> will not look like 'less' as long as you use
spaces around, if you mean operator, and no spaces if you mean template
instantiation :)
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethourhs(O)wp.pl>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"I am allergic to Java because programming in Java reminds me casting spells"
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Sektor
|
12/13/2005 11:25:21 PM
|
|
> You put a lot of faith in a design document, and assume that it is correctly
> followed. I don't, I put my faith in what the language does or does not
> guarantee, and I look at the actual code. Therefore, I need a language where
> the design can be expressed unambiguously in the code. I want to be able to
> look at the code, and visually verify it against the design document. I
> can't do that with C++ for function virtualness (or for several other
> characteristics in the language; macros being the most obvious example).
Can anything from DSM and MDD technologies help in this discussion
about design and strategy decisions to improve the useful abstraction
level for the desired applications?
http://en.wikipedia.org/wiki/Domain-specific_modelling
Are you looking for specific enhancements to be able to specify
detailed constraints that will be automatically checked with
corresponding tool support?
Are there any features that do not fit into the paradigma of the
current language definition?
Regards,
Markus
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Markus
|
12/13/2005 11:27:06 PM
|
|
kanze wrote:
> Dietmar Kuehl wrote:
>> Java's behavior with respect to exception specifications is
>> inherently broken. So is C++'s but the impact in C++ is
>> fortunately not at all that severe. Essentially, the problem
>> is that exception specifications don't mix at all with generic
>> code (here "generic" is used in the sense that any form of
>> polymorphism, not only static polymorphism, is used): an
>> interface (in the classical meaning of "interface" not Java's
>> meaning) accepting polymorphic objects cannot know what kind
>> of exceptions may be thrown from these objects.
>
> But it must, to some degree. Otherwise, the LSP doesn't hold,
> and you can't use the derived object in place of the base.
This seems like a reasonable exception for LSP to be be required:
As long as a derived class does not throw from a method which is
assumed not to throw, I think it is OK to throw arbitrary
exceptions. Exceptions unknown to the framework would just pass
through the framework and could be handled by the surrounding
code which called framework functions with a setup trigging these
unknown exceptions.
The only reasonable need for conformance to a certain exception
specification would be a framework method which does not throw
but somehow calls generic code. Such a method could not possibly
handle unknown exceptions. Of course, such a method would be a
bad idea in the first place. A non-throwing framework method could
only reasonably call into functions which are required not to throw.
Reading the text over, I think we are complete agreement about
exception specifications :-)
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/14/2005 3:11:00 AM
|
|
James Kanze wrote:
> And of course, unless you're a much better coder than I am, such
> checks aren't necessarily redundant during the development
> phase.
I don't think it is much of an issue of being better or worse. The
STL-way to formulate your algorithms reduces the potential of doing
stupid things dramatically and I have become very used to check my
iterators against past-the-end iterator prior to access. This mostly
stylistic change in coding resulted in no out-of-bounds errors (as
tested with purify) for the last few years.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/14/2005 3:11:32 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> How about the standard library? Should it just leave operator[] with
> undefined behavior and wash its hands?
Yes. I wouldn't be opposed to variations on the classes with mandated
contract checks which could optionally be used. I would be opposed to
always mandating the checks, at least until other potential for
damage arising from the core language is also removed or until it can
be proved that the mandated checks come at zero costs (in terms of
executable size, run-time, memory occupation, etc.) on all platforms
where C++ is available: if the core language constructs could still
cause damage, there is no point in trying to be rigorous about the
library. If people complain about the lack of mandated checking they
are free to choose a different design philosophy for their language
of choice and use e.g. Java or they can find a C++ compiler vendor
who guarantees to implement all checks.
> I don't know. I do think, however, that the risk is low. Destructors
> tend to clean modular data structures, and less often affect global
> state. I know you can come with examples to the contrary, and then
> I can
> come with examples to the contrary of the contrary. Absent hard data,
> we'll reach a diplomatic impasse :o).
I think we already established that we disagree in our view on how
to cope with detected programming errors. I have made my point quite
clear (i.e. stated that the only viable response to a programming
error is to abort as soon as possible) but I will neither try to
convince others nor bless other approaches.
>> Handle them to what end? To continue with corrupted program state?
>> I doubt that you can come up with carefully designed modules which
>> recover from yet unknown programming errors (if the programming
>> errors were known, it would be easier to fix them than to create a
>> module recovering from them).
>
> That's not true.
The only assertion I made which can be considered to be false is that
it is easier to fix known errors than to create code recovering from
them. Is that what you refer to?
> I know of systems that must continue execution with a
> known error for various reasons.
I don't dispute this. However, note that in this case you don't have
the opportunity to create a specific handler for the known error
because, as you stated, the system must continue execution. My
assertion stated you cannot reasonably handle unknown errors and if
the error is a known programming error, it is easier to fix that than
to add specific recovery for this programming error.
Things become indeed interesting when the known programming error is
in a third party library. It might be tempting to try to work around
these but there are only two possible situations:
1. You have the source code and you can fix the problem.
2. You don't have the source code and you cannot determine all
effects of the programming error.
In case 1. it is clear that it is easier to fix the library and in
case 2. I would claim that you either have to get a fix from the
third party or use a different approach. Continuing after unknown
corruptions is plainly too dangerous.
> For example, the fix + deployment takes
> a long time, is impractical, or impossible (embedded applications). Or
> because the application uses multiple, relatively independent
> threads -
> a corrupt thread does not necessarily corrupt the state of the entire
> application.
If you cannot prove that the corrupt thread has not corrupted any
data unrelated to this thread it may be save to continue execution.
How do you prove this for an unknown programming error detected at
run-time? In C++ I don't think you can prove this at all. Instead,
you have to assume that any byte, including e.g. thread local storage
of unrelated threads, is corrupted. You might be able to prove that
there is no corruption if all access are checked which in turn would
require a rather different than the current C++ execution model.
--
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
<http://www.eai-systems.com> - Efficient Artificial Intelligence
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dietmar
|
12/14/2005 3:13:06 AM
|
|
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail@moderncppdesign.com> wrote in message
news:IrG7BI.pzL@beaver.cs.washington.edu...
> Again, ScopeGuard is a lame paliative for the highly desirable
> on_scope_xxx feature. Its usefulness comes from us needing and lacking
> the true on_scope_xxx feature.
If you could write up a more detailed treatment of exactly how it
works, I'd
be seriously interested in it.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/14/2005 3:14:11 AM
|
|
"Andrei Alexandrescu (See Website For Email)"
<SeeWebsiteForEmail@moderncppdesign.com> wrote in message
news:IrG8AG.qIn@beaver.cs.washington.edu...
> In a typical Usenet
> manner, you're not being explained that right away; you are left to
> expand on your mistake, and only after a few more posts will you be
> ridiculed :o).
That wouldn't be the first time, and likely won't be the last <g>.
That's
the risk of posting here; one's mistakes are there for all to see, and
thanks to google, will remain forever.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/14/2005 3:18:43 AM
|
|
"Gerhard Menzl" <gerhard.menzl@hotmail.com> wrote in message
news:439ec706$1@news.kapsch.co.at...
> Walter Bright wrote:
>
> > The problem with that approach is that a non-virtual destructor
> > *implies* one is not meant to derive from the class, but it is
> > implication only.
>
> I disagree. All that a non-virtual destructor implies ist that one is
> not meant to delete objects through the base class.
The problem is, there is no way to tell by reading the code if the
programmer intended it that way or if it was a mistake. It *is* a common
programming mistake, enough so that it is often mentioned in C++ programming
guidelines.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/14/2005 6:09:43 AM
|
|
Andrei Alexandrescu <SeeWebsiteForEmail@moderncppdesign.com> writes:
> It's not the same, because the "do-something-dangerous" is included
> within the construct (as in "finally"), not apart from it.
Yes, you're right. I wasn't quite sure about your requirement for
scoping, so I thought I'd bring it up and see how it compares among
the other constructs you're evaluating.
> unwind-protect is more like "finally": it's hard to scale its usage
> up - the parens are gonna kill ya :o).
Well, rarely would any more than one unwind-protect form be present in
a single function, but I do agree that it's a lot like "finally".
My realy point in bringing it up wasn't so much to marvel at the
unwind-protect primitive itself, but to show how other operators could
be built on top of it with perfect syntactic fidelity. Perhaps that's
more a testament to the powerful macro facility than to unwind-protect
itself.
> I'm not sure if it's possible to write a Scheme macro that transforms:
>
> (on-scope-exit (report))
> (do-something-dangerous)
>
> into
>
> (unwind-protect
> (do-something-dangerous)
> (report))
>
> I don't know whether Scheme macros can modify the S-expression
> surrounding them. (I don't think so.)
I can only speak to the Common Lisp and ELisp macro facilities, and
neither would allow this transformation. The macro-expansion operates
on one or more forms supplied as arguments; it can't reach "up and
out" into the surrounding AST.
> You can, however, implement with a macro:
>
> (on-scope-exit (report)
> (do-something-dangerous)
> ) ;; um ehm ho hum
Note that that's just unwind-protect with a lone cleanup form:
(defmacro on-scope-exit (cleanup-form &body body)
`(unwind-protect
(progn ,@body)
,cleanup-form))
(pprint (macroexpand
'(on-scope-exit (report)
(do-something-dangerous))))
-> (UNWIND-PROTECT (PROGN (DO-SOMETHING-DANGEROUS)) (REPORT))
> You see, I've come to think that those extra parens make or break
> the entire idiom.
Yes, I see that you're picky about this point.
> If you must include normal code *within* the on_scope_xxx construct,
> then you can't scale up.
Can you clarify what you mean by scaling up here? I don't see the
problem with wrapping protection around the suspect calls, but I
concede that this wrapping is of greater syntactic significance in a
C-like language given its formatting conventions (more indentation,
perhaps more empty lines save for an open/closing brace).
[...]
> We have LISP that has unwind-protect - which is pretty much a
> "finally" no matter how you macroize it.
True, but oh what macroizing can be done.
[...]
> I have to add on a related note that D implements contracts "so
> wrong it hurts."
Can you elaborate?
--
Steven E. Harris
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Steven
|
12/14/2005 6:11:30 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Bob Bell wrote:
> > Not a pointer or a hint, but a question. Suppose you have a
> > checked_vector type that provides such a hook on out of bounds access.
> > How will the lead developer use the hook? What code can he write that
> > will harden the framework against errors from ignorant developers?
> >
> > In other words, an ignorant developer uses the vector in a buggy way.
> > When the hook is called, all that is known is that an out of bounds
> > access occurred. What can the hook do to harden the framework against
> > an error that a) already occurred and b) is built-in to the code (and
> > so will occur again in the future)?
>
> The problem is the following. With an unchecked operator[], there is the
> risk that the error will go undetected for the longest time, having the
> erroneous program obliterating random locations in memory (including
> over code and data owned by correct modules).
Perhaps I've been unclear; I don't advocate an unchecked operator[].
There seem to be two issues that are getting jumbled up: whether
operator[] should check its argument, and whether operator[] should
throw an exception when its argument is out of bounds. I'm all for
checking, and against throwing.
Right now, we have two indexing functions, operator[] and at(). With
at(), an out of bounds index is OK, it's part of the designed behavior
of the function. With operator[], an out of bounds index is a bug. I
think this is a useful distinction to make, but I don't have a strong
opinion on whether the behavior should be reversed.
The reason I like the distinction is because I think it's useful to
have an indexing operation that _never_ throws, so I can use it in
contexts where an exception can't be tolerated (i.e., a destructor or
other no-throw function, or in the "modify without throwing" half of a
strongly exception safe function). I want to write as much no-throw
code as possible, so the more operations I can count on not throwing,
the better.
Despite that, however, I would like the no-throw indexing operation to
check indices and take appropriate action (where my version of
"appropriate action" is "stop the program and tell the programmer what
went wrong"). Right now, operator[] doesn't do any checking (as far as
the standard is concerned), and that's a problem. I'm all for adding
more language to the standard to mandate more sanity checks of this
type.
> With a checked operator[],
> the "ignorant developer" will find out about the wrong access as soon as
> it is causally possible.
Provided that checking doesn't involve throwing an exception.
> I can't believe it takes so much effort to get this simple point across.
I actually don't think we disagree as much as it might seem, but that's
the way online discussions seem to go sometimes. ;-)
> >>>From this experience it would be great if C++ would check everything and
> >>throws an exception if something is wrong. This is obviously not going to
> >>happen because of speed concerns.
> >
> > It's not speed concerns that are going to prevent this from happening;
> > it's that it's a bad idea. If you throw an exception whenever anything
> > goes wrong, exception safety becomes impossible.
>
> Exceptions are defined behavior and hard errors. That's better than
> undefined behavior and soft errors.
There are errors and there are errors. One difficulty with this
discussion is that the word "error" tends to get used for two separate
concepts: run time failures and programmer errors.
Run time failures are conditions that a program can anticipate, and for
which the program can be designed to have a well-defined response. Note
that when a run time error is detected, it is often at a fairly
low-level point which lacks the context to be able to decide how to
handle it. That context typically exists higher up the call chain in a
higher-level function. Thus, throwing an exception is a natural way to
communicate the problem from the point of detection to the point at
which it can be handled.
Programmer errors, however, carry the program into an unanticipated,
undefined state; by definition, there can be no well-defined response.
Typically, when a programmer error occurs, it is a very local problem;
there is no higher-level context that can deal with it. And when it's
not a local problem (as you've pointed out in other messages in this
thread), it's likely because some other buggy code somewhere else
scribbled into memory inappropriately. Again, there is no higher-level
context that can deal with this. Since using exceptions always transmit
control to an enclosing scope, it doesn't gain us anything to use them
when a bug is detected.
Sorry for the lecturing tone, but I wanted to highlight how these two
kinds of errors are different with respect to exceptions. All IMHO, of
course.
> It can be discussed whether stopping
> execution is more appropriate. As of now, I'm undecided. Throwing might
> not be a bad idea for a large category of applications.
It's my belief that the opposite is true; that throwing is a good idea
for only a small category of applications. Throwing when a bug is
detected has the following bad consequences:
* it allows stack unwinding to occur; because of the bug, this might
do more damage
* unwinding the stack destroys information that might help you
understand the bug
* a catch(...) can make the bug go unnoticed
* it allows any arbitrary code (including the code that contains the
bug) to execute again (because execution can be returned to a normal
path after the bug exception is "handled")
* it overloads the purpose of exception handling -- dealing with run
time failures as well as programmer errors -- making the exception
handling design more complex
* most of the time, a bug is a local problem; there is typically
little or nothing a catcher could do to understand, avoid, work around,
or otherwise deal with a bug that occurs in some (possibly deeply
nested) function
If you have an application for which these aren't important concerns
(and I'm sure there are probably some), then by all means throw when a
bug is detected. But I think that such applications are few and far
between.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/14/2005 6:13:02 AM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134492853.088622.45710@z14g2000cwz.googlegroups.com...
> Do we have to discuss the advantages of any added safety?
> Unless it really does have prohibitive costs, which is rarely
> the case. And while performance has occasionnally been given as
> the reason, much of the time, I think that simply inertia and C
> compatibility and traditions are as big a factor. I've started
> using garbage collection regularly with C++, for example, and
> I've yet to encounter a measurable slow-down.
I've often gotten significant performance *improvements* when switching to
garbage collection, for several reasons:
1) no longer any need for memory bookkeeping code being executed (like
reference counters).
2) no longer any need for "when in doubt, make a copy".
3) it becomes possible to 'slice' arrays rather than make copies.
4) for many types of programs, you can ditch the gc cleanup pass completely.
It's a myth that explicit memory allocation is faster than gc, although it's
true that a gc program will use more heap memory (like double).
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/14/2005 12:07:29 PM
|
|
"Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
message news:dnnhb4$12s$1@kujawiak.man.lodz.pl...
> Dnia 13 Dec 2005 12:08:12 -0500, Walter Bright skrobie:
>
> > "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
> > message news:dnk4g2$q1u$1@kujawiak.man.lodz.pl...
> > > Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
> > > > Sure, this is very obscure, and even the C++ experts don't know it's
> > there.
> > > > But there are some that do trip people up:
> > >
> > > > 1) the well-known template > angle bracket tokenizing problems
> > > > 2) inconsistent behavior between std::string's and quoted string
> > literals
> > >
> > > What an inconsistent behavior?
>
> > "hello" + "world"
>
> > doesn't work.
>
> It depends if you criticize a language that was created from scratch, or
you
> criticize a language, which had to be most possibly compatible with C
> language.
I understand why the inconsistencies are there. Nevertheless, they *are*
there, and they are a problem.
> Another thing is where it is a problem. Of course, you cannot do:
> "hello" + "world"
> but you can do:
> "hello" "world"
True, but you cannot do:
s1 s2
where s1 and s2 are std::string's. It's inconsistent.
> What else is a problem?
printf("%s\n", "hello");
printf("%s\n", s); // s is an std::string
The root problem is that "string" and std::string are different types. They
behave differently, they overload differently, they react to operators
differently, etc.
> > > > 3) inconsistent behavior between core arrays, and std::vector
> > int array[] = { 1, 2, 3 };
> > doesn't work for std::vector.
>
> That's only because std::vector has no such constructor (or something
> similar). boost::array has:
>
> boost::array<int,4> a = { 1, 2, 3 };
Yeah, but you can't resize that one. In general, that's the problem - a data
type may have, for example, 5 necessary characteristics. Between the core
type, the std type, and boost type I may get 5, but I never seem to get all
5 in the same type. Each one has some sort of gotcha to it.
> > > > 4) inconsistent and counterintuitive name lookup rules for dependent
and
> > > > non-dependent names
> > > Better idea?
> > Sure. Have them work like functions do. The function arguments are
looked up
> > in the scope of the point of calling, the function body names are looked
up
> > in the point of the function definition. This is how D templates work,
and
> > it works intuitively to the point that the issue *has never even come
up*.
> > It works like one would expect it to, it requires no complicated
> > explanation, and it never occurs to anyone it should be any other way.
There
> > aren't any crazy kludges like local names cannot have the same name as a
> > template parameter, or that template base class members aren't found.
>
> Show me an example. I do not exactly understand what you mean.
>From C++98 14.6.1-3:
A template parameter shall not be redeclared within its scope (including
nested scopes).
A template parameter shall not have the same name as the template name.
[Example:
template<class T, int i> class Y {
int T; // error: template parameter redeclared
void f() {
char T; // error: template parameter redeclared
}
};
>From C++98 14.6.2-3:
In the definition of a class template or in the definition of a member of
such a template that appears outside
of the template definition, if a base class of this template depends on a
template parameter,
the base class scope is not examined during name lookup until the class
template is instantiated. [Example:
typedef double A;
template<class T> B {
typedef int A;
};
template<class T> struct X : B<T> {
A a; // a has type double
};
There's more, but that'll do to illustrate the point. C++ is this way to
support preparsing of templates, but I don't agree that it had to be that
way (preparsing is unnecesary).
> > > Also, I think using parameters in <> and arguments in () is a good
thing,
> > much
> > > better than any other solution I saw, including templ!(par).
>
> > I presume you have an aesthetic only objection to templ!(par), because
there
> > are no technical problems with it. It has no syntactical or lexical
> > ambiguities, and is easy to parse. Aesthetics are also a function of
what
> > one is used to, people who try the !() for a while rapidly become
accustomed
> > to it, and it feels natural.
>
> If I were a compiler creator, maybe this wouldn't bother me. But as long
as I
> am a user of compilers, this looks wrong to me. The ! here does not mean
> anything significant for me,
One big reason why D uses ! as a binary operator is precisely because it
does not already have a conventional meaning as such in other languages.
> > a<b>, on the other hand, always looks to me like some comparison
operation
> > that has gone awry, especially when >> appears as well.
>
> '>>' won't appear.
It will according to a proposal for the next C++ standard that is almost a
sure thing to get adopted.
> And a<b> will not look like 'less' as long as you use
> spaces around, if you mean operator, and no spaces if you mean template
> instantiation :)
Relying on whitespace to infer meaning is not reliable (it's as unreliable
as comments are). I've seen too much code come across my desk with erratic,
inconsistent whitespace. For a code review, or a bug hunt, you cannot take
anything for granted. Consider:
a<b>c;
Is it a declaration of c of type a<b>? Or is it an unusual use of < operator
overloading (considering the overloading of << and >> by iostreams, I do
not consider this farfetched)? Can't tell by inspection.
a!(b)c;
is unambiguous, regardless of whitespace. No need to schlep through megs of
...h files and layers of abstractions to figure it out.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/14/2005 12:09:05 PM
|
|
* Andrei Alexandrescu (See Website For Email):
> Alf P. Steinbach wrote:
> > * Andrei Alexandrescu (See Website For Email):
> >
> >>ScopeGuard is a kludge invented in absence of on_scope_xxx. We've even
> >>written a macro, ON_BLOCK_EXIT, that awkwardly emulates on_scope_exit.
> >
> > Could you elaborate on how that macro works for the case of
> >
> > return 42;
> >
> > in the scope's code?
>
> http://erdani.org/publications/cuj-12-2000.html
ScopeGuard, yes: it seemed you referred to something more.
> > ON_SCOPE_FAILURE can readily be implemented as a set of macros (as mentioned,
> > I did that just to see), with some restrictions (mainly, the set of exception
> > types supported), but it seems that ON_SCOPE_EXIT could not allow free inline
> > code more than ScopeGuard does, i.e. at most it could allow binding a function
> > plus its arguments, to be called on block exit via a destructor.
> >
> > Do you perhaps require using special macros for 'return', or simply not
> > support 'return' (not to mention 'goto')?
>
> I think there's again a misunderstanding somewhere. I don't like
> ON_SCOPE_FAILURE for reasons I mentioned, so saying again that it's good
> "with some restrictions" won't help any.
Let's nail down that misunderstanding... ;-) I'm not saying that my
ON_SCOPE_FAILURE macro is good: I'm saying the proposed on_scope_failure
language feature is currently implementable as an ugly, limited and
inefficient macro-based and convention-based library solution. And that in
contrast, the other two inline code mechanisms you proposed,
on_scope_success and on_scope_exit, seem to require language support.
Does the "I don't like ON_SCOPE_FAILURE" mean you don't like the
ON_SCOPE_FAILURE macro (that kind of kludgy implementation) or does it mean
you don't like your own proposed on_scope_failure language facility?
If the latter, is the reason for proposing it then a sense of logical
completeness, or what?
> I don't like my own
> "ON_BLOCK_EXIT" because it's implemented on top of ScopeGuard, which I
> don't like either, because it doesn't have access to the current scope.
> Yes, ON_BLOCK_EXIT does not support inline code, and I never claimed it
> did. And that's that.
>
> Again, ScopeGuard is a lame paliative for the highly desirable
> on_scope_xxx feature. Its usefulness comes from us needing and lacking
> the true on_scope_xxx feature.
Much of the point of the existing exception handling constructs is to move
error and failure handling code out of the normal case code, and restrict
its access to things explicitly provided for it.
on_scope_failure does the opposite, and so is undesirable IMHO.
on_scope_success moves normal case code back to where it belongs, inline,
and by doing that also means that error/failure-handling code can be given
more limited access things, and so on_scope_success is desirable on that
count, IMHO. On the other hand, on_scope_success also means that one cannot
see from the code's placement what has executed successfully before that
code: code that comes later in the apparent linear flow may actually execute
first. I find that unpalatable, but then I'm generally a SESE fan.
on_scope_exit mixes normal case code and error/failure handling code, or at
least allows and encourages that. I think that's undesirable, like
'finally'. And if history is anything to go by, a language feature that's
not clearly desirable is probably undesirable for the programmers, or if not
that, it complicates the language specification just to give a conceptually
clean picture, like local references to const initialized with rvalues.
In summary, as I see it, the one construct that's readily (but not
efficiently) implemented without language support, namely on_scope_failure,
is undesirable, and the two features that are not clearly undesirable or
desirable, on_scope_success and on_scope_exit, with the former possibly of
great value to some programmers, seemingly require language support.
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/14/2005 12:10:33 PM
|
|
peter steiner wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>
> agreed. i meant sloppy resource management in addition to sloppy error
> handling (an additional problem in many language, contrary to c++ where
> this is solved with raii). that is how i understood the point of the
> quoted argument, but the argument does not justify either position in
> this discussion anyways.
True. All I can say at this point is that I have the gut feeling that
the advantages of such a feature would dwarf the opportunity of misusing
them. Oh, and I can add that ScopeGuard also can be misused, yet people
don't seem to misuse it - and that precisely because its idiomatic usage
is also the simplest and most straightforward.
>>I've shown "finally" sucks. It doesn't scale up and doesn't provide a
>>simple intellectual framework for writing correct code. Plus, I repeat,
>>at the time C++ was being created, and even today, we don't know what
>>we're looking for when it comes about error handling. So I'm not that
>>much sensitized by the argument by authority. :o)
>
> naturally, as most would consider yourself an authority too... :-)
Ehm. That wasn't my thought!
> implementing the beast ontop of an existing parsers though looks like a
> daunting task.
It's not too hard if you own the parser. Don't forget that the blocks
are statically scoped and that try/catch and destructors already provide
most of the mechanics.
> im still not convinced that on_scope_success and on_scope_exit are that
> much an improvement. the complexity of different control flows is the
> reason why i already avoid these statements whenever possible. a scope
> with a single point of exit is generally both more comprehensible and
> failsafe.
The on_exit_xxx blocks don't add exit points nor control flows; they add
activities to be done upon scope exit and can't be introduced
dynamically (e.g., you can't say, "if a condition is true add this
on_scope_xxx" or "for each element in a container add this on_scope_xxx").
So in that sense, exceptions are much more disruptive because they do
introduce more points of exit, and worse, not readily visible to the
function implementer. The on_scope_xxx construct provides a solid
foundation for reining back in when exception do their deeds.
> your exit handlers clearly resolve the second issue, as it is possible
> to write safe code with them without worrying where and when scope is
> left. the first issue still remains: it is very difficult to track what
> happens when scope is left if hooks are scattered all around a more
> complex procedure. the logical argument would be that complex code is
> usually a sign of missing code modularity, but imho complex code is
> still bound to occur.
I think a fair comparison of said code must be done with equivalent
code. Of course code that takes zero contingency measure will be
shorter. But the six-liners I showcased with on_scope_xxx is
bulletproof. As I've shown, the equivalent code is by necessity more
verbose, less correct, or both. :o)
> this issue does not apply to on_scope_failure, at least in the way you
> have used it to implement rollback. here it is not necessary to track
> the hooks at first because it always resolves to reverting the
> modifications already executed.
And that's the idiomatic usage, the easiest, and the most natural for
the feature.
>>Therefore, code that uses on_scope_success is much easier to read
>>because it provides a strong guarantee. Also, I've noticed that placing
>>the "I want this to be done in case..." code earlier rather than later
>>makes much more sense.
>
>
> i totally agree that this is the case while developing new code. are
> you sure this is still true when maintaining code some time afterwards?
Fortunately I do have experience with it. I can say it's been a
lifesaver for my rather hefty programs in Perl and Z-shell. Actually
it's so useful I put my on_scope_xxx support in a file called
"Basix.pm"/"Basix.sh", together with an Enforce primitive (much like
assert). I don't care writing any nontrivial amount of code without
availing myself of these two features (even though Perl actually has
destructors, too).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/14/2005 12:10:59 PM
|
|
Walter Bright wrote:
>>I disagree. All that a non-virtual destructor implies ist that one is
>>not meant to delete objects through the base class.
>
> The problem is, there is no way to tell by reading the code if the
> programmer intended it that way or if it was a mistake. It *is* a
> common programming mistake, enough so that it is often mentioned in
> C++ programming guidelines.
And there is a guideline which, if adhered to, doesn't leave any doubt
about the intention of the author of the base class: if deletion through
the base class is intended, make the destructor public and virtual,
otherwise make it protected and non-virtual.
--
Gerhard Menzl
#dogma int main ()
Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
12/14/2005 12:51:59 PM
|
|
peter steiner wrote:
> imho error handling such transactional procedures with your scopeguard
> idiom or rollback functors lacks one thing that you already addressed:
> the necessity of defining kludgy raii classes like IntDecrementer etc.
IntDecrementer type classes would be hidden in the background in some
library you just "use".
> with a boost::lambda like library with containable lambda expressions
> and rollback_t as container of those lambdas that can be executed on
> request, your example could be expressed as follows:
> void Widget::foo(const bool condition) {
> rollback_t rollback;
> try {
> cont.push_back(elem);
> rollback( (_1.pop_back())(cont) );
> if (condition) ++counter;
> rollback( if_then(_1, --_2)(condition, counter) );
> database.insert(serialize(elem));
> }
> catch (...) { rollback(); throw; }
> }
Rather ugly and overuse of operator() overloading. It's very difficult
for me to see immediately what this code is doing even if I do get the
hang of these funny _1 and _2 expressions (which I believe are global
objects and therefore not thread-safe anyway).
There are slightly tidier ways to get rollback-able expressions.
> unfortunately besides the unnatural syntax a boost::lambda like
> approach yields a lot of problems for more complicated expressions.
>
> now in a hypothetical c++ that supports first class lambda expressions
> the code translates to the following:
>
> void Widget::foo(const bool condition) {
> rollback_t rollback;
> try {
> cont.push_back(elem);
> rollback( { cont.pop_back(); } );
> if (condition) ++counter;
> rollback( { if (condition) --counter; } );
> database.insert(serialize(elem));
> }
> catch (...) { rollback(); throw; }
> }
>
> here no kludgy definitions are necessary because they can be
> implemented inline.
>
> it is my believe that a first class lambda would add a lot of diverse
> possibilities to the language, also at the error handling domain.
>
> ultimately i would like to see the following:
>
> void Widget::foo(const bool condition) {
> transaction {
> cont.push_back(elem);
> if (condition) ++counter;
> database.insert(serialize(elem));
> }
> }
>
> but then, i seem to be dreaming again... :-)
>
> -- peter
>
Here the rollback class contains in some hidden manner a list of
rollback transactions it must perform should it be requested to do so.
What you want with your lambda expressions are implicit ways to create
such rollback-transactions, which is fine, although I dislike the
overuse of operator() overloads. Actually a += overload would be better
thus:
rollback += rollbackOperation;
(In reality it would have to store a list of shared-pointers but
rollback can work out a way to create them internally).
We might also, by the way, be able to add the operation at the time we
execute it, thus if we are setting an int the rollOperation can
automatically copy the old values in.
So now we could do
rollback += setValue( lValue, rValue );
where lValue would be a reference and rValue a value to which it is
setting. Now for those who want fancy lambdas we might want to do
instead
rollback += ( lValue = rValue );
but that won't work of course as it will perform lValue = rValue before
doing the implicit conversion and so we need
rollback += (ourLambda( lValue ) = rValue);
thus an explicit constructor. ourLambda (which would not have that
name) would be a template function returning a class. It returns a temp
so operator= would that follows would have to have an unusual meaning -
it would have to create a new instance rather than modify the old one.
You'd overload also all the other operators like ++, --, += etc that
might be called.
The minor alternative is to have a transaction class to which you add
all these operations and then require an explicit "commit()" or the
rollback will happen automatically on loss of scope. You could even
have both options.
I do think these should be library additions though, not language
specification changes.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Earl
|
12/14/2005 12:57:12 PM
|
|
On 2005-12-14 06:13, Bob Bell wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
:
>> It can be discussed whether stopping
>> execution is more appropriate. As of now, I'm undecided. Throwing might
>> not be a bad idea for a large category of applications.
>
> It's my belief that the opposite is true; that throwing is a good idea
> for only a small category of applications. Throwing when a bug is
> detected has the following bad consequences:
:
> * unwinding the stack destroys information that might help you
> understand the bug
> * a catch(...) can make the bug go unnoticed
These can be avoided by creating a core dump and logging the bug
before throwing the exception.
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/14/2005 3:41:49 PM
|
|
Walter Bright wrote:
> "kanze" <kanze@gabi-soft.fr> wrote in message
> news:1134492853.088622.45710@z14g2000cwz.googlegroups.com...
> > Do we have to discuss the advantages of any added safety?
> > Unless it really does have prohibitive costs, which is
> > rarely the case. And while performance has occasionnally
> > been given as the reason, much of the time, I think that
> > simply inertia and C compatibility and traditions are as big
> > a factor. I've started using garbage collection regularly
> > with C++, for example, and I've yet to encounter a
> > measurable slow-down.
> I've often gotten significant performance *improvements* when
> switching to garbage collection,
I don't doubt it. I would expect improvements, on the average.
If the goal were to fake^H^H^H^Hwrite a benchmark proving that
garbage collection slowed things down, I think I could do so.
Ditto the reverse. In practice, however, I would expect a
slight speed up in most cases, but with a fair degree of
variance. Which corresponds to what I've seen so far (although
I don't yet have enough measurments to say that they are
statistically significant). Most of the programs show a little
bit of speed up, with one or two showing none. To date, however,
no radical speed ups, and no slow-downs what so ever.
In terms of program run-time -- I've noticed a significant speed
up in terms of programmer production:-).
> for several reasons:
> 1) no longer any need for memory bookkeeping code being executed (like
> reference counters).
> 2) no longer any need for "when in doubt, make a copy".
> 3) it becomes possible to 'slice' arrays rather than make copies.
> 4) for many types of programs, you can ditch the gc cleanup
> pass completely.
Just for the record, none of my uses so far fall into this
category. There are two cases where I think that garbage
collection cannot help but be a significant win:
-- Short running programs which only free allocated memory at
the end. This is your case -- you get to the end, and
garbage collection hasn't run at all.
-- Programs with important pauses (e.g. waiting for user
input). If you can arrange to run garbage collection during
the pauses, it is effectively free.
> It's a myth that explicit memory allocation is faster than gc,
> although it's true that a gc program will use more heap memory
> (like double).
That's also been my experience:-). But then, the programs I
tested didn't leak memory before using garbage collection,
either. My experience (not in projects I've worked on, luckily)
has been that a lot of C++ applications leak memory; while
garbage collection doesn't offer a 100% guarantee of no leaks
(some of the early versions of Swing are proofs of that), it
does eliminate some of the more frequent categories of leaks.
And a garbage collected program will quickly use *less* memory
than one which leaks.
And we've not yet touched on the issues of type safety which
Andrei mentionned the last time the question came up.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/14/2005 3:46:10 PM
|
|
Walter Bright wrote:
> "kanze" <kanze@gabi-soft.fr> wrote in message
> news:1134391373.061982.66110@f14g2000cwb.googlegroups.com...
> > The idea that all that is necessary for a function to be
> > overriden is to declare it virtual, however, is false.
> Correct, but I didn't make such a claim.
Maybe not directly, but you're entire argument hinges on it.
> > > I have, however, many times had the "I shoulda made it
> > > virtual" bug, and those bugs are particularly hard to
> > > track down.
> > Funny, but I've never had a bug because I should have
> > declared a function virtual, and didn't. Which functions,
> > if any, can be overridden is part of the basic design.
> The way most programmers code is that "basic design" and
> coding are interleaved and an ongoing process.
That depends largely on the size of the project. If other
people are using your interface, you can't just change it on a
whim. But that really has nothing to do with my argument.
> Virtual is often left off of functions for efficiency reasons.
> I know I code in a stepwise refinement process.
When I'm coding alone, I often make sweeping changes in an
interface based on experience using it as well. When I'm part
of a larger group, such changes have to be approved and
synchronized, so that other people's code doesn't suddenly break
without warning.
But how is this relevant to the question at hand?
> > > Furthermore, it's the responsibility of the writer of the
> > > derived class to 'fit in' with the base class design.
> > It's the responsibility of the author of the derived class
> > to meet the documented contract. If you know how to specify
> > a documented contract which can be met with different
> > implementations, you do so, and you declare the function
> > virtual. But you have to be much more careful with
> > contracts when dealing with virtual functions -- when
> > calling the function, you don't really know what it does.
> You're still coming at it from the idea that the base class,
> once designed, is immutable. Maybe you are good enough to get
> base class design right the first time, but I am not.
Where do you get that? I'm coming at it from the idea that if
the code that implements the base class, and the code which uses
the base class, doesn't expect a function to be overridden, it
will cease to be correct if the function is overridden. The
safe default is thus that the function will not be overridden;
it takes special handling to deal with a possibly overridden
function, so it is normal that a special keyword is used to
identify it.
> > That doesn't mean that virtual functions are bad -- I use
> > them all the time. But it does mean that making a function
> > virtual should be a conscious decision on the part of the
> > base class designer.
> The bugs come in when the design gets changed - and there's no
> way to tell by inspection of a C++ class whether its methods
> are virtual or not, without carefully examining each of its
> ancestors. THAT is the source of error.
The fact that there is no way to know whether a function is
virtual or not without looking at the entire hierarchy *is* a
weak point. The fact that a small typo may result in what I
though was an override not being one is another. What I'd like
to see is:
virtual:
Only used to declare the function at the base of the
hierarchy. A function declared virtual cannot override a
virtual function below it in the hierarchy.
override:
Used to declare a virtual function which overloads a
function below it in the hierarchy. If there isn't such a
function, an error.
final:
Used instead of override if the function cannot be further
overridden.
Functions with none of the above are not virtual, and are not
considered as part of a virtual hierarchy. Although I would
accept the idea that all non-virtual functions should be
declared final as well; i.e. that every member function must be
qualified with one of the above three qualifiers.
Practically, this would cause too many backward compatibility
problems for it to be adopted in C++.
I might add that globally, I don't think it that big of an
issue. The above describes what I consider the ideal; I prefer
(based on concrete experience with both) the C++ model to that
in Java, but I can live fairly reasonably with either. In
practice, I've not had a lot of problems with people overriding
fucntions they shouldn't, nor with people not declaring virtual
functions which should be declared virtual. (In practice, of
course, deep hierarchies are the exception, and typically, the
only virtual functions are declared pure virtual in the base,
and are overridden once -- it is very, very rare for a virtual
function to override a non pure function in a base class.)
> > > I also know of no cases where one *intentionally*
> > > overrides a non-virtual member function with another
> > > function. C++'s method does not prevent this kind of
> > > error, so how is it safer?
> > I don't understand what you are asking. You can't override
> > a non-virtual member function, intentionally or otherwise.
> Sure you can:
>
> struct A { int foo(); };
> struct B : A { int foo(); };
> is perfectly legal C++.
Of course it is. I do it all the time. As a derived class, B
has a right to loosen the preconditions and tighten the post
conditions of A. So you often have something like:
class A
{
public:
int foo()
{
assert_strict_preconditions() ;
int result = doFoo() ;
assert_loose_postconditions();
return result ;
}
private:
virtual int doFoo() = 0 ;
} ;
class B : public A
{
public:
int foo()
{
assert_loose_preconditions() ;
int result = doFoo() ;
assert_strict_postconditions() ;
return result ;
}
private:
virtual int doFoo() ; // With an implementation elsewhere..
} ;
Cliens who know that the actually have a B can take advantage of
the looser preconditions and the stricter postconditions by
using the B interface, rather than the A interface.
I would have considered this part of the basics of programming
by contract.
> > On the other hand, it's not that rare to provide
> > non-virtual functions in the derived class with the same
> > name as non-virtual functions in the base class, e.g. to
> > provide looser pre-conditions, for the user who knows he is
> > dealing with a derived class.
> So, you are saying that in your experience it *never happens*
> that one overrode a method thinking it was virtual, and yet it
> was not?
I've never seen it. I don't know, but all of the programmers
I've worked with, without exception, have been conciencious.
They make mistakes, but not something as fundamental as that.
(In fact, it's very rare for a virtual function which should be
overridden not to be pure virtual. so forgetting the virtual
would result in an error, either from the compiler, if you
remembered the = 0, or from the linker, if you forgot that too.)
It's been my experience in Java that programmers do regularly
forget the final, which should be present on about 2/3 of the
fucntions.
Think about it for awhile. The design of a class corresponds to
a certain model, idiom, pattern, or whatever you want to call
it. People deriving from the class know this; otherwise, their
attempt is doomed from the start. A particular function being
virtual won't change that. If you don't understand the
constraints of the base class, your attempt at derivation is
doomed before you start.
> Let me reiterate that it is not possible, by inspection of
> class X, to determine if methods are virtual or not (one must
> examine all of X's base classes).
Agreed, although I don't quite see the relevance here. But 9
times out of ten, if you're deriving from a class, it has no
base classes.
> It certainly has happened in my experience, and obviously in
> many other peoples' experience, otherwise the virtual
> destructor problem would not get such attention.
The destructor is a special case -- at the very least, a
compiler should warn if a class has virtual functions, but the
destructor isn't virtual. I would even accept an argument that
the destructor should be virtual by default, because it *must*
always be overridden. (If the author of the derived class
doesn't do it, the compiler will do it for him.)
The contract of the destructor, and the object model of C++,
which defines that contract, say that the destructor should be
virtual. That's not true for other functions, where the normal
situation is that the function should not be virtual.
> > Barring that, the C++ solution favors safety, requiring an
> > explicit mark when the added flexibility is desired;
> That's just it, it does not require an explicit mark (since
> virtualness is inherited).
You seem to be getting hung up on that fact. First, of course,
it's a point where we don't really disagree, but is irrelevant
to what the default should be. And second, the case is really,
really rare -- one just doesn't normally override a function
which already has an implementation. (I could even live with a
rule that forbid overloading anything but a pure virtual. In
the absense of any means of declaring that my particular
override is final, such a rule would probably be an
improvement.)
> Furthermore, one can imagine one is overriding a virtual
> function, but actually not be since one forgot a 'virtual'
> somewhere in the inheritance heirarchy. I fail to see how this
> design favors safety.
> > the Java solution favors flexibility, requiring an explicit
> > mark when the added safety is required.
> That's the first time I've ever seen one post that Java favors
> flexibility <g>.
Really? I've seen more than a few comments to the effect that
Java is to dynamic, that it puts off too many decisions until
run-time. And that's the flexibility we're talking about here.
> > My experience is that Java programmers tend to forget the
> > final. The code works anyway, so who cares. At least, the
> > code works for now, as long as no one actually does override
> > the function.
> So, Java is less safe than C++ because one can inadvertantly
> override a virtual function?
That's certainly one point. Not the most important, certainly.
Let's not exagerate. Compared with other issues, it's a minor
point.
with regards to safety, I'd say that the worst problem with Java
is that you cannot impose a contract in an interface. Followed
very closely by the fact that classes are always loaded
dynamically, with the result that you really don't know what
your program does, and have no way of ensuring that the user is
running the same code you validated and tested.
> I am just not getting why this is less safe than C++, where
> one can override it inadvertantly in C++ as well as
> inadvertantly overriding non-virtual functions.
That's because you're not listening. It all comes down to a
basic principle: the less direct control you have, the more risk
you take. A virtual function represents an added risk. Not
necessarily a big one, if you've wrapped it correctly in a
non-virtual function which verifies pre- and post-conditions,
but a small risk. In Java, you take the risk by default,
declaring explicitly that you don't want it. In C++, you have
to declare explicitly that you do. Given that the risk is
small, because programmers don't derive from a base class
without knowing what the constraints are, it's not an important
issue -- at least not as important as some others. But if you
have two languages which are otherwise equal, the one which
requires an explicit virtual is safer, and the one which
requires an explicit final is more flexible -- if you want real
flexibility, you can go even further, and not require the base
class to declare the function at all.
As with many things, it's a compromize. I find that for the
work I do, C++ has struck about the right level of compromize,
at least with regards to this one issue. People working on more
dynamic and less critical applications may feel differently
about it. There's also a large area where both solutions are
perfectly acceptable, although one or the other may be slightly
preferrable.
> > On the other hand, a C++ programmer cannot forget the
> > virtual where it is needed, because the code won't work.
> Oh, he certainly can forget it, and it isn't true that the
> "code won't work." What'll happen is that there will be a bug
> in the code, a hard to find bug, and a bug that may not
> manifest itself unless you've got a very thorough test suite.
> It can also result in pointer bugs, which may persist for
> years without being discovered.
Do you really have an example of such? I can't conceive of it.
If I write a derived class, expecting my derived function to be
called, and the base class function is called instead, the code
will fail in the first set of unit tests, and the reason for
failure will be obvious. (Most of the time, of course, if it is
really intended that I override the function, the function will
be pure virtual.)
> > (The one exception is the destructor, where the code may
> > appear to work until the class is used in a multiple
> > inheritance lattice, or a derived class has a non-trivial
> > destructor. But a good compiler will warn if a class has
> > virtual functions, but a non-virtual destructor.)
> The existence of warnings that a "good compiler" will emit is
> indicative of a significant deficiency in the language design.
But we're talking here only about destructors. A destructor is
a special case, because it's contract is defined by the C++
object model, and not by the author of the base class.
> > > I'm not sure what you're saying there, but the problem in
> > > C++ comes from overriding a non-virtual function,
> > You can't override a non-virtual function. You'll have to
> > explain what you mean here.
> See above.
You mean where your example of "wrong" code actually
corresponded to a fairly common and approved idiom for writing
robust C++?
> > > and the fact that doing so is almost always inadvertent
> > > and a bug, exacerbated by:
> > > 1) virtualness is inherited, meaning you can't look at a
> > > function and tell if it is virtual or not without
> > > examining all its base classes
> > That is a bit of a problem. All of the coding guidelines
> > I'm familiar with require using the virtual keyword in the
> > derived class, but of course, it's easy to forget, and may
> > confuse programmers deriving from the derived class.
> This is like the warning problem above, using coding
> guidelines in this manner is indicative of a deficiency in the
> language.
OK. So we forbid overriding a non-pure virtual function. That
would be acceptable.
> C++ would be better if it required the virtual keyword when
> overriding a virtual function, and if it disallowed the
> virtual keyword if overriding a non-virtual function.
A little bit. It would be even better if it required you to
distinguish which one you thought you were doing. It *is* an
occasional problem when the base class defines a function to
take a non-const reference, because the contract allows the
object to modify the parameter, and the the derived class
declares a const reference, because it doesn't actually modify
it. Most of the time, this results in a compiler error when you
try to instantiate the class, because you've failed to override
a pure virtual function; in the less frequent cases where the
virtual function is not pure, you end up with a subtle run-time
bug (although typically caught in the unit tests).
> > > 2) programmers tend to pull the 'virtual' keyword off of
> > > functions in classes that nobody derives from at the
> > > moment, for efficiency reasons.
> > If a programmer insists on being stupid, there's not much
> > you can do about it. All I can say is that I've never
> > encountered one that stupid.
> I do it. Call me stupid, but my compilers run faster than
> anyone else's.
Do they have less errors? And do you really think that this
makes a measurable difference in a compiler? How many virtual
functions do you have in a compiler, anyway? And how is it that
you have classes where you don't know whether it is designed to
be used as a base class or not?
Or is it a case of your declaring everything virtual to start
with, even those functions which shouldn't be, and then
stripping the virtuals off when the profiler says you must. On
the projects I've worked on, programmers only make something
virtual if there is a need for it. And of course, if there is a
need for it, you can't strip it off later for efficiency
reasons. Most classes are not designed to be derived from, so
they never get a single virtual to begin with.
Design may be iterative, but that doesn't mean not to do it.
Another good rule is that premature genericity is a lot like
premature optimization.
> > Whether a function is virtual or not is part of the contract
> > of the class. How on earth does a programmer of the base
> > class know what the derived classes may or may not do? If
> > the design says that a function may be overridden, then he
> > must make it virtual. If the design says that it cannot be
> > overridden, or the function is part of the implementation,
> > then it may not be virtual.
> You put a lot of faith in a design document, and assume that
> it is correctly followed. I don't, I put my faith in what the
> language does or does not guarantee, and I look at the actual
> code.
The language does exactly what I tell it to. I design before I
code. I decide what a class is to do before I write the actual
code. I've found that doing things the other way round --
writing code, and then either decrying that what it does is what
it should do, or deciding after the fact what it should do, and
trying to "fix" it after the fact, just doesn't work.
> Therefore, I need a language where the design can be expressed
> unambiguously in the code.
I do this whenever possible, but the language isn't C++, it's
annotated UML. And I don't think it an accident that
development costs have been lower, and program reliability
higher, in companies which have used Rose, as opposed to those
which used no design tool.
(This is based purely on my own experience, and it's quite
possible that there is a common third cause -- whatever it is in
the organization which causes them to develop good code
efficiently also causes them to use Rose. Also, I've no
concrete experience with other similar tools, like Together; I
presume that they would also have a similar effect, but I'm just
guessing there.)
Of course, in such cases, the choice of virtual or not is made
explicitly, in the tool, and the tool takes whatever steps the
programming language requires to achieve the desired choice.
> I want to be able to look at the code, and visually verify it
> against the design document. I can't do that with C++ for
> function virtualness (or for several other characteristics in
> the language; macros being the most obvious example).
Again, you're talking about a different problem. I agree that
C++ good stand improvements in readability. In many areas. I
just don't see how that affects what the best default is: my
experience with Java is that programmers are often forgetting
the final, with the results that you can't tell whether it makes
sense to override a function or not, period. (But Java has a
lot of even more serious problems here, like the fact that the
code and the declarations are all mixed up in the same source
file.)
> > > Then, later, the class does get derived from, the virtual
> > > isn't put back on, and voila, a difficult to find bug.
> > If that's the way you run a project, then it really doesn't
> > matter what the default is, because the program isn't going
> > to work correctly anyway. In the places I've worked, a
> > programmer doesn't change an interface on a whim. I must
> > say, too, that your experience is far different than mine.
> > In the code I've worked on, it has been very rare that a
> > virtual function wasn't pure virtual in the base class, or
> > that an existing definition of a virtual function in a
> > derived class was further overridden.
> I agree, our experiences are different. But what I am
> inferring from your comments is you rely on a strictly imposed
> methodology in order to get safety out of C++.
I rely on a fairly strict methodology to ensure a safe design.
And to ensure that the C++ (or Java, or whatever -- I've even
used it for shell scripts) that is written corresponds to the
design.
> One of the design goals for D is to make it easier to manage
> code by making it more inspectable, and hence make it less
> necessary to rely on a rigidly imposed (and hence expensive)
> methodology.
I'm not arguing with the goal:-). I'm not even arguing that C++
could stand some serious improvement with regards to that goal.
But that's not what is being discussed. What is being discussed
is what the correct default behavior should be. Which, come to
think about it, preassumes already some compromize vis-a-vis
this particular goal -- it seems obvious to me that the most
easily written code will require explicit specification in all
cases, without any default. Beyond that, it seems (slightly)
safer to me to assume that in the absense of any explicit
indication to the contrary, it is not safe nor reasonable to
override any specific function.
> To that end, for methods, by inspection of the method, and
> only that method, you can determine:
> 1) if it is direct or virtual
> 2) if it can be overridden or cannot be
> It isn't necessary to assume that the comments or design
> document are either correct or adhered to.
It is necessary to inspect hand written code to ensure that it
conforms to the specifications. Otherwise, how can anyone use
it. That seems pretty straight forward and obvious -- code
which doesn't conform to its specifications is an error.
But that's a simple truism. I fail to see how it impacts on the
current discussion.
> > > > > Adding in the 'final's would be an optimization.
> > > > Preventing errors is an optimization? Since when?
> > > From the perspective that if you take a working, bug free
> > > program that uses 'final', and remove all the 'final'
> > > keywords, you will not introduce a bug.
> > First, that's potentially false for some idioms.
> I don't see how. Example, please?
See my example of A::foo and B::foo, above. Remove the final
(i.e. make foo virtual), and you start verifying the wrong pre-
and post-conditions. Let the functions have slightly different
semantics (not a good idea in general, I'll grant you), and you
turn a correct program (which thought it was getting the
semantics of A::foo, and actually gets those of B::foo) into an
incorrect one.
> >You don't introduce an immediate bug, but you introduce
> >serious maintenance problems.
> If there are serious maintenance problems if you don't use
> 'final', then you are implying that those very same *serious*
> problems exist in C++, as there is no way to express 'final'.
> So why is C++ safer?
Because you rarely need to express "final" for a virtual
function. I'd guess that well over 90% of all inheritance is
one level, with the virtual functions in the base class pure
virtual. In the occasional cases where you need to express
final on a virtual function (e.g. when you use the template
pattern in the implementation of an interface), this is a
weakness, but it's not that common a case.
> > The presence or absense of final is a design decision, just
> > like the presence or absense of virtual. I've never heard
> > of anyone removing a virtual because a program was too slow
> > (although I guess in certain domains it could happen).
> You have now! I make a living writing programs that run faster
> than anyone else's.
I thought you make a living writing compilers. The most
essential feature of a compiler is that it is correct. Speed is
nice, of course, but it is secondary. And it is mainly gained
by reducing and optimizing disk accesses, using different
parsing algorithms, etc. Do you seriously mean to tell me that
you have profiling data or benchmarks which show a difference of
more than 10%, or even 5% in compile times, just because you
were able to avoid using virtual on functions which the original
design required virtual?
Maybe the difference is because I only use virtual when the
design requires it. The C++ idiom, in fact.
> I use a lot dirtier tricks than that one <g>.
That's neither here nor there. The question is: having written
the code correctly in the first place, with virtual where the
design required virtual, and non-virtual where it didn't, did
you actually manage to get some measurable speed up by removing
the virtual.
> Also, in the normal process of providing tech support for
> compilers, I see a lot of diverse code come across my desk.
> In the process of designing a language, I'm a bit of a
> pragmatist in trying to adapt to the way diverse programmers
> actually work rather than just how the top 2% work.
I tend to work on large projects. Which get very good results
with more or less average programmers.
> Much of the motivation for inspectable code comes from my
> discussions with programming managers. Many of them have
> corporate coding styles which ban certain C++ constructs
> because they make the code uninspectable. They want to be
> able to look at a piece of code and determine what it does
> without having to examine every #include'd header, believing
> that this reduces costs and improves reliability. This
> motivation is also why many projects stick with C.
Understanding what code does is certainly important. I've just
never found this to be a problem with well written C++.
You obviously have to have some knowledge of the overall design,
and what the classes used by your class do. But it's not that
big an issue in practice.
> Java is much more inspectable, and this shouldn't be
> discounted when wondering why a corporate project chooses Java
> over C++.
There was an early enthusiasm for Java, but from what I've seen
lately, when a company chooses Java, it is because Java has the
infrastructure needed for what they want to do. It's a lot
easier to integrate network components written in Java (or
rather, in a certain style of Java) into Websphere or Apache
that it is to integrate C++.
> > (although anything which makes the code more predictable and
> > readable indirectly makes programs safer).
> I just argued in favor of that <g>.
You've pointed out certain places where C++ could improve its
readability. But that's all pretty orthogonal to the choice of
a default.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/14/2005 3:46:34 PM
|
|
Earl Purple wrote:
> peter steiner wrote:
>
> > imho error handling such transactional procedures with your scopeguard
> > idiom or rollback functors lacks one thing that you already addressed:
> > the necessity of defining kludgy raii classes like IntDecrementer etc.
>
> IntDecrementer type classes would be hidden in the background in some
> library you just "use".
you are still bound to define new classes as the need for functionality
arises that isn't covered by the library of existing scopeguards.
problems like forwarding the scope local variables into the scopeguard
and the general overhead of declaring a class for the purpose of
executing a simple line of code in the destructor hinders the
efficiency of this technique.
imho this issue is similar to the problem with container algorithms.
the lack of lambda expressions in c++ often require the programmer to
declare bulky one-shot functor classes for the algorithms' loop body.
the overhead often simply does not warrant the advantages in practise.
>
> > with a boost::lambda like library with containable lambda expressions
> > and rollback_t as container of those lambdas that can be executed on
> > request, your example could be expressed as follows:
>
> > void Widget::foo(const bool condition) {
> > rollback_t rollback;
> > try {
> > cont.push_back(elem);
> > rollback( (_1.pop_back())(cont) );
> > if (condition) ++counter;
> > rollback( if_then(_1, --_2)(condition, counter) );
> > database.insert(serialize(elem));
> > }
> > catch (...) { rollback(); throw; }
> > }
>
> Rather ugly and overuse of operator() overloading. It's very difficult
> for me to see immediately what this code is doing even if I do get the
> hang of these funny _1 and _2 expressions (which I believe are global
> objects and therefore not thread-safe anyway).
>
> There are slightly tidier ways to get rollback-able expressions.
i totally agree. this was just posted for the sake of showing the lack
of current lambda solutions in c++.
>
> > unfortunately besides the unnatural syntax a boost::lambda like
> > approach yields a lot of problems for more complicated expressions.
> >
> > now in a hypothetical c++ that supports first class lambda expressions
> > the code translates to the following:
> >
> > void Widget::foo(const bool condition) {
> > rollback_t rollback;
> > try {
> > cont.push_back(elem);
> > rollback( { cont.pop_back(); } );
> > if (condition) ++counter;
> > rollback( { if (condition) --counter; } );
> > database.insert(serialize(elem));
> > }
> > catch (...) { rollback(); throw; }
> > }
> >
> > here no kludgy definitions are necessary because they can be
> > implemented inline.
> >
> > it is my believe that a first class lambda would add a lot of diverse
> > possibilities to the language, also at the error handling domain.
> >
> > ultimately i would like to see the following:
> >
> > void Widget::foo(const bool condition) {
> > transaction {
> > cont.push_back(elem);
> > if (condition) ++counter;
> > database.insert(serialize(elem));
> > }
> > }
> >
> > but then, i seem to be dreaming again... :-)
> >
> > -- peter
> >
>
> Here the rollback class contains in some hidden manner a list of
> rollback transactions it must perform should it be requested to do so.
>
> What you want with your lambda expressions are implicit ways to create
> such rollback-transactions, which is fine, although I dislike the
> overuse of operator() overloads.
yes, overloading operator() for both registration and execution of
rollback actions is not the cleanest solution in this example, but this
is besides my point.
taking andrei's suggestion of hiding the rollback(void) call into
consideration, the design would mimic existing solutions anyways:
void Widget::foo(const bool condition) {
transaction_t transaction;
cont.push_back(elem);
transaction.rollback( { cont.pop_back(); } );
if (condition) ++counter;
transaction.rollback( { if (condition) --counter; } );
database.insert(serialize(elem));
transaction.commit();
}
> Actually a += overload would be better
> thus:
>
> rollback += rollbackOperation;
>
> (In reality it would have to store a list of shared-pointers but
> rollback can work out a way to create them internally).
>
> We might also, by the way, be able to add the operation at the time we
> execute it, thus if we are setting an int the rollOperation can
> automatically copy the old values in.
>
> So now we could do
>
> rollback += setValue( lValue, rValue );
>
> where lValue would be a reference and rValue a value to which it is
> setting. Now for those who want fancy lambdas we might want to do
> instead
>
> rollback += ( lValue = rValue );
>
> but that won't work of course as it will perform lValue = rValue before
> doing the implicit conversion and so we need
>
> rollback += (ourLambda( lValue ) = rValue);
you are basically reinventing boost::lambda like expression templates
that you have criticised above in a slightly different way. i do not
think such an approach proves efficient and usable in the long run, at
least not without massive changes in the template compilation
mechanism. have you ever tried to use expression template libraries
like boost::lambda widely in a complex project? soon you spend more
time analyzing template compilation errors than developing the
application itself...
>
> thus an explicit constructor. ourLambda (which would not have that
> name) would be a template function returning a class. It returns a temp
> so operator= would that follows would have to have an unusual meaning -
> it would have to create a new instance rather than modify the old one.
>
> You'd overload also all the other operators like ++, --, += etc that
> might be called.
>
> The minor alternative is to have a transaction class to which you add
> all these operations and then require an explicit "commit()" or the
> rollback will happen automatically on loss of scope. You could even
> have both options.
yes, that could be the way to go, see above...
>
> I do think these should be library additions though, not language
> specification changes.
i can just repeat myself: whatever the concrete syntax might be, there
is no single feature on the c++ horizon as promising as a first class
lambda. i know that language changes on this scale don't come by
easily, but i hope this will be implemented as a generally applicable
syntax extension.
any other solutions just work around the lack of first class lambda in
a cumbersome way.
-- peter
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
peter
|
12/14/2005 3:46:58 PM
|
|
Walter Bright wrote:
> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
> message news:dnnhb4$12s$1@kujawiak.man.lodz.pl...
[...]
> > What else is a problem?
> printf("%s\n", "hello");
> printf("%s\n", s); // s is an std::string
Just a nit, but you can write:
std::cout << boost::format( "%s\n" ) % "hello" ;
std::cout << boost::format( "%s\n" ) % s ;
I won't disagree with your basic contention, but citing printf
for proof doesn't back up your argument any. (If you don't like
boost::format, say because the % operator makes it unreadable in
this context, both Andrei and I have our own versions which do
more or less the same thing, with slightly different syntaxes.
My own dates from some ten or twelve years ago, so there's
nothing new here.)
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/14/2005 3:47:20 PM
|
|
Earl Purple wrote:
> peter steiner wrote:
> What you want with your lambda expressions are implicit ways to create
> such rollback-transactions, which is fine, although I dislike the
> overuse of operator() overloads. Actually a += overload would be better
> thus:
[snip]
> I do think these should be library additions though, not language
> specification changes.
You got to have lexically-scoped functions to implement on_block_xxx the
way you mentioned, and that's a language change.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/14/2005 7:28:54 PM
|
|
Walter Bright wrote:
> "Andrei Alexandrescu (See Website For Email)"
> <SeeWebsiteForEmail@moderncppdesign.com> wrote in message
> news:IrG7BI.pzL@beaver.cs.washington.edu...
>
>>Again, ScopeGuard is a lame paliative for the highly desirable
>>on_scope_xxx feature. Its usefulness comes from us needing and lacking
>>the true on_scope_xxx feature.
>
>
> If you could write up a more detailed treatment of exactly how it
> works, I'd
> be seriously interested in it.
Ok, I initially wrote that as a private email, but then I realized the
semantics might be of interest to the C++ community.
I will define below the semantics of the three on_scope_xxx constructs
by constructions, that is, I will define transformations that take code
using on_scope_xxx and yield code using try/catch/finally. (Along the
way we'll see why "finally" is needed.)
I will use _statements_ as the grammatical construct to build a sequence
of zero or more statements.
All transformations below are applied *top-down*, recursively, within a
scope. That is, when you see the *first* lexical on_scope_xxx in a
scope, you apply the transformation and then continue examining the
transformed code.
1. on_scope_exit has the following semantics:
{
_statements_1
on_scope_exit { _statements_3 }
_statements_2
}
is equivalent with:
{
_statements_1
try {
_statements_2
} finally {
_statements_3
}
}
2. on_scope_failure has the following semantics:
{
_statements_1
on_scope_failure { _statements_3 }
_statements_2
}
is equivalent with:
{
_statements_1
try {
_statements_2
} catch (...) {
_statements_3
throw;
}
}
(I used catch (...) and throw; with the semantics they have in standard
C++. I believe in D it would be catch(Object obj) and throw obj, right?
Also, I've ignored the ill effects that catch (...) has in some C++
implementations.)
3. on_scope_success has the following semantics:
{
_statements_1
on_scope_success { _statements_3 }
_statements_2
}
is equivalent with:
{
bool __failed = false;
_statements_1
try {
_statements_2
} catch (...) {
__failed = true;
throw;
} finally {
if (__failed) ; else {
_statements_3
}
}
}
The name "__final" is a reserved name. There's no legal program that can
cause duplicate definitions errors, because user code is forbidden to
use the symbol __final, and because the implementation's own multiple
definitions of __final will be nested by virtue of the way
on_scope_success is transformed. (Note how _statements_1 never contains
top-level __failed, and how _statements_2 is nested within the scope of
_statements_1).
Also I've used the "if (__failed) ; else" trick to allow the
implementation to postpone semantic checking of the statements to after
the transformation.
================
Note that there are some interesting consequences of the transformations
above. You can nest on_scope_xxx as you wish because try statements
nest. You can sequence them any way you wish. They have semantics that
are in principle available to try/catch/finally, but without the nesting
that causes so much trouble.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/14/2005 8:59:32 PM
|
|
Walter Bright wrote:
> "kanze" <kanze@gabi-soft.fr> wrote in message
> news:1134492853.088622.45710@z14g2000cwz.googlegroups.com...
>
>>Do we have to discuss the advantages of any added safety?
>>Unless it really does have prohibitive costs, which is rarely
>>the case. And while performance has occasionnally been given as
>>the reason, much of the time, I think that simply inertia and C
>>compatibility and traditions are as big a factor. I've started
>>using garbage collection regularly with C++, for example, and
>>I've yet to encounter a measurable slow-down.
>
>
> I've often gotten significant performance *improvements* when switching to
> garbage collection, for several reasons:
>
> 1) no longer any need for memory bookkeeping code being executed (like
> reference counters).
> 2) no longer any need for "when in doubt, make a copy".
> 3) it becomes possible to 'slice' arrays rather than make copies.
> 4) for many types of programs, you can ditch the gc cleanup pass completely.
>
> It's a myth that explicit memory allocation is faster than gc, although it's
> true that a gc program will use more heap memory (like double).
Mostly true. See http://www.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf.
I believe garbage collection is best when coupled with the notion of
immutable data ("const"). This is because you can alias const data with
much more magnanimy, while at the same preserving encapsulation. It's
ironic that D lacks const, and Java has it only in a limited form :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/14/2005 9:02:18 PM
|
|
Alf P. Steinbach wrote:
> Let's nail down that misunderstanding... ;-) I'm not saying that my
> ON_SCOPE_FAILURE macro is good: I'm saying the proposed on_scope_failure
> language feature is currently implementable as an ugly, limited and
> inefficient macro-based and convention-based library solution.
Isn't everything...? :o)
> And that in
> contrast, the other two inline code mechanisms you proposed,
> on_scope_success and on_scope_exit, seem to require language support.
I see what you're saying. Well, true; that's because C++ supports "catch
(...) { ... ; throw; } but not "finally".
> Does the "I don't like ON_SCOPE_FAILURE" mean you don't like the
> ON_SCOPE_FAILURE macro (that kind of kludgy implementation) or does it mean
> you don't like your own proposed on_scope_failure language facility?
The former.
> Much of the point of the existing exception handling constructs is to move
> error and failure handling code out of the normal case code, and restrict
> its access to things explicitly provided for it.
>
> on_scope_failure does the opposite, and so is undesirable IMHO.
Exception handling constructs have done a hell of a job at helping,
haven't they :o}.
That's an emperor clothes issue. It was about time that we rise and say,
and you can quote me on that, "hey, try/catch/finally... they *suck*!"
Same happened to global variables, dynamically-scoped symbols, I/O built
into the language, multiple inheritance, inheritance, and other
misfeatures. For each, there was a point in history when most people
thought they were good and were dispensing advice on how to use them.
Such misfeatures have been used and abused until someone got up and said
they aren't as good as everybody says. It's about time we think out of
the box and revisit the concept "error-safe code is supposed to suck"
and "try/catch/finally move error and failure handling out of the way,
and that's a good thing".
Contingency actions aren't supposed to be "out of the way". Each
elementary step in the progress of a computation must be followed by
piecemeal contingency measures that will be taken if computation as a
whole fails.
We can hope that a static analysis will do most of that automatically in
the future, but we must crawl before we run. And crawling at this point
means to (1) openly acknowledge that if try/catch/finally were supposed
to help us with exception handling, they've done an incredibly lousy job
at it, and (2) understand the importance of on_scope_xxx constructs as
essential building blocks for exception-safe code.
> on_scope_success moves normal case code back to where it belongs, inline,
> and by doing that also means that error/failure-handling code can be given
> more limited access things, and so on_scope_success is desirable on that
> count, IMHO. On the other hand, on_scope_success also means that one cannot
> see from the code's placement what has executed successfully before that
> code: code that comes later in the apparent linear flow may actually execute
> first. I find that unpalatable, but then I'm generally a SESE fan.
Part of understanding why on_scope_xxx is important is that
straight-line code isn't appropriate for dealing with nonlocal errors.
If I were a SESE fan, I'd be happy with on_scope_xxx because they are
the closest approximation of straight-line code that deals with errors:
they don't introduce extra flow of control, are statically scoped, and
statically ordered.
> on_scope_exit mixes normal case code and error/failure handling code, or at
> least allows and encourages that. I think that's undesirable, like
> 'finally'. And if history is anything to go by, a language feature that's
> not clearly desirable is probably undesirable for the programmers, or if not
> that, it complicates the language specification just to give a conceptually
> clean picture, like local references to const initialized with rvalues.
That answer comes straight from within the box. Let's think outside the
box and see, what do we have now? What have we got? Do we have a clean,
simple way of writing exception-safe code? We don't. Then, why shall we
think that the tenets of these lousy means of error handling would be
immutable laws?
Why is mixing normal case code with error/failure code bad? Was
attempting to separate them leading anywhere interesting? It sure
didn't. Then maybe that wasn't such a good hypothesis, was it. And now,
come to think of it unbiased. Nowadays I think it's great that as soon
as I've done one little thing, I leave a safeguard behind me just in
case the big thing fails. After having educated myself that way and used
that with Perl, I've realized just how fallacious is to do things the
try/catch/finally way (which Perl has but I *never* use because I don't
need it!)
The problem with try/catch/finally is that they foster code that does a
big chunk of computation, followed by a big chunk of contingency
actions. But during contingency actions you have no information about
where the big chunk of computation failed, unless (a) you track that
yourself with explicit state (as I've shown in another post, or (b) you
nest try statements like crazy (as I've shown in that same post).
> In summary, as I see it, the one construct that's readily (but not
> efficiently) implemented without language support, namely on_scope_failure,
> is undesirable, and the two features that are not clearly undesirable or
> desirable, on_scope_success and on_scope_exit, with the former possibly of
> great value to some programmers, seemingly require language support.
Let's.Think.Outside.The.Box.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/14/2005 9:02:40 PM
|
|
Andrei Alexandrescu <SeeWebsiteForEmail@moderncppdesign.com> writes:
> {
> bool __failed = false;
> _statements_1
> try {
> _statements_2
> } catch (...) {
> __failed = true;
> throw;
> } finally {
> if (__failed) ; else {
> _statements_3
> }
> }
> }
Why the catch block?
{
_statements_1
bool __failed = true;
try {
_statements_2
__failed = false;
} finally {
if (__failed) ; else {
_statements_3
}
}
}
--
Steven E. Harris
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Steven
|
12/15/2005 10:42:07 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> That's an emperor clothes issue. It was about time that we rise and say,
> and you can quote me on that, "hey, try/catch/finally... they *suck*!"
> Same happened to global variables, dynamically-scoped symbols, I/O built
> into the language, multiple inheritance, inheritance, and other
> misfeatures. For each, there was a point in history when most people
> thought they were good and were dispensing advice on how to use them.
> Such misfeatures have been used and abused until someone got up and said
> they aren't as good as everybody says.
Hm, in the fire of the argument I categorized multiple inheritance and
inheritance as "misfeatures", when initially I was thinking of building
the sentence around a "often abused features" clause. Just trying to get
this message out before the OO police shows at my door :o).
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/15/2005 10:42:30 AM
|
|
On 2005-12-14 20:59, Andrei Alexandrescu (See Website For Email) wrote:
:
> I will define below the semantics of the three on_scope_xxx constructs
> by constructions, that is, I will define transformations that take code
> using on_scope_xxx and yield code using try/catch/finally.
[definitions snipped]
Two comments:
1. When the on_scope_xxx block throws an exception, shouldn't this be
handled as with throwing from destructors?
2. The transformations you use have the consequence that e.g.
int a = 1;
...
on_scope_xxx { ... }
...
int a = 2;
becomes valid while
int a = 1;
...
int a = 2;
of course is still invalid.
I presume that's not really your intent, or is it?
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/15/2005 10:43:02 AM
|
|
On 2005-12-14 21:02, Andrei Alexandrescu (See Website For Email) wrote:
:
> I believe garbage collection is best when coupled with the notion of
> immutable data ("const"). This is because you can alias const data with
> much more magnanimy, while at the same preserving encapsulation. It's
> ironic that D lacks const, and Java has it only in a limited form :o).
For references/pointers to const it wouldn't make much of a
difference, since those don't say anything about whether the
referenced data is actually const.
-- Niklas Matthies
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Niklas
|
12/15/2005 10:43:40 AM
|
|
Peter Most wrote:
> But because
> of the forementioned ignorant developers, the language is getting a bad
> reputation for it's "unsafety" which also influences decision makers to
> abandon C++ projects.
Wrong.
The "decision makers" abandon C++ because their colleague (whom they
admire) said the other night at the bar that "We've been following
productivity, and that just stinks with C++, so I finally decided to
lay this out to the Boss."
Problem is that almost none of the Pointy Haired Bosses know that there
actually do exist languages where you get "the productivity of Python,
the power of C++, and the bare metal feel of C".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
gwrede
|
12/15/2005 10:44:41 AM
|
|
Walter Bright wrote:
> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
>>Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
>>>4) inconsistent and counterintuitive name lookup rules for dependent and
>>>non-dependent names
>>Better idea?
> Sure. Have them work like functions do. The function arguments are looked up
> in the scope of the point of calling, the function body names are looked up
> in the point of the function definition. This is how D templates work, and
> it works intuitively to the point that the issue *has never even come up*.
> It works like one would expect it to, it requires no complicated
> explanation, and it never occurs to anyone it should be any other way. There
> aren't any crazy kludges like local names cannot have the same name as a
> template parameter, or that template base class members aren't found.
How would this work in the presence of partial specialization, when it
is not clear until instantiation what the members of the base class are?
I.e. what would be displayed by the D equivalent of the following:
#include <iostream>
#include <ostream>
void foo() { std::cout << "Global foo\n"; }
void bar() { std::cout << "Global bar\n"; }
template<int N> struct toto
{ void foo() { std::cout << "toto<" << N << ">::foo\n"; } };
template<int N> struct titi : public toto<6*N>
{ void hello() { foo(); bar(); } };
template<> struct toto<42>
{ void bar() { std::cout << "toto<42>::bar\n"; } };
int main()
{
titi< 7> t007; t007.hello();
titi<111> t111; t111.hello();
return 0;
}
Falk
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
ISO
|
12/15/2005 10:45:19 AM
|
|
* Andrei Alexandrescu (See Website For Email):
> 3. on_scope_success has the following semantics:
>
> {
> _statements_1
> on_scope_success { _statements_3 }
> _statements_2
> }
>
> is equivalent with:
>
> {
> bool __failed = false;
> _statements_1
> try {
> _statements_2
> } catch (...) {
> __failed = true;
> throw;
> } finally {
> if (__failed) ; else {
> _statements_3
> }
> }
> }
I'd write that as
{
_statements_1
_statements_2
_statements_3
}
;-)
As you put it in an earlier reply to me in this thread,
Think.Inside.The.Box.
Which means that on_scope_success' only reason for existence is to write
_statements_3 out of execution order, so that in general later statements in
a sequence can be executed before earlier statements, which IMHO is UnGood.
On the other hand, I like the basic idea and motication for
on_scope_failure. In particular, ScopeGuard, although possibly the best
innovation ever in C++ (thanks Petri & Andrei!), suffers from an
All-Catching Destructor, which affects debugging, and a macro implementation
of on_scope_failure, at least the one I came up with, suffers from so much
there isn't space here to enumerate it all, but first and foremost
inefficiency. on_scope_failure fixes all that, so
* I'd really like to see something like on_scope_failure in the language,
_but_ I'd like it restricted to the bottom of the scope (i.e. with no
out-of-textual-order execution), and a single one, e.g. syntax like
{
_NormalCaseCode;
on_failure
{
_FailureHandling;
}
}
where the important difference from 'finally', and the reason for existence
of this constuct, is that 'on_failure' does _not_ include normal case code
or the possibility of normal case code; the C++98 equivalent being
{
try
{
_NormalCaseCode;
}
catch( ... )
{
_FailureHandling;
throw;
}
}
Needless to say, perhaps, that means I don't like the other two proposed
constructs, namely on_scope_exit (essentially 'finally' only worse) and
on_scope_success (a distillation of the very worst in 'finally'), which I
think are the result of over-generalization, with the very undesirable
consequence of mixing success and failure case code, like 'finally', and in
addition altering the execution order of sequential statements.
Yes, on_scope_exit would solve the general problem of destroying a non-C++
resource, like e.g. closing a file handle. But it would do that at the
level of abstraction of 'goto', allowing so much else, so many Bad things!
Instead, what's needed there is IMHO something that would make it easier to
implement ScopeGuard-like things, a general mechanism for accessing constant
local variables in inline defined code that can be passed around, e.g. like
Java inline local classes -- which would solve a bunch of other problems
and ease programming in general, in one fell swoop.
While we're on the subject of new exception handling constructs, Dave
Harris, in <url:
http://groups.google.com/group/comp.lang.c++.moderated/msg/e61bc0158363ee6d>,
had an excellent suggestion for avoiding one such construct proposed by me
(namely "goto try" with implicit rethrow if the goto isn't executed, that
rethrow being the same safety idea as the safety in the on_scope_failure
mechanism), namely instead using the template pattern -- D.H.'s code,
struct Retrier {
void doit( unsigned maxTries );
protected:
virtual void attempt( unsigned tryCount,
const std::string &errorMessage ) = 0;
};
void Retrier::doit( unsigned maxTries ) {
std::string errorMessage;
for (unsigned tryCount = 1; true; ++tryCount)
try {
attempt( tryCount, errorMessage );
return;
}
catch (std::exception &e) {
if (count < maxTries)
errorMessage = e.what();
else
throw;
}
}
}
However, templatizing the template pattern is difficult...
Partially as a result I later came up with the "succeed_or_throw" proposal,
which as with your proposal was defined in terms of C++98 equivalence,
<url: http://groups.google.com/group/comp.std.c++/msg/b37094d7a003cb4b>,
and with the main discussion and a simpler (the original) explanation in
<url:
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/c9afdba1f852cee7/98b002b20ebe5cb0>.
"succeed_or_throw" would potentially have allowed dealing with double
exceptions, and IMO made for a more rational exception handling at a higher
and less error-prone level of abstraction than pure try-catch.
However, after a while that discussion devolved into opinions on what a
contract is (does it include exception specifications or not?), which means
that possibly we should
* see this all in connection with Contract Programming,
<url: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1773.html>.
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/15/2005 10:46:00 AM
|
|
* Alf P. Steinbach will write, in a posting sent a few seconds ago:
{
try
{
_NormalCaseCode;
}
catch( ... )
{
_FailureHandling;
throw;
}
}
where an important sentence disappared during editing,
except that _FailureHandling has access to the declarations in the
'try' block
This was all to avoid introducing a more complicated equivalence with loop
and use of hypothetical internal C++ compiler features.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/15/2005 10:46:44 AM
|
|
kanze wrote:
> Walter Bright wrote:
> > "kanze" <kanze@gabi-soft.fr> wrote in message
> > > ... I've started using garbage collection regularly
> > > with C++, for example, and I've yet to encounter a
> > > measurable slow-down.
>
> > I've often gotten significant performance *improvements*
> > when switching to garbage collection,
>
> I don't doubt it. I would expect improvements, on the
> average.
I have a concern. I've been using a BlackBerry for more than
a year now and my understanding is that it is a JVM running
applications written in Java.
In all honestly, it has been an annoying device to use for
two primary reasons.
First, the amount of time any particular action takes seems
unpredictable. Sometimes opening the message log might be
immediate sometimes it may take a few seconds. Escaping out
of a menu might be immediate or it might take several
seconds. I'm saying that seemingly identical actions take
largely varying amounts of time. Isn't this one of the
symptoms of automatic garbage collection? If so, from a
consumer perspective I find it nearly intolerable as I wait
in anticipation to see if my last input was registered.
Second, the device seems to crash every month or so and
throws exceptions that amusingly appear on the screen as if
a user can somehow take corrective action. And they read as
the usual exceptions we've all seen from Java programs. The
previous cell phone I had, a Motorola, ran software written
in who knows what (assembly?) and it never once crashed in
four years I used it. And it never through any exceptions. I
can't help but wonder if the Java philosophy played a role
in creating this software that seems to so often throw and
crash?
Can anyone shed some light on this BlackBerry software
quality? What about other handhelds or similar devices using
a Java foundation?
Finally, regardless of language, do developers really
believe that unpredictable garbage collection delays are
acceptable and that consumers will put up with them in the
long run?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Keith
|
12/15/2005 10:48:37 AM
|
|
kanze wrote:
> Dietmar Kuehl wrote:
>
>>gerg wrote:
>>
>>>i beleive that a C++ which used the exception behaviour of
>>>Java would be great for the language. I the example
>>>mentioned above the ignorant programmer would not be able to
>>>compile his code using the [] operator unless he caught a
>>>"invalid access" exception.
>
>
>>Java's behavior with respect to exception specifications is
>>inherently broken.
I agree.
> So is C++'s but the impact in C++ is
>>fortunately not at all that severe. Essentially, the problem
>>is that exception specifications don't mix at all with generic
>>code (here "generic" is used in the sense that any form of
>>polymorphism, not only static polymorphism, is used): an
>>interface (in the classical meaning of "interface" not Java's
>>meaning) accepting polymorphic objects cannot know what kind
>>of exceptions may be thrown from these objects.
>
>
> But it must, to some degree. Otherwise, the LSP doesn't hold,
> and you can't use the derived object in place of the base.
If you intend to recover from the exception is also needed that the
contract specifies what the state of the object is when a
certain exception is thrown.
-Thorsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thorsten
|
12/15/2005 10:50:12 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
> Thorsten Ottosen wrote:
>
>>Andrei Alexandrescu (See Website For Email) wrote:
>>
>>>So... shall we now say that C++ is safe because it allows multiple
>>>inheritance?!?
>>
>>No, but we shall say that there is not tons of other techniques to
>>achieve the same. Not even one.
>
>
> Ehm. Somehow I don't remember of a situation in which I told myself,
> "Gosh! Good thing we have inheritance of implementation. Otherwise this
> whole project would fall apart due to its lack of safety!" Do you? Does
> anyone?
he he, no, but the sentence
"So... shall we now say that C++ is safe because it allows multiple
inheritance?!?"
is yours.
If you believe that the technique James is advocating is useful
and leads to better programs, then we have to acknowledge that
C++ is better is this regard.
If you insist on writing programs with memory errors, the so be it.
Once you get past that problem, more correct programs rely on good
design and flexibility of the language.
People like to draw circle when the illustrate the space of verifiable
programs, "safe ones" like the ones you write in Java of C#.
People forget that the circle of correct programs is a much smaller
circle partly overlapping the circle of verifiable programs. And after
all, I'd rather be in the circle of correct programs than in the circle
of varifiable programs.
-Thorsten
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thorsten
|
12/15/2005 10:50:59 AM
|
|
kanze wrote:
> Walter Bright wrote:
>
>>The bugs come in when the design gets changed - and there's no
>>way to tell by inspection of a C++ class whether its methods
>>are virtual or not, without carefully examining each of its
>>ancestors. THAT is the source of error.
>
>
> The fact that there is no way to know whether a function is
> virtual or not without looking at the entire hierarchy *is* a
> weak point. The fact that a small typo may result in what I
> though was an override not being one is another. What I'd like
> to see is:
>
> virtual:
> Only used to declare the function at the base of the
> hierarchy. A function declared virtual cannot override a
> virtual function below it in the hierarchy.
>
> override:
> Used to declare a virtual function which overloads a
> function below it in the hierarchy. If there isn't such a
> function, an error.
>
> final:
> Used instead of override if the function cannot be further
> overridden.
>
> Functions with none of the above are not virtual, and are not
> considered as part of a virtual hierarchy. Although I would
> accept the idea that all non-virtual functions should be
> declared final as well; i.e. that every member function must be
> qualified with one of the above three qualifiers.
>
> Practically, this would cause too many backward compatibility
> problems for it to be adopted in C++.
>
> I might add that globally, I don't think it that big of an
> issue. The above describes what I consider the ideal; I prefer
> (based on concrete experience with both) the C++ model to that
> in Java, but I can live fairly reasonably with either. In
> practice, I've not had a lot of problems with people overriding
> fucntions they shouldn't, nor with people not declaring virtual
> functions which should be declared virtual. (In practice, of
> course, deep hierarchies are the exception, and typically, the
> only virtual functions are declared pure virtual in the base,
> and are overridden once -- it is very, very rare for a virtual
> function to override a non pure function in a base class.)
It seems to me that the disagreement between Walter and James results
from different concepts of inheritance. If I am not mistaken, it was
Herb Sutter who coined the distinction between "inheriting to reuse" and
"inheriting to be reused" (and leaves no doubt that he prefers the latter).
Inheriting to reuse is typical for monolithic,
all-the-world-is-an-object hierarchies that characterize GUI application
frameworks. Deriving from concrete classes in order to tweak their
behaviour is the norm in such an environment. Naturally, programmers who
are accustomed to this kind of software construction prefer virtual as
the default for member functions, are worried about having to look up
definitions all the way up to the top of the hierarchy, and emphasize
the responsibility of the derived class designer. Now I don't want to
impute that Walter writes software in this way, but given the influence
these frameworks have on the minds of programmers, it is not very risky
to assume that most of his customers do.
Inheriting to be reused (and using aggregation to reuse) emphasizes the
reduction of coupling. There is no single hierarchy. Inheritance trees
are typically small, flat, and isolated. Base classes define a contract
and are abstract. A substantial portion of classes are stand-alone. The
normal case is not to derive and not to override, hence the preferred
default for member functions is non-virtual. Lookup of base class
definitions is not a big deal, as most of the times there is only one
level. It is the responsibility of the base class designer to define a
meaningful contract, and it is up to the derived class designer to
fulfill it. If I understand James correctly, this is where he is coming
from.
In view of these radically different design philosophies, I think it is
rather futile to argue about the safety and maintainability of
inheritance at the language level. Rather than discussing whether the
option to omit the virtual keyword in the declaration of overriding
member functions leads to confusion and maintenance problems, I would
ask whether it isn't deep hierarchies themselves that are confusing and
hard to maintain.
Leading C++ experts advocate inheriting to be reused, and I agree with
them. What I have not been able to make up my mind about so far is
whether this is peculiar to the C++ language, or a general software
engineering principle. In other words, are monolithic hierarchies
preferable in Java or C# because they are idiomatic in these languages,
or are they indications of questionable language design? After all,
minimizing coupling is an engineering goal worth pursuing, regardless of
the language.
--
Gerhard Menzl
#dogma int main ()
Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
12/15/2005 2:17:37 PM
|
|
Keith H Duggar wrote:
> kanze wrote:
> > Walter Bright wrote:
> > > "kanze" <kanze@gabi-soft.fr> wrote in message
> > > > ... I've started using garbage collection regularly
> > > > with C++, for example, and I've yet to encounter a
> > > > measurable slow-down.
> > > I've often gotten significant performance *improvements*
> > > when switching to garbage collection,
> > I don't doubt it. I would expect improvements, on the
> > average.
> I have a concern. I've been using a BlackBerry for more than a
> year now and my understanding is that it is a JVM running
> applications written in Java.
> In all honestly, it has been an annoying device to use for
> two primary reasons.
> First, the amount of time any particular action takes seems
> unpredictable. Sometimes opening the message log might be
> immediate sometimes it may take a few seconds. Escaping out
> of a menu might be immediate or it might take several seconds.
> I'm saying that seemingly identical actions take largely
> varying amounts of time. Isn't this one of the symptoms of
> automatic garbage collection?
Not necessarily. It depends on the implementation of the
garbage collection, and how it is used. In an interactive
device, as you seem to be describing, only a very poor garbage
collector will manifest such symptoms. With two qualifications:
with a totally generalized collector, like the Boehm collector,
you may have to take explicit steps to ensure the correct
behavior, and in some cases, where the processing in response to
an input is very long, you may notice a threashhold effect --
response time is more or less linear according to the
complexity, up to a point, and then there is a big jump. (This
can be avoided by giving the garbage collector more memory to
work with.)
In general, in an interactive application, the garbage collector
should run while the program is waiting for user input. Which
means that it should never have to run when processing user
input.
> If so, from a consumer perspective I find it nearly
> intolerable as I wait in anticipation to see if my last input
> was registered.
> Second, the device seems to crash every month or so and throws
> exceptions that amusingly appear on the screen as if a user
> can somehow take corrective action. And they read as the
> usual exceptions we've all seen from Java programs. The
> previous cell phone I had, a Motorola, ran software written in
> who knows what (assembly?) and it never once crashed in four
> years I used it. And it never through any exceptions. I
> can't help but wonder if the Java philosophy played a role in
> creating this software that seems to so often throw and crash?
Maybe. At least the part of it which says that everything out
of the ordinary, from incorrect user input through virtual
machine failure, should be reported by an exception. More
likely, however, it's just careless programming. While I prefer
C++ for the type of work I do, I can assure you that there is
nothing in Java either which forces you to write bad code; in
the applications I've written in Java, I can assure you that the
user never saw a trace of an exception, except, perhaps, for
something like virtual machine failure, over which the
programmer has no influence. (We did have to write our own
event handler, and a couple of other basic classes, to achieve
this.)
Another possibility is simply that there are serious errors in
the virtual machine. I didn't have any major problems with this
under Windows, but it's possible that some of the ports to other
environments were done too quickly. (Note that this can also be
a serious problem with C++. There's never any guarantee
concerning the quality of the compiler, or of the OS.)
> Can anyone shed some light on this BlackBerry software
> quality? What about other handhelds or similar devices using
> a Java foundation?
> Finally, regardless of language, do developers really believe
> that unpredictable garbage collection delays are acceptable
> and that consumers will put up with them in the long run?
Regardless of language, does anyone really think today that
garbage collection results in unpredictable delays? I use any
number of programs which use garbage collection, written in a
variety of languages, and while this might have been a problem
fifteen years ago, garbage collection technology has evolved
since then.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/15/2005 2:19:37 PM
|
|
Thorsten Ottosen wrote:
> kanze wrote:
> > Dietmar Kuehl wrote:
> >>gerg wrote:
> >>>i beleive that a C++ which used the exception behaviour of
> >>>Java would be great for the language. I the example
> >>>mentioned above the ignorant programmer would not be able
> >>>to compile his code using the [] operator unless he caught
> >>>a "invalid access" exception.
> >>Java's behavior with respect to exception specifications is
> >>inherently broken.
> I agree.
> >> So is C++'s but the impact in C++ is
> >>fortunately not at all that severe. Essentially, the
> >>problem is that exception specifications don't mix at all
> >>with generic code (here "generic" is used in the sense that
> >>any form of polymorphism, not only static polymorphism, is
> >>used): an interface (in the classical meaning of "interface"
> >>not Java's meaning) accepting polymorphic objects cannot
> >>know what kind of exceptions may be thrown from these
> >>objects.
> > But it must, to some degree. Otherwise, the LSP doesn't
> > hold, and you can't use the derived object in place of the
> > base.
> If you intend to recover from the exception is also needed
> that the contract specifies what the state of the object is
> when a certain exception is thrown.
This raises another issue. I'm mentionned it once or twice in
passing, generally while discussing programming by contract.
You are completely correct -- a function needs a separate
contract when it exits via an exception. In C++, we could write
something like:
void f()
{
check_precondtions() ;
try {
doF() ;
check_normal_postconditions() ;
} catch ( ... ) {
check_exceptional_postconditions() ;
throw ;
}
}
I don't know if many people do. In Java, *and* in Eiffel, as
far as I know, there is nothing you can do.
Of course, most of the time, the exceptional post-conditions
will be limited to a very small subset of the class invariants.
Enough to ensure that the object can be destructed, and not
more. In such cases, there may be no real checking to do.
--
James Kanze GABI Software
Conseils en informatique orient�e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S�mard, 78210 St.-Cyr-l'�cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
kanze
|
12/15/2005 2:20:31 PM
|
|
* Alf P. Steinbach:
> * Alf P. Steinbach will write, in a posting sent a few seconds ago:
>
> {
> try
> {
> _NormalCaseCode;
> }
> catch( ... )
> {
> _FailureHandling;
> throw;
> }
> }
>
> where an important sentence disappared during editing,
>
> except that _FailureHandling has access to the declarations in the
> 'try' block
>
> This was all to avoid introducing a more complicated equivalence with loop
> and use of hypothetical internal C++ compiler features.
And that was a Really Bad Idea, because _FailureHandling would have
access to potentially not constructed variables.
I can still think of ways to ensure *textual execution order* (lacking in
on_scope_failure) that honor the normal access rules, e.g., instead of the
proposed
{
_NormalCaseCode1
on_scope_failure
{
_FailureHandlingCode // Not textual execution order, which
// gets worse with 2 or more such.
}
_NormalCaseCode2
}
using a semantically equivalent construct like
{
_NormalCaseCode1
commit_or_throw
{
_NormalCaseCode2
}
rollback
{
_FailureHandling // Textual execution order.
}
}
where rollback (presumably the principal usage of on_scope_failure) only has
access to declarations before commit_or_throw, with C++98 _exact_ equivalent
// C++98 equivalent:
{
_NormalCaseCode1
try
{
_NormalCaseCode2
}
catch( ... )
{
_FailureHandling
throw;
}
}
and where a commit only if three or more separate code snippets succeed can
be simply expressed via nesting of the construct.
In addition to solving the textual order versus execution order problem
(that later code can be executed before earlier code with on_scope_failure),
this also solves the conceptual problem of
int x = 1;
on_scope_failure { ... }
int x = 2;
which with commit_or_throw is much more clear:
int x = 1;
commit_or_throw { int x = 2; }
rollback { ... }
And hm, with commit_or_throw the code suddenly became more readable, I
think...
And double hm, this is very very close to the "succeed_or_throw" I proposed
earlier(*). In fact it's a simple subset of that functionality. Capturing
the desirable guaranteed throwing behavior of the _FailureHandling code that
on_scope_failure has, but without the non-textual execution order problem,
and without the seemingly redeclared variable problem.
*:
<url: http://groups.google.com/group/comp.std.c++/msg/b37094d7a003cb4b>
<url:
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/c9afdba1f852cee7/98b002b20ebe5cb0>
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/15/2005 2:21:15 PM
|
|
kanze wrote:
> Keith H Duggar wrote:
>
>>Finally, regardless of language, do developers really believe
>>that unpredictable garbage collection delays are acceptable
>>and that consumers will put up with them in the long run?
>
>
> Regardless of language, does anyone really think today that
> garbage collection results in unpredictable delays?
I don't think. I've seen it.
For example KMP string search being slower then brute force
search :) On machine without hyperthreading KMP is loser on
machine with hyperthreading faster. Totally unpredictable.
I use any
> number of programs which use garbage collection, written in a
> variety of languages, and while this might have been a problem
> fifteen years ago, garbage collection technology has evolved
> since then.
Garbage collection has serious problems in multi threading
environment. It stops the world while simple dealocation
might or might not block. That is a big problem,
and cause of stutters.
GC is not a magic, It is complex program that takes
a lot of CPU cycles, worse yet, it thrashes CPU cache
like no tommorow. While I;m for it, it is a mit
that GC cames without costs. It is reasonable
in applications that are not memory hungry or
not expected to service more then 60 clients
on average PC. But if you want 1400 requests per
second per CPU, GC is simply out of question.
I can imagine simple collector to perform reasonably,
but just imagine one that moves data around.
It has to *update* all references.
Finally, GC cames pretty good on application
that previously implemented some form of it and
suited memory usage accordingly.
Greetings, Bane.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Branimir
|
12/15/2005 11:17:13 PM
|
|
Alf P. Steinbach wrote:
> * Alf P. Steinbach will write, in a posting sent a few seconds ago:
>
> {
> try
> {
> _NormalCaseCode;
> }
> catch( ... )
> {
> _FailureHandling;
> throw;
> }
> }
>
> where an important sentence disappared during editing,
>
> except that _FailureHandling has access to the declarations in the
> 'try' block
But that's totally wrong and exposes the fallacy of the entire construct.
You end up accessing uninitialized variables inside the try block.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/15/2005 11:17:50 PM
|
|
Niklas Matthies wrote:
> On 2005-12-14 20:59, Andrei Alexandrescu (See Website For Email) wrote:
> :
>
>>I will define below the semantics of the three on_scope_xxx constructs
>>by constructions, that is, I will define transformations that take code
>>using on_scope_xxx and yield code using try/catch/finally.
>
> [definitions snipped]
>
> Two comments:
>
> 1. When the on_scope_xxx block throws an exception, shouldn't this be
> handled as with throwing from destructors?
>
> 2. The transformations you use have the consequence that e.g.
>
> int a = 1;
> ...
> on_scope_xxx { ... }
> ...
> int a = 2;
>
> becomes valid while
>
> int a = 1;
> ...
> int a = 2;
>
> of course is still invalid.
> I presume that's not really your intent, or is it?
HAHAHAHAHA!!!!
Ok, here's the story. As I pressed Control-Enter to send the post you're
answering to, the following train of events happened:
1. I realized the transformation doesn't allow postponing semantic
checks because of the problem you mentioned.
2. I thought I'll add somewhere a comment.
3. I pressed Escape while Thunderbird was sending the message and
showing me that annoying little window.
4. Thunderbird said something like "Can't save to the Sent folder. Do
you want to return to the compose window?"
5. Thinking that nothing bad will happen if I press Esc again, I did.
6. Thunderbird closed the window and I lost every my trace of my message.
Talk about transactional code :o).
So, yes, the intent was that _statements_1 and _statements_3 are
semantically in the same scope.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/15/2005 11:18:31 PM
|
|
Steven E. Harris wrote:
> Andrei Alexandrescu <SeeWebsiteForEmail@moderncppdesign.com> writes:
>
>
>>{
>> bool __failed = false;
>> _statements_1
>> try {
>> _statements_2
>> } catch (...) {
>> __failed = true;
>> throw;
>> } finally {
>> if (__failed) ; else {
>> _statements_3
>> }
>> }
>>}
>
>
> Why the catch block?
>
> {
> _statements_1
> bool __failed = true;
> try {
> _statements_2
> __failed = false;
> } finally {
> if (__failed) ; else {
> _statements_3
> }
> }
> }
Because in the general case, _statements_2 could contain a return,
continue, break, or goto.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/15/2005 11:19:28 PM
|
|
"Steven E. Harris" <seh@panix.com> writes:
> Why the catch block?
>
> {
> _statements_1
> bool __failed = true;
> try {
> _statements_2
> __failed = false;
> } finally {
> if (__failed) ; else {
> _statements_3
> }
> }
> }
Replying to myself, if _statements_2 is a return statement my
transformation would not behave identically to Andrei's original
version.
--
Steven E. Harris
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Steven
|
12/15/2005 11:20:17 PM
|
|
Alf P. Steinbach wrote:
> * Alf P. Steinbach:
> And that was a Really Bad Idea, because _FailureHandling would have
> access to potentially not constructed variables.
Ok.
> I can still think of ways to ensure *textual execution order* (lacking in
> on_scope_failure) that honor the normal access rules, e.g., instead of the
> proposed
>
> {
> _NormalCaseCode1
> on_scope_failure
> {
> _FailureHandlingCode // Not textual execution order, which
> // gets worse with 2 or more such.
> }
> _NormalCaseCode2
> }
>
> using a semantically equivalent construct like
>
> {
> _NormalCaseCode1
> commit_or_throw
> {
> _NormalCaseCode2
> }
> rollback
> {
> _FailureHandling // Textual execution order.
> }
> }
But until you understand what you're looking for, all attempts are
doomed. And right now, you're looking for the wrong solution all over
the map.
Look at your solution. It won't scale. If the code changes nonlocal
state in six steps, each of which might fail, you'll end up indenting
six levels deep and obfuscating the code to no end.
The problem with try/catch is that they look great on paper. For
one-liners, it's really seductive. "I want to do this thing, and if it
won't work, I want to take these measures." It's great for one-liners,
but it won't scale anywhere beyond that. This is because you need to
address each state change individually, not globally - and the execution
stack is a great automatic place to keep that information for you. With
try/catch, you need to track your state evolution by hand, either with
additional state or by nesting try/catch constructs. Both idioms are
untenable.
> In addition to solving the textual order versus execution order problem
> (that later code can be executed before earlier code with on_scope_failure),
> this also solves the conceptual problem of
>
> int x = 1;
> on_scope_failure { ... }
> int x = 2;
That's not supposed to compile, see below.
> which with commit_or_throw is much more clear:
>
> int x = 1;
> commit_or_throw { int x = 2; }
> rollback { ... }
>
> And hm, with commit_or_throw the code suddenly became more readable, I
> think...
There's no problem. The contigency code must NOT execute in textual
order, but in REVERSE order following success of each individual action.
Destructors have clearly shown the soundness of such an approach.
Your feature is trying to solve a problem that doesn't exist, and in the
process introduces a problem that did not exist. I think we're dealing
more with clinging to old wrong habits more and a mild overpreoccupation
for SESE, than anything else. For example try implementing my
five-liner, no indent example:
void Widget::foo(const bool condition) {
cont.push_back(elem);
on_scope_failure { cont.pop_back(); }
if (condition) ++counter;
on_scope_failure { if (condition) --counter; }
database.insert(serialize(elem));
}
with your feature:
void Widget::foo(const bool condition) {
cont.push_back(elem);
commit_or_throw {
if (condition) ++counter;
commit_or_throw {
database.insert(serialize(elem));
}
rollback { if (condition) --counter; }
}
rollback { cont.pop_back(); }
}
YUCKITY YUCK!!!
> And double hm, this is very very close to the "succeed_or_throw" I proposed
> earlier(*).
And, as Seinfeld replied to Elaine's "But I love to dance!" -- that
doesn't help either!
> In fact it's a simple subset of that functionality. Capturing
> the desirable guaranteed throwing behavior of the _FailureHandling code that
> on_scope_failure has, but without the non-textual execution order problem,
> and without the seemingly redeclared variable problem.
The seemingly redeclared variable is a semantic check that in my vision
must fail before transformation. It escaped to me when I posted the
intended transformations for on_scope_xxx. To be clear:
{
_statements_1
on_scope_failure { _statements_3 }
_statements_2
}
is equivalent with:
{
_statements_1
try {
_statements_2
} catch (...) {
_statements_3
throw;
}
if (false) {
_statements_1
_statements_2
}
}
That is, the sequence _statements_1 _statements_2 must pass regular
semantic checks that apply to statements within the same scope.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/16/2005 10:35:18 AM
|
|
Alf P. Steinbach wrote:
> * Andrei Alexandrescu (See Website For Email):
>
>>3. on_scope_success has the following semantics:
>>
>>{
>> _statements_1
>> on_scope_success { _statements_3 }
>> _statements_2
>>}
>>
>>is equivalent with:
>>
>>{
>> bool __failed = false;
>> _statements_1
>> try {
>> _statements_2
>> } catch (...) {
>> __failed = true;
>> throw;
>> } finally {
>> if (__failed) ; else {
>> _statements_3
>> }
>> }
>>}
>
>
> I'd write that as
>
> {
> _statements_1
> _statements_2
> _statements_3
> }
>
> ;-)
(Thank God for that emoticon.)
> As you put it in an earlier reply to me in this thread,
> Think.Inside.The.Box.
I've been. For too long :o).
> Which means that on_scope_success' only reason for existence is to write
> _statements_3 out of execution order, so that in general later statements in
> a sequence can be executed before earlier statements, which IMHO is UnGood.
No. on_scope_success' reason for existence is to lead to more
maintainable code. For some odd reason, control flow statements such as
return, continue, and break are still around and many seem to enjoy
using them.
With your sequence
{
_statements_1
_statements_2
_statements_3
}
I can say nothing. With the sequence:
{
_statements_1
on_scope_success { _statements_3 }
_statements_2
}
I can say for sure that _statements_3 will be executed if ever reached
and if the scope is exited normally, without having to look at
_statements_2. Thus, _statements_2 can contain a control flow statement
or not, and that won't affect correctness. That's great for maintainability.
> Needless to say, perhaps, that means I don't like the other two proposed
> constructs, namely on_scope_exit (essentially 'finally' only worse) and
> on_scope_success (a distillation of the very worst in 'finally'), which I
> think are the result of over-generalization, with the very undesirable
> consequence of mixing success and failure case code, like 'finally', and in
> addition altering the execution order of sequential statements.
It was only after having freed my mind of "success and failure case code
must be separated" preconception, when I realized just what a fallacy is.
It would be great if they were separated, but that wastes all causality
information. *Where* did the computation fail? With on_scope_xxx you'll
know as a natural outcome of control flow reaching the statement or not.
> Yes, on_scope_exit would solve the general problem of destroying a non-C++
> resource, like e.g. closing a file handle. But it would do that at the
> level of abstraction of 'goto', allowing so much else, so many Bad things!
It's not a statement that introduces supplemental control flows.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/16/2005 10:35:40 AM
|
|
kanze wrote:
> Keith H Duggar wrote:
> > I can't help but wonder if the Java philosophy played a
> > role in creating this software that seems to so often
> > throw and crash?
>
> Maybe. At least the part of it which says that everything
> out of the ordinary, from incorrect user input through
> virtual machine failure, should be reported by an
> exception. More likely, however, it's just careless
> programming ... I can assure you that there is nothing
> in Java either which forces you to write bad code;
Oh of course I agree. I realize there is nothing forcing one
to write bad Java code. I was just curious if there was a
Java mentality that lead to code more likely to throw
unhandled exceptions.
> in the applications I've written in Java, I can assure you
> that the user never saw a trace of an exception, except,
> perhaps, for something like virtual machine failure, over
> which the programmer has no influence. (We did have to
> write our own event handler, and a couple of other basic
> classes, to achieve this.)
That sounds great. Of course you are developer who has much
experience with C++ and I'm sure many other languages. I
wonder how much your experience from a C++ perspective
played a role in leading you to develop such robust software
in Java?
> > First, the amount of time any particular action takes
> > seems unpredictable ... Isn't this one of the symptoms
> > of automatic garbage collection?
>
> Not necessarily. It depends on the implementation of the
> garbage collection, and how it is used. In an interactive
> device, as you seem to be describing, only a very poor
> garbage collector will manifest such symptoms ... In
> general, in an interactive application, the garbage
> collector should run while the program is waiting for user
> input. Which means that it should never have to run when
> processing user input.
....
> > Finally, regardless of language, do developers really believe
> > that unpredictable garbage collection delays are acceptable
> > and that consumers will put up with them in the long run?
>
> Regardless of language, does anyone really think today that
> garbage collection results in unpredictable delays? I use any
> number of programs which use garbage collection, written in a
> variety of languages, and while this might have been a problem
> fifteen years ago, garbage collection technology has evolved
> since then.
Ok I didn't realize the concern was so dated. In many of the
recent garbage collection threads in this and other
newsgroups, collection delays at inconvenient times has been
raised as one con of automatic garbage collection. Are you
saying this is no longer a practical concern?
I've only implemented toys in Java such as block-breaker
games etc, and some very small simulators. However, in these
toys there were certainly noticeable delays at
unpredictable times and the only explanation seemed to be
garbage collection. Had I been more adept at Java coding
could I have avoided these delays? Or made them more
deterministic?
However, if this is no longer an issue with off-the-shelf
Java and similar implementations, then I'm glad to hear it.
Is that really the case?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Keith
|
12/16/2005 10:36:40 AM
|
|
* Andrei Alexandrescu (See Website For Email):
> Alf P. Steinbach wrote:
> > * Alf P. Steinbach will write, in a posting sent a few seconds ago:
> >
> > {
> > try
> > {
> > _NormalCaseCode;
> > }
> > catch( ... )
> > {
> > _FailureHandling;
> > throw;
> > }
> > }
> >
> > where an important sentence disappared during editing,
> >
> > except that _FailureHandling has access to the declarations in the
> > 'try' block
>
> But that's totally wrong
Yes. ;-)
Here's the story. First, I wrote that sentence. Then, a millisecond later,
I saw that it was UnGood and removed it. Then I posted the article. Then
somehow I got the idea that the sentence really should have been there, had
been removed in error, and posted the correction above. Then I had a very
nice dinner, and wham!, it hit me: I must be the most stupid human ever
existing on planet Earth. So I posted a correction to the correction...
<url:
http://groups.google.com/group/comp.lang.c++.moderated/msg/1afaa81411a2c66b>.
Evidently that hadn't appeared when you responded.
Anyway, the construct, sans the totally wrong statement, has
1 + No textual-versus-execution order problem
(on_scope_failure has that problem,
allowing textually later code to change variables used by earlier code).
2 + No variable redeclaration problem
(on_scope_failure has that problem,
allowing a variable to be apparently redeclared in the same scope).
3 - A need for introducing a bare scope in some cases
(no such problem with on_scope_failure).
and is otherwise the same as on_scope_failure.
> and exposes the fallacy of the entire construct.
If there is a fallacy in on_scope_failure, that would be interesting to know
about...
Examples of the two on_scope_failure problems I currently know:
// Textually later code affecting textually earlier code:
int uhuh = 0;
int* p = 0;
{
p = new int(666);
// At this point what's established so far cannot be assumed to
on_scope_failure{ delete p; p = 0; } // hold for the cleanup code.
_MaintenanceProgrammersCode // Not primarily because of this, but
// because this undo/rescue action is actually executed first:
on_scope_failure{ p = &uhuh; } // invalidating earlier assumption.
_MoreCode
}
// Apparent redeclaration:
{
int x = 1;
on_scope_failure {}
int x = 2; // Hm.
}
But I think you may be referring to some vague idea that with my suggested
on_failure, which is limited to the end of a scope only, declarations in the
normal case code cannot be referenced in the failure handler.
The following snippets (a) using your proposed on_scope_failure, (b) using
my suggested restriction to failure handler only at the end of the scope
(which solves the two problems noted above), and (c) using the more clean
and problem-free syntax I introduced in the posting with URL given above,
are equivalent in what they do,
except that (a), on_scope_failure, has the problems of execution order and
allowing redeclarations in apparently the same scope, and (b) has the
problem of introducing/requiring a bare scope in the most general case:
// a
{
_NormalCase1
on_scope_failure
{
_FailureHandling
}
_NormalCase2
}
// b
{
_NormalCase1
{
_NormalCase2
on_failure
{
_FailureHandling
}
}
}
// c
{
_NormalCase1
succeed_or_throw
{
_NormalCase2
}
rollback
{
_FailureHandling
}
}
I think (c) could perhaps benefit from more general keywords, but the point
is, it doesn't suffer from the problems of (a) and (b).
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/16/2005 10:37:07 AM
|
|
* kanze:
>
> Regardless of language, does anyone really think today that
> garbage collection results in unpredictable delays?
If I think garbage collection predictably causes delays, should I answer yes
or no? ;-)
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/16/2005 10:38:04 AM
|
|
* Andrei Alexandrescu (See Website For Email) -> Alf P. Steinbach:
>
> With your sequence
>
> {
> _statements_1
> _statements_2
> _statements_3
> }
>
> I can say nothing. With the sequence:
>
> {
> _statements_1
> on_scope_success { _statements_3 }
> _statements_2
> }
>
> I can say for sure that _statements_3 will be executed if ever reached
> and if the scope is exited normally, without having to look at
> _statements_2. Thus, _statements_2 can contain a control flow statement
> or not, and that won't affect correctness. That's great for maintainability.
In other words, on_scope_success is a device for
* Having code executed out of textual order,
_and_
* Employing multiple normal-case exits in a technically safe way.
IMO both make for less readability and are therefore detrimental to
maintainability, and I think the SEME-support feature would be abused.
However, since the great SESE versus SEME debate never concluded anything
(as I recall), it's probably less than fruitful to discuss that further;
it's an agree to disagree situation, I believe.
[snip]
> It was only after having freed my mind of "success and failure case code
> must be separated" preconception, when I realized just what a fallacy is.
>
> It would be great if they were separated, but that wastes all causality
> information. *Where* did the computation fail? With on_scope_xxx you'll
> know as a natural outcome of control flow reaching the statement or not.
Let's write small functions and blocks where that doesn't matter. ;-)
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/16/2005 4:44:49 PM
|
|
* Andrei Alexandrescu (See Website For Email):
> * Alf P. Steinbach:
>
> > I can still think of ways to ensure *textual execution order* (lacking in
> > on_scope_failure) that honor the normal access rules, e.g., instead of the
> > proposed
> >
> > {
> > _NormalCaseCode1
> > on_scope_failure
> > {
> > _FailureHandlingCode // Not textual execution order, which
> > // gets worse with 2 or more such.
> > }
> > _NormalCaseCode2
> > }
> >
> > using a semantically equivalent construct like
> >
> > {
> > _NormalCaseCode1
> > commit_or_throw
> > {
> > _NormalCaseCode2
> > }
> > rollback
> > {
> > _FailureHandling // Textual execution order.
> > }
> > }
>
> But until you understand what you're looking for, all attempts are
> doomed. And right now, you're looking for the wrong solution all over
> the map.
>
> Look at your solution. It won't scale. If the code changes nonlocal
> state in six steps, each of which might fail, you'll end up indenting
> six levels deep
No, I'll end up with three to six small state-change functions. I know you
agree with me (and probably all the rest here) that one shouldn't try to
overstuff a function. So I think that argument is again based on a too
strong sense of logical completeness: that the construct should support
whatever complexity parameters one throws at it. But I don't think it
should. I think it should be streamlined for reasonable code, and
preferably not supporting the overstuffing.
> and obfuscating the code to no end.
I disagree with that: I think implicit things are obfuscating, and I think
overstuffed functions are obfuscating. Regarding the former, if a scope
does something extra when it exits via an exception, I want to be able to
determine that by looking at a known place, such as the end of the scope. I
don't want to have to go looking for some well-hidden on_scope_failure in a
fortythree line block.
> The problem with try/catch is that they look great on paper. For
> one-liners, it's really seductive. "I want to do this thing, and if it
> won't work, I want to take these measures." It's great for one-liners,
> but it won't scale anywhere beyond that. This is because you need to
> address each state change individually, not globally - and the execution
> stack is a great automatic place to keep that information for you. With
> try/catch, you need to track your state evolution by hand, either with
> additional state or by nesting try/catch constructs. Both idioms are
> untenable.
Not sure, but we evidently agree that try-catch is at a too low level of
abstraction for many of its applications, such as transactions.
> > In addition to solving the textual order versus execution order problem
> > (that later code can be executed before earlier code with on_scope_failure),
> > this also solves the conceptual problem of
> >
> > int x = 1;
> > on_scope_failure { ... }
> > int x = 2;
>
> That's not supposed to compile, see below.
OK.
> > which with commit_or_throw is much more clear:
> >
> > int x = 1;
> > commit_or_throw { int x = 2; }
> > rollback { ... }
> >
> > And hm, with commit_or_throw the code suddenly became more readable, I
> > think...
>
> There's no problem. The contigency code must NOT execute in textual
> order, but in REVERSE order following success of each individual action.
There is no mutual exclusion, as the commit_or_throw construct shows: with
commit_or_throw, contingency (rollback) code must execute in textual order,
AND it must execute in the reverse order of the order each action succeeded.
> Destructors have clearly shown the soundness of such an approach.
Nope. Destructors have shown that cleaning up in reverse order of
successful actions is sound. But they have shown nothing about non-textual
order of execution in a sequence, and so are irrelevant for that.
> Your feature is trying to solve a problem that doesn't exist, and in the
> process introduces a problem that did not exist.
?
> I think we're dealing
> more with clinging to old wrong habits more and a mild overpreoccupation
> for SESE, than anything else. For example try implementing my
> five-liner, no indent example:
>
> void Widget::foo(const bool condition) {
> cont.push_back(elem);
> on_scope_failure { cont.pop_back(); }
> if (condition) ++counter;
> on_scope_failure { if (condition) --counter; }
> database.insert(serialize(elem));
> }
>
> with your feature:
>
> void Widget::foo(const bool condition) {
> cont.push_back(elem);
> commit_or_throw {
> if (condition) ++counter;
> commit_or_throw {
> database.insert(serialize(elem));
> }
> rollback { if (condition) --counter; }
> }
> rollback { cont.pop_back(); }
> }
>
> YUCKITY YUCK!!!
Yes, I agree, it's UnGood to use that inline conditional increment of
'counter'. Probably it counts elements of a given type or with certain
properties, which could be expressed via function names. If it absolutely
must be updated inline, in the same code, then e.g.
void Widget::foo(const bool condition) {
cont.push_back(elem);
commit_or_throw {
database.insert(serialize(elem));
if (condition) ++counter;
}
rollback { cont.pop_back(); }
}
but I think structure is more important than minimum number of lines, so
given a choice I'd do
void Widget::foo_not_counted() {
cont.push_back(elem);
commit_or_throw { database.insert(serialize(elem)); }
rollback { cont.pop_back(); }
}
void Widget::foo_counted() {
foo_not_counted();
++counter;
}
void Widget::foo(const bool condition) {
condition? foo_counted() : foo_not_counted();
}
And if against expection ++counter can throw, I'd put a commit_or_throw in
foo_counted, like this:
void Widget::foo_counted() {
++counter;
commit_or_throw { foo_not_counted(); }
rollback { --counter }
}
> > And double hm, this is very very close to the "succeed_or_throw" I proposed
> > earlier(*).
>
> And, as Seinfeld replied to Elaine's "But I love to dance!" -- that
> doesn't help either!
Well, it does, for dancing, obviously...
And if you come up with nearly the same solution to two different problems
(the problem I was addressing then was something like Eiffel retry) then
that solution is more likely to be of general value.
Also, that connection means that there is a possibility of generalizing the
construct in certain ways, e.g. towards handling double exceptions.
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/16/2005 4:45:45 PM
|
|
>>>a factor. I've started using garbage collection regularly
>>>with C++, for example, and I've yet to encounter a
>>>measurable slow-down.
>
>
>>I've often gotten significant performance *improvements* when
>>switching to garbage collection,
>
>
> I don't doubt it. I would expect improvements, on the average.
One thing to consider here is that heap allocators in GC are often much
better implemented/optimized.
E.g. I have created my custom global heap allocator (I mean, normal
global new/delete replacement, no GC) inspired by the implementation
idea of Boehms GC (divide memory to 4KB pages and use single page for
single small block size) and got quite interesting 30% performance boost
(compared to glibc malloc/free) in memory intensive C++ code. Plus, as
the free aditional bonus, lower memory consumption (again compared to
glibc).
Anyway, speaking GC, I do not like it as it is inherently incompatible
with destructors, means GC can take care of your memory resources, but
that is all. It will not close your windows in GUI or release your lock.
Therefore I prefer deterministic and automatic resource management...
Mirek
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mirek
|
12/17/2005 10:54:28 AM
|
|
Alf P. Steinbach wrote:
>>It was only after having freed my mind of "success and failure case code
>>must be separated" preconception, when I realized just what a fallacy is.
>>
>>It would be great if they were separated, but that wastes all causality
>>information. *Where* did the computation fail? With on_scope_xxx you'll
>>know as a natural outcome of control flow reaching the statement or not.
>
>
> Let's write small functions and blocks where that doesn't matter. ;-)
"This function is too complicated and needs to be broken into several
smaller functions" has become too much of a cop-out lately. Usually I
noticed SESE people use it whenever their style of coding complicates
things too much. If a language feature allows writing said functions
tersely and clearly, then the argument "function too complicated" can't
be applied anymore.
Andrei
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
12/17/2005 10:57:41 AM
|
|
Andrei Alexandrescu (See Website For Email) wrote:
>
> You got to have lexically-scoped functions to implement on_block_xxx the
> way you mentioned, and that's a language change.
>
What about using
void f( bool param )
{
if ( param ) ++x;
struct UndoX
{
UndoX( bool param_ ) : param ( param_ ) {}
bool param;
~UndoX()
{
cout << "UndoX " << x;
if ( param ) --x;
cout << " -> " << x << endl;
}
} _UndoX( param );
throw "throw";
}
And wrap it in some macro shugar
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
mzaytsev2
|
12/17/2005 11:04:04 AM
|
|
Mirek Fidler wrote:
> Anyway, speaking GC, I do not like it as it is inherently incompatible
> with destructors, means GC can take care of your memory resources, but
> that is all. It will not close your windows in GUI or release your
> lock. Therefore I prefer deterministic and automatic resource
> management...
I would argue that GC isn't "inherently" incompatible with destructors.
The reason that they seem to be is because of the notion that the
destruction of an object has anything to do with the release of the
associated memory. This could arguably be the case for C++ POD objects
and (more importantly) you can argue that this is a notion that has
been carried over from C (where the memory is 'the object').
If look into the separation of object scoping and memory release there
may well a way to create a GC paradigm that takes care of the memory,
but "The Language" enforcing the expected, deterministic object
lifetime.
To put it into a C++ context, the system would ensure that the objects
destructor would be called at a deterministic point in the object's
lifetime but the memory occupied would be shunted off to a GC queue
that would eventually be cleared down at a time more convenient to the
system.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Timo
|
12/18/2005 12:26:55 PM
|
|
Keith H Duggar wrote:
> kanze wrote:
>> Keith H Duggar wrote:
>> > Finally, regardless of language, do developers really believe
>> > that unpredictable garbage collection delays are acceptable
>> > and that consumers will put up with them in the long run?
>>
>> Regardless of language, does anyone really think today that
>> garbage collection results in unpredictable delays? I use any
>> number of programs which use garbage collection, written in a
>> variety of languages, and while this might have been a problem
>> fifteen years ago, garbage collection technology has evolved
>> since then.
>
> Ok I didn't realize the concern was so dated. In many of the
> recent garbage collection threads in this and other
> newsgroups, collection delays at inconvenient times has been
> raised as one con of automatic garbage collection. Are you
> saying this is no longer a practical concern?
>
Given the existence of good, thread-safe, interruptible, generational
garbage collectors, it shouldn't be an issue. You should be able to set
things up such that garbage collection only runs when the system is idling
(waiting for user input, for example), and only runs for short enough
periods of time to not cause disruption.
To take a game engine as an example: the garbage collector could live in its
own thread, and only run when the current frame has finished rendering, but
the system isn't ready to start rendering the next thread.
> I've only implemented toys in Java such as block-breaker
> games etc, and some very small simulators. However, in these
> toys there were certainly noticeable delays at
> unpredictable times and the only explanation seemed to be
> garbage collection. Had I been more adept at Java coding
> could I have avoided these delays? Or made them more
> deterministic?
>
I'm not a Java programmer (again, I've only really implemented toy
programs), but I understand that the techniques in
http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html will let you tune
Sun's garbage collector until it's not noticeable. Secondly, the
java.lang.System.gc() call provides you with a way to hint to the JVM that
now would be a good time to garbage collect, and hopefully reduce the
chances of a long collection run while the user is waiting.
--
Simon Farnsworth
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Simon
|
12/18/2005 12:38:36 PM
|
|
* Andrei Alexandrescu (See Website For Email):
> * Alf P. Steinbach:
> > > It was only after having freed my mind of "success and failure case code
> > > must be separated" preconception, when I realized just what a fallacy is.
> > >
> > > It would be great if they were separated, but that wastes all causality
> > > information. *Where* did the computation fail? With on_scope_xxx you'll
> > > know as a natural outcome of control flow reaching the statement or not.
> >
> > Let's write small functions and blocks where that doesn't matter. ;-)
>
> "This function is too complicated and needs to be broken into several
> smaller functions" has become too much of a cop-out lately. Usually I
> noticed SESE people use it whenever their style of coding complicates
> things too much. If a language feature allows writing said functions
> tersely and clearly, then the argument "function too complicated" can't
> be applied anymore.
Mostly it can. The question is whether the textual structure reflects (or
should reflect) the logical structure. It's not difficult to not indent the
code, but when employing if, for, and other such constructs, one tends to
react to non-indented code, because "{" and "}", or the absence of them,
tell where the indentation should have been.
If on the other hand the same code is expressed with a new conditional jump
construct, say, jumpif(...), with that construct hypothetically being
generally accepted as a clean one, perhaps because it has been demonstrated
that on its own, each single use, it can do No Evil, then I think one might
get away with no indentation. For the the logical structure, the actual
complexity, is then not directly apparent at a glance. With jumpif, or some
other such construct, one must laboriously _analyze_ the code to see whether
it's actually trivial or actually very complex.
Orchestrating the simultanous update of six or more interdependent globals
in a transactional manner is not trivial; e.g. the order of statements might
be crucial.
Yet textually, with a sufficiently jumpif-like construct, this inherent
actual complexity can be hidden so that a trivial function looks much the
same, textually, as this one -- only revealed by careful analysis.
That's the reason why we generally frown on use of goto and regard goto as
unclean: while each single use of the C++ variant of goto can do No Evil, it
hides actual complexity, including that it hides actual structure.
Cheers,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
alfps
|
12/18/2005 12:42:13 PM
|
|
Dietmar Kuehl <dietmar_kuehl@yahoo.com> wrote in news:4065l3F17gveeU1
@individual.net:
>
> If you cannot prove that the corrupt thread has not corrupted any
> data unrelated to this thread it may be save to continue execution.
> How do you prove this for an unknown programming error detected at
> run-time? In C++ I don't think you can prove this at all. Instead,
> you have to assume that any byte, including e.g. thread local storage
> of unrelated threads, is corrupted.
How do you prove that a third-party library has not corrupted any byte of
the process memory, if it appears to work well and returns correct results?
You just can't, and thus there is no point in splitting the hair in the
case where the third-party library appears to fail.
For example, I'm pretty sure that in my program there are still several
bugs left. Should I write abort() as the first line in main()?
Paavo
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Paavo
|
12/19/2005 10:26:27 AM
|
|
"Falk Tannh�user" <clcppm-poster@this.is.invalid> wrote in message
news:43a09776$0$12518$626a14ce@news.free.fr...
> Walter Bright wrote:
>> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
>>>Dnia 7 Dec 2005 18:25:35 -0500, Walter Bright skrobie:
>>>>4) inconsistent and counterintuitive name lookup rules for dependent and
>>>>non-dependent names
>>>Better idea?
>> Sure. Have them work like functions do. The function arguments are looked
>> up
>> in the scope of the point of calling, the function body names are looked
>> up
>> in the point of the function definition. This is how D templates work,
>> and
>> it works intuitively to the point that the issue *has never even come
>> up*.
>> It works like one would expect it to, it requires no complicated
>> explanation, and it never occurs to anyone it should be any other way.
>> There
>> aren't any crazy kludges like local names cannot have the same name as a
>> template parameter, or that template base class members aren't found.
>
> How would this work in the presence of partial specialization, when it
> is not clear until instantiation what the members of the base class are?
> I.e. what would be displayed by the D equivalent of the following:
>
> #include <iostream>
> #include <ostream>
>
> void foo() { std::cout << "Global foo\n"; }
> void bar() { std::cout << "Global bar\n"; }
>
> template<int N> struct toto
> { void foo() { std::cout << "toto<" << N << ">::foo\n"; } };
>
> template<int N> struct titi : public toto<6*N>
> { void hello() { foo(); bar(); } };
>
> template<> struct toto<42>
> { void bar() { std::cout << "toto<42>::bar\n"; } };
>
> int main()
> {
> titi< 7> t007; t007.hello();
> titi<111> t111; t111.hello();
> return 0;
> }
Global foo
toto<42>::bar
toto<666>::foo
Global bar
i.e. what one would intutively expect to happen.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/19/2005 10:59:36 AM
|
|
"kanze" <kanze@gabi-soft.fr> wrote in message
news:1134570423.082009.66360@g47g2000cwa.googlegroups.com...
> Walter Bright wrote:
>> "Sektor van Skijlen" <ethouris@pl.wp.spamu.lubie.nie.invalid> wrote in
>> > What else is a problem?
>
>> printf("%s\n", "hello");
>> printf("%s\n", s); // s is an std::string
>
> Just a nit, but you can write:
>
> std::cout << boost::format( "%s\n" ) % "hello" ;
> std::cout << boost::format( "%s\n" ) % s ;
>
> I won't disagree with your basic contention, but citing printf
> for proof doesn't back up your argument any.
The point is, they are different types, and this shows up when using
variadic argument lists. Are you content to deprecate variadic argument
lists in C++? I'm not.
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/19/2005 11:00:33 AM
|
|
"Gerhard Menzl" <gerhard.menzl@hotmail.com> wrote in message
news:439ff872$1@news.kapsch.co.at...
> Walter Bright wrote:
>
>>>I disagree. All that a non-virtual destructor implies ist that one is
>>>not meant to delete objects through the base class.
>>
>> The problem is, there is no way to tell by reading the code if the
>> programmer intended it that way or if it was a mistake. It *is* a
>> common programming mistake, enough so that it is often mentioned in
>> C++ programming guidelines.
>
> And there is a guideline which, if adhered to, doesn't leave any doubt
> about the intention of the author of the base class: if deletion through
> the base class is intended, make the destructor public and virtual,
> otherwise make it protected and non-virtual.
The key phrase here is "a guideline which, if adhered to". The trouble is
that, when you're doing a code review, or looking for a bug, you cannot
assume guidelines are adhered to. Wouldn't it be better to be able to rely
on what the *code* says?
-Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/19/2005 11:00:54 AM
|
|
Timo Geusch wrote:
> If look into the separation of object scoping and memory release there
> may well a way to create a GC paradigm that takes care of the memory,
> but "The Language" enforcing the expected, deterministic object
> lifetime.
>
> To put it into a C++ context, the system would ensure that the objects
> destructor would be called at a deterministic point in the object's
> lifetime but the memory occupied would be shunted off to a GC queue
> that would eventually be cleared down at a time more convenient to the
> system.
Isn't this is exactly what C++/CLI does?
--
Gerhard Menzl
#dogma int main ()
Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
12/19/2005 11:01:15 AM
|
|
"Mirek Fidler" <cxl@volny.cz> wrote in message
news:40gch3F1acug8U1@individual.net...
> Anyway, speaking GC, I do not like it as it is inherently incompatible
> with destructors, means GC can take care of your memory resources, but
> that is all. It will not close your windows in GUI or release your lock.
> Therefore I prefer deterministic and automatic resource management...
GC is generally better at managing plentiful resources, like memory. It is
not so good at managing tight resources, such as windows and file handles.
It makes sense that a well designed language should offer access to both
techniques.
Walter Bright
www.digitalmars.com C, C++, D programming language compilers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Walter
|
12/19/2005 11:01:37 AM
|
|
Walter Bright wrote:
> "Mirek Fidler" <cxl@volny.cz> wrote in message
> news:40gch3F1acug8U1@individual.net...
>
>>Anyway, speaking GC, I do not like it as it is inherently incompatible
>>with destructors, means GC can take care of your memory resources, but
>>that is all. It will not close your windows in GUI or release your lock.
>>Therefore I prefer deterministic and automatic resource management...
>
>
> GC is generally better at managing plentiful resources, like memory. It is
> not so good at managing tight resources, such as windows and file handles.
> It makes sense that a well designed language should offer access to both
> techniques.
My undestanding of the problem is that while combination of both
techniques is generally possible, it will in any case weaken
deterministic nature of destructors - in the best of all combination
strategies, destructors are sometimes called by the compiler at the end
of the scope and sometimes by GC. I am afraid that it will inevitably
result in hard to catch bugs and leaks.
Consider just simple composition case
struct Foo {
File x;
};
now if your code is supposed to be correct and Foo is allocated on GC
collected heap, you should have to manually terminate Foo (delete or
close or whatever). However, if GC is supposed to call destructor for
undestructed object (it should) and it is some of new generation
generational GC, forgetting to terminate Foo will not result in buggy
behaviour most of time - and "works most of time" is exactly the kind of
bugs I am most afraid of....
Mirek
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mirek
|
12/19/2005 2:46:10 PM
|
|
Simon Farnsworth wrote:
> Keith H Duggar wrote:
>
> > kanze wrote:
> >> Keith H Duggar wrote:
> >> > Finally, regardless of language, do developers really believe
> >> > that unpredictable garbage collection delays are acceptable
> >> > and that consumers will put up with them in the long run?
> > >
> >> Regardless of language, does anyone really think today that
> >> garbage collection results in unpredictable delays? I use any
> >> number of programs which use garbage collection, written in a
> >> variety of languages, and while this might have been a problem
> >> fifteen years ago, garbage collection technology has evolved
> >> since then.
> >
> > Ok I didn't realize the concern was so dated. In many of the
> > recent garbage collection threads in this and other
> > newsgroups, collection delays at inconvenient times has been
> > raised as one con of automatic garbage collection. Are you
> > saying this is no longer a practical concern?
> >
> Given the existence of good, thread-safe, interruptible, generational
> garbage collectors, it shouldn't be an issue. You should be able to
> set things up such that garbage collection only runs when the system
> is idling (waiting for user input, for example), and only runs for
> short enough periods of time to not cause disruption.
While I'd agree that this would probably cover most of the requirements
during the lifetime of a program, it does omit the one case where the
garbage collector *has* to run, namely when the available heap has been
exhausted.
Some of the code I work on - which in non-GC'd, but would definitely
benefit from GC or in fact, *any* proper memory management - has the
nasty habit of allocating lots and lots of small chunks of memory due
to the fact that it was originally written back in the time when
containers of pointers were the way to go. As a result of that the
working set size tends to shoot up rapidly in certain programs. If
these were converted to use GC instead of basic new/delete memory
management, I would expect that every time these container allocations
happen, the GC would have to free up enough memory to satisfy the
requests. As the request for the object creation is synchronous, the GC
would have to halt the rest of the program while the collector is
running.
While it may be argued that this is a bit of a pathological case, IME
it is not that uncommon to come across behaviour like this. And
unfortunately it seems to be mostly in those pieces of software that
would really benefit from improved memory management.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Timo
|
12/19/2005 3:55:39 PM
|
|
Gerhard Menzl wrote:
> Timo Geusch wrote:
>
> > If look into the separation of object scoping and memory release
> > there may well a way to create a GC paradigm that takes care of the
> > memory, but "The Language" enforcing the expected, deterministic
> > object lifetime.
> >
> > To put it into a C++ context, the system would ensure that the
> > objects destructor would be called at a deterministic point in the
> > object's lifetime but the memory occupied would be shunted off to a
> > GC queue that would eventually be cleared down at a time more
> > convenient to the system.
>
> Isn't this is exactly what C++/CLI does?
Well, it would make sense to me but TBH I don't know. Maybe someone
like Herb Sutter would be in a position to answer this question...
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Timo
|
12/19/2005 3:56:01 PM
|
|
Mirek Fidler wrote:
> combination of both techniques is generally possible
....
> However, if GC is supposed to call destructor for
> undestructed object (it should)
Oh, this old argument again!
For the record, I believe that it's incorrect for a GC
to call destructors on the objects it reclaims. In my
opinion, a GC allows memory assigned to objects to be
reused if it determines that those objects will never
be used again by the program. This means that it is
reclaiming memory, not destructing objects. As far as
the program is concerned, those objects continue to
exist forever, and therefore their destructors are never
called.
As I've also said many times before, I believe that if
you are really determined to take some action when the
GC reclaims the memory of an object, then you should
register a separate callback procedure that the GC will
invoke. I further believe that this callback should not
have access to the object being reclaimed. If it needs
some resource ID to free, then that should be supplied
separately at registration time and passed at invocation
time.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Hyman
|
12/19/2005 3:59:54 PM
|
|
Hyman Rosen wrote:
> Mirek Fidler wrote:
>
>>combination of both techniques is generally possible
>
> ...
>
>>However, if GC is supposed to call destructor for
>>undestructed object (it should)
>
>
> Oh, this old argument again!
>
> For the record, I believe that it's incorrect for a GC
> to call destructors on the objects it reclaims.
Yes, that is another GC/destructor combination approach. IMHO inferior,
but then again, I am the one thinking that destructors are inherently
incompatible with GC :) I was just answering to other post that I
believe suggested just this...
> In my
> opinion, a GC allows memory assigned to objects to be
> reused if it determines that those objects will never
> be used again by the program. This means that it is
> reclaiming memory, not destructing objects. As far as
> the program is concerned, those objects continue to
> exist forever, and therefore their destructors are never
> called.
>
> As I've also said many times before, I believe that if
> you are really determined to take some action when the
> GC reclaims the memory of an object, then you should
> register a separate callback procedure that the GC will
> invoke. I further believe that this callback should not
> have access to the object being reclaimed. If it needs
> some resource ID to free, then that should be supplied
> separately at registration time and passed at invocation
> time.
That is actually OK and this behaviour is more or less consistent to
Java/C#. Anyway, using this approach would (actually, does in C++/CLI :)
result in breaking C++ classes into two incompatible kinds - GC
collected and classic C++. Scary to me.
Mirek
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mirek
|
12/19/2005 5:25:41 PM
|
|
Walter Bright wrote:
> "Gerhard Menzl" <gerhard.menzl@hotmail.com> wrote in message
> news:439ff872$1@news.kapsch.co.at...
> > Walter Bright wrote:
> >
> >>>I disagree. All that a non-virtual destructor implies ist that one is
> >>>not meant to delete objects through the base class.
> >>
> >> The problem is, there is no way to tell by reading the code if the
> >> programmer intended it that way or if it was a mistake. It *is* a
> >> common programming mistake, enough so that it is often mentioned in
> >> C++ programming guidelines.
> >
> > And there is a guideline which, if adhered to, doesn't leave any doubt
> > about the intention of the author of the base class: if deletion through
> > the base class is intended, make the destructor public and virtual,
> > otherwise make it protected and non-virtual.
>
> The key phrase here is "a guideline which, if adhered to". The trouble is
> that, when you're doing a code review, or looking for a bug, you cannot
> assume guidelines are adhered to. Wouldn't it be better to be able to rely
> on what the *code* says?
I dunno, this sounds like a tools issue to me. It's trivial for a code
browser to know if any given member function is virtual or not,
regardless of what the class declaration says. (If I recall correctly,
the CodeWarrior code browser does it.)
I agree with Gerhard: all a virtual destructor means is that it's OK to
delete a derived class object using a pointer to base. It says nothing
about whether inheritance is allowed. Many people interpret it that
way, but that doesn't make them right.
Bob
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bob
|
12/20/2005 12:31:31 AM
|
|
Simon Farnsworth wrote:
> Secondly, the
> java.lang.System.gc() call provides you with a way to hint to the JVM that
> now would be a good time to garbage collect, and hopefully reduce the
> chances of a long collection run while the user is waiting.
This almost always turns out to be a bad idea, right? An application
cannot reliably know when it would be a good time to muck around with
the GC heap when it does not have any knowledge of all the other
applications running in the same PC. Is that why you used the word
'hint'? to indicate that even if you made such a call, there is no
guarantee the GC will run?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Dilip
|
12/20/2005 12:31:53 AM
|
|
Mirek Fidler wrote:
> That is actually OK
I'm not sure from context to what "that" is referring.
> two incompatible kinds - GC collected and classic C++.
I don't see why this is the case. There aren't two kinds
of classes, there are two kinds of situations. In one,
the program knows when it is done with something, and it
cleans that thing up at that time. In the other, the logic
of the program is such that no isolated piece of code knows
when the entire program is done using a resource, and then
a global overview, the GC, is required to determine when
this is the case.
In the GC case, trying to destruct an object can lead to
an access which contradicts the required inaccessibility,
so the GC must not destruct objects. Instead, if an object
contains resources which it would be convenient to free if
the object became inaccessible, those resources should be
registered separately with the collector, and then they can
be freed when the collector reclaims the memory of the object
in which they're embedded.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| | | |