|
|
We do not use C++ exceptions
I found the arguments in the Google Style Guide quite interesting:
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions
quote Con(3): "Exception safety requires both RAII and different coding
practices. Lots of supporting machinery is needed to make writing
correct exception-safe code easy. Further, to avoid requiring readers to
understand the entire call graph, exception-safe code must isolate logic
that writes to persistent state into a "commit" phase. This will have
both benefits and costs (...)"
- Of course. When people are not using exceptions, they are usually just
ignoring the error return codes, so no need for a no-fail commit phase.
::-)
quote Decisinon: "Given that Google's existing code is not
exception-tolerant, the costs of using exceptions are somewhat greater
(...) We don't believe that the available alternatives to exceptions,
such as error codes and assertions, introduce a significant burden."
"(...) Because we'd like to use our open-source projects at Google and
it's difficult to do so if those projects use exceptions, we need to
advise against exceptions in Google open-source projects as well."
- I really don't know. I'm all for not introducing exceptions into
existing code bases that are not exception safe. But recommending
against the use of exceptions for new (sub)projects and modules because
the existing code base does not use them seems a bit overreacting.
I just hope this doesn't spread.
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/9/2009 5:21:33 PM |
|
On 9 Jan, 23:21, "Martin T." <0xCDCDC...@gmx.at> wrote:
> I found the arguments in the Google Style Guide quite interesting:
>
> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
[snip]
> - I really don't know. I'm all for not introducing exceptions into
> existing code bases that are not exception safe. But recommending
> against the use of exceptions for new (sub)projects and modules because
> the existing code base does not use them seems a bit overreacting.
I agree, but consider that users of such a sub-project, for example a
library, will have to deal with exceptions. They means that the code
that uses the sub-project will need to be exception-aware/safe, use
RAII etc. Thus exception related code will spread through the
project.
This reminds me of the fuss people used to make about the use of
const, which has a similar viral effect on code and coding practices.
But use of const won out in the end - I expect the use of exceptions
will do the same.
I thought the remainder of the Google coding standards were clear,
concise and in general pretty good.
Neil Butterworth
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
nbutterworth1953
|
1/10/2009 6:21:51 PM
|
|
I've been thinking several times of using exceptions in new projects
(done in C++). But ultimately I decided against because when you see a
function declaration, you cannot see which exceptions might be thrown
by that function. You have to read and re-read almost every time you
want to use that function to be sure to catch the right exceptions.
Also, IIRC but please correct me, if I'm wrong, under Windows throwing
exceptions is problematic once it has to cross DLL borders (ie. when
you've implemented an interface within the DLL and a function within
the implementation has to throw an exception).
That's the point I like in Java: You declare the exceptions the
function might throw in the function declaration.
Have fun,
Metron
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Metron
|
1/10/2009 6:22:23 PM
|
|
"Martin T." <0xCDCDCDCD@gmx.at> :
>I found the arguments in the Google Style Guide quite interesting:
>
> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions
I read through the guide, it has a couple items that is completely nuts:
- "Reference Arguments: All parameters passed by reference must be labeled
const. ...
In fact it is a very strong convention that input arguments are values or
const references while output arguments are pointers. ..."
I'm using C++ since about CFront 2.0, first time to hear this idea, and it
is IMNSHO utter nonsense.
- Default Arguments: We do not allow default function parameters.
DOH.
- Doing Work in Constructors: Do only trivial initialization in a
constructor. If at all possible, use an Init() method for non-trivial
initialization. ... If your object requires non-trivial initialization,
consider having an explicit Init() method and/or adding a member flag that
indicates whether the object was successfully initialized.
I recon most mentors say to do 2 phase init only when absolutely necessary
for data flow or use cases (i.e. you need instances before knowing params).
Make it based on "amount of work"?
- (here comes your observed) Exceptions: We do not use C++ exceptions.
> quote Con(3): "Exception safety requires both RAII and different coding
> practices. ...
> - Of course. When people are not using exceptions, they are usually just
> ignoring the error return codes, so no need for a no-fail commit phase.
> ::-)
Yea++. RAII and SEME-tolerant layout serves better readability and
correctness. Wrt anything, including exceptions.
> quote Decisinon: "Given that Google's existing code is not
> exception-tolerant, the costs of using exceptions are somewhat greater
Anyone has familiarity with Google code? Is it correct and readable? This
statement rings me a "stay away" warning.
> (...) We don't believe that the available alternatives to exceptions,
> such as error codes and assertions, introduce a significant burden."
Assertion is NOT an alternative to exception (or error code).
Having no exception takes us back by 2 decades (almost) to fully-local error
handling and inability to separate concepts. I recall rewriting some old
code to use exception-triggering classes (mostly handling file i/o and
database ops), it shrinked to 20% and became totally readable and obvious at
glance.
> "(...) Because we'd like to use our open-source projects at Google and
> it's difficult to do so if those projects use exceptions, we need to
> advise against exceptions in Google open-source projects as well."
>
> - I really don't know. I'm all for not introducing exceptions into
> existing code bases that are not exception safe. But recommending
> against the use of exceptions for new (sub)projects and modules because
> the existing code base does not use them seems a bit overreacting.
Taking the earlier statement it makes sense -- for Googlers -- if they can't
see so better make darkness the standard.
:-((
- Run-Time Type Information (RTTI): We do not use Run Time Type Information
(RTTI) ... "A query of type during run-time typically means a design
problem."
Hard to decide whether we should laugh or cry. Dealing with polymorphism
has a toolset, dynamic_cast is one good element of it. How about teaching
the ways to pick tools and use REVIEWs to address allegedly "typical
misuse".
- Preincrement and Predecrement: Use prefix form (++i) of the increment and
decrement operators with iterators and other template objects.
The typical guideline states like "use postincrement ONLY when you need the
previous value". Not depending on types involved, or the phase of the moon.
(corollary: "say what you mean, mean what you say".)
- Use of const: ...
the guideline is correct, but the explanation text has fishy elements.
- Variable and Array Initialization: Your choice of = or (). ... the
following are all correct: ... string name("Some Name"); string name = "Some
Name";
Who cares difference of direct init and copy-init after all...
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/10/2009 6:25:13 PM
|
|
On Jan 9, 6:21 pm, "Martin T." <0xCDCDC...@gmx.at> wrote:
> I found the arguments in the Google Style Guide quite interesting:
>
> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
I agree to a point. For example: (pseudo code)
f(x):
if x return 1/x
else throw
Throwing when x is zero is really good here because the user would
have no good way of knowing the return value is bad (if this code was
allowed to continue). However, generally, exceptions are not good at
all. Having an unhandled exception in one module affects every other
module. If I have functions that throw in my trivial module part of a
greater system, and the exception is not caught, even the important
modules kindly stop as the program recognizes the exception.
On the other hand, if the istream threw a little more often, than I
wouldn't see, about once a week, a newbie having it explained to him/
her about how you have to clear the flag, ignore the buffer, and start
again. But, this is actually a problem in interface. If it were easier
to recognize such problems and start again, we might not see beginners
get this wrong so much and the textbooks might be able to explain it
better.
So: I don't think throwing is generally good, and its necessity is due
to bad interface, but when there is no possible other way to inform
the user of a problem, it's good.
On a note about that, though: Some libraries, like OpenGL, instead of
throwing create lists of problems stored in strings. It doesn't crash
my program and it tells me what the problem is so I can just rewrite
the bad code. Not a bad work-a-round!
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Hakusa
|
1/10/2009 6:28:20 PM
|
|
On 10 jan, 00:21, "Martin T." <0xCDCDC...@gmx.at> wrote:
> - I really don't know. I'm all for not introducing exceptions into
> existing code bases that are not exception safe. But recommending
> against the use of exceptions for new (sub)projects and modules because
> the existing code base does not use them seems a bit overreacting.
This is probably just justification for the fact that they do not want
to learn how to design exception-safe code with RAII.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/10/2009 6:29:56 PM
|
|
On 10 Jan., 00:21, "Martin T." <0xCDCDC...@gmx.at> wrote:
> I found the arguments in the Google Style Guide quite interesting:
> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
They're not saying exceptions are inherently bad. They are saying that
they think it is a bad idea to add exceptions to an existing project
that has been designed without exceptions in mind. But as far as I can
tell they do acknowledge the benefits of exceptions in case you can
start a new project from scratch.
Cheers!
SG
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
SG
|
1/10/2009 6:30:32 PM
|
|
{ It's about the Google Style Guide at
<http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions>.
Please include some quoting yourself to establish context. -mod }
I think the situations is a bit like the one when you have const
methods, but objects that do not adhere to const correctness. You will
not be able to call the non-const methods of those, and need const-
cast. That's additional code, but it's not near as painful as adding
RAII object everywhere to ensure cleanup functions of objects are
called that do not adhere to the RAII/SBRM principle (deallocate in
destructor). And remember you need to do that even tho you only call
throwing code indirectly, possibly not noticing you call possibly
throwing code in the end. With C++1x i think the whole situation will
be somewhat better, because you can do something like
{
ScopeGuard s = [&] { queue.CleanUp(); }
...
}
But now, i think i can understand google's decision. They don't want
to create SBRM objects for each and every situation that need such a
special handling.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
litb
|
1/10/2009 6:33:20 PM
|
|
On 11 Jan, 00:25, "Balog Pal" <p...@lib.hu> wrote:
Some comments on yours:
> - "Reference Arguments: All parameters passed by reference must be
> labeled
> const. ...
> In fact it is a very strong convention that input arguments are values
> or
> const references while output arguments are pointers. ..."
>
> I'm using C++ since about CFront 2.0, first time to hear this idea,
> and it
I can assure you that there were a lot of guidelines sloshing around
back in CFront days that said you should use references to pass
information and pointers to pass ownership.
> is IMNSHO utter nonsense.
Nonsense maybe, but not _utter_ nonsense :-)
> - Default Arguments: We do not allow default function parameters.
>
> DOH.
Actually, I find myself using defaults less and less as time goes on.
It seems to be often the case that there is subtle semantic difference
between a method that takes one or two (one defaulted) parameters. I
agree a blanket ban is silly.
> - Doing Work in Constructors: Do only trivial initialization in a
> constructor. If at all possible, use an Init() method for non-trivial
> initialization. ... If your object requires non-trivial
> initialization,
I thought they meant factoring out common constructor code into a
private Init() method...
> consider having an explicit Init() method and/or adding a member flag
> that
> indicates whether the object was successfully initialized.
... but I guess I was wrong. I didn't read this, which is obviously
completely wrong-headed.
> - Run-Time Type Information (RTTI): We do not use Run Time Type
> Information
> (RTTI) ... "A query of type during run-time typically means a design
> problem."
>
> Hard to decide whether we should laugh or cry. Dealing with
> polymorphism
> has a toolset, dynamic_cast is one good element of it. How about
> teaching
> the ways to pick tools and use REVIEWs to address allegedly "typical
> misuse".
This was the main thing in GC guidelines I disagreed with, but to be
fair, I've only started using RTTI in the past 5 years or so myself. I
guess it's too much to expect a company full of Python programmers to
embrace it any quicker!
Neil Butterworth
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
nbutterworth1953
|
1/11/2009 11:52:02 AM
|
|
"Metron" <MetronSM@gmail.com> az al�bbiakat �rta a k�vetkezo
h�r�zenetben:
bd98f839-dde7-44a3-89d3-134e3f325877@r15g2000prh.googlegroups.com...
> I've been thinking several times of using exceptions in new projects
> (done in C++). But ultimately I decided against because when you see a
> function declaration, you cannot see which exceptions might be thrown
> by that function. You have to read and re-read almost every time you
> want to use that function to be sure to catch the right exceptions.
This sounds like missing the point. I rarely check exceptions
per-function:
normally the *component* defines an exception policy and a set of its
own
exceptions.
And most of the client code shall not bother -- the baseline is to make
functions exception-transparent. You catch only at special places,
where
you need to handle or convert.
Integrating an exceotion-using component also just needs a wrapper, that
converts the exceptions to retcodes if really that is what yo want.
Refusing exceptions in C++ means you refrain using std::, boost::, many
other libs following or using them, and use only nothrow new? Sounds
like a
huge cut.
> Also, IIRC but please correct me, if I'm wrong, under Windows throwing
> exceptions is problematic once it has to cross DLL borders (ie. when
> you've implemented an interface within the DLL and a function within
> the implementation has to throw an exception).
Can't recall any problem there. MFC sits in a DLL, throws a dozen
different
exceptions my code gladly caught...
> That's the point I like in Java: You declare the exceptions the
> function might throw in the function declaration.
Opinion is mixed on that, I recall articles from mentors (including
Bruce
Eckel) to switch to unchecked exceptions in some cases. Observing ill
practices, like catch {} to make code compile.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/11/2009 11:57:17 AM
|
|
Metron wrote:
> I've been thinking several times of using exceptions in new projects
> (done in C++). But ultimately I decided against because when you see a
> function declaration, you cannot see which exceptions might be thrown
> by that function.
So you neither use containers from the standard library or 'new'
without 'nothrow'? Further, you don't use an overloaded 'operator+' to
concatenate strings? Or, FWIW, even constructors for objects that
require
any operations there that might fail?
Any container requires allocation and will throw when that fails, just
as 'new' does. 'operator+' requires allocation, too, but its returnvalue
is
that object already so there is no way to signal that this allocation
failed, safe using a string that can not only contain various amounts of
textual data but also have a 'broken' state. Similarly, passing a string
literal to a function that takes a string type requires an allocation.
Reading a file into memory requires memory and that opening the file
works.
> You have to read and re-read almost every time you
> want to use that function to be sure to catch the right exceptions.
Sorry, but that is the wrong way to think about using exceptions. The
point
is that you usually don't catch them at the point where they escape from
a
function, because usually you can't handle them there anyway. Rather,
you
bundle several operations in a single try-catch clause. Of course, if
you
need cleanup in the middle, you might also catch, cleanup and rethrow
or,
better, use a scope guard.
> Also, IIRC but please correct me, if I'm wrong, under Windows throwing
> exceptions is problematic once it has to cross DLL borders (ie. when
> you've implemented an interface within the DLL and a function within
> the implementation has to throw an exception).
Not that I was aware of.
Uli
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Ulrich
|
1/11/2009 12:02:18 PM
|
|
Mathias Gaunard wrote:
> On 10 jan, 00:21, "Martin T." <0xCDCDC...@gmx.at> wrote:
>
>> - I really don't know. I'm all for not introducing exceptions into
>> existing code bases that are not exception safe. But recommending
>> against the use of exceptions for new (sub)projects and modules
>> because
>> the existing code base does not use them seems a bit overreacting.
>
> This is probably just justification for the fact that they do not want
> to learn how to design exception-safe code with RAII.
I think it's unfair to blame them "that they do not want to learn".
You see, I do not agree with their arguments against exceptions, but
teaching new staff the right use of exception is a non-trivial affair.
At our shop my sup. is against the use of exceptions for the very reason
that he thinks our devs can't use them properly (esp. with regd to
RAII). As things are I think he's mostly right. Many devs come out of
uni/college having done some basic C/C++ (read: "C with classes") and
some Java programming and then continue to program "C with classes".
The fear is also, that even if a dev has understood the concepts to
write exception-correct code in C++ he still can break existing code
because that legacy code was not exception save.
I would have thought though that Google at least should have the will,
time and resources to bring their developers to a decent language level
.... either that guide is not for their employees or they just stumble
along as everyone else :-)
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/11/2009 12:07:39 PM
|
|
Hakusa@gmail.com wrote:
> On Jan 9, 6:21 pm, "Martin T." <0xCDCDC...@gmx.at> wrote:
>> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
>
> I agree to a point. For example: (pseudo code)
>
> f(x):
> if x return 1/x
> else throw
>
> Throwing when x is zero is really good here because the user would
> have no good way of knowing the return value is bad (if this code was
> allowed to continue).
Kind of bad example, because:
int main(int, char*[]) {
using namespace std;
double x = 0.0;
double inf = 1/x;
cout << inf << endl; // prints: 1.#INF
return 0;
}
( but maybe this is impl. specific, I'm never too sure with that FP stuff)
> However, generally, exceptions are not good at
> all. Having an unhandled exception in one module affects every other
> module. If I have functions that throw in my trivial module part of a
> greater system, and the exception is not caught, even the important
> modules kindly stop as the program recognizes the exception.
>
Well, this is certainly *not* an argument against exceptions. Because
C++ has quite a few other situations that will result in std::terminate
(or whatever). It's just a buggy module if it generates an unhandled
exceptions, just as it's a bugy module if it does a nullptr access ...
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/11/2009 1:34:21 PM
|
|
Balog Pal wrote:
>
> "Martin T." <0xCDCDCDCD@gmx.at> :
>>I found the arguments in the Google Style Guide quite interesting:
>>
>>
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions
>
> I read through the guide, it has a couple items that is completely
> nuts:
>
> - "Reference Arguments: All parameters passed by reference must be
> labeled const. ...
> In fact it is a very strong convention that input arguments are values
> or const references while output arguments are pointers. ..."
>
> I'm using C++ since about CFront 2.0, first time to hear this idea,
> and it is IMNSHO utter nonsense.
For me, it is not the first time I hear it (though I don't adhere to
it).
The idea is that, at the call-site you can more easily identify the
arguments that will/should be changed by the function without having to
lookup the prototype. This is because, like in C, the out-parameters
are identified by the use of the operator& at the call site.
For me, the argument is not strong enough to include the rule in any
style guide I am responsible for, but I will follow it if someone else
insist on it.
<snip>
> - Doing Work in Constructors: Do only trivial initialization in a
> constructor. If at all possible, use an Init() method for non-trivial
> initialization. ... If your object requires non-trivial
> initialization, consider having an explicit Init() method and/or
> adding a member flag that indicates whether the object was
> successfully initialized.
>
> I recon most mentors say to do 2 phase init only when absolutely
> necessary for data flow or use cases (i.e. you need instances before
> knowing params). Make it based on "amount of work"?
I see this as a consequence of not allowing exceptions.
If you can't use exceptions to report errors from constructors, you
simply can't avoid two-phase construction if the object requires a
fallible operation on construction. And any non-trivial initialisation
can possibly fail.
<snip>
> - Run-Time Type Information (RTTI): We do not use Run Time Type
> Information (RTTI) ... "A query of type during run-time typically
> means a design problem."
>
> Hard to decide whether we should laugh or cry. Dealing with
> polymorphism
> has a toolset, dynamic_cast is one good element of it. How about
> teaching the ways to pick tools and use REVIEWs to address allegedly
> "typical misuse".
I am not sure what you mean with this comment, but that might be due to
a different interpretation of the phrase "use RTTI".
To me, that phrase only means the explicit use of type_info objects (for
other purposes than printing a typename as a diagnostic tool). But I am
aware that some people also include any use of dynamic_cast in their
meaning of the phrase "use RTTI".
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bart
|
1/11/2009 1:34:24 PM
|
|
Metron wrote:
> I've been thinking several times of using exceptions in new projects
> (done in C++). But ultimately I decided against because when you
> see a function declaration, you cannot see which exceptions might
> be thrown by that function. You have to read and re-read almost
> every time you want to use that function to be sure to catch the
> right exceptions.
Unlike when it returns error codes? :-)
How do you know what codes to check for?
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
|
1/11/2009 1:35:39 PM
|
|
Hakusa@gmail.com wrote:
> On Jan 9, 6:21 pm, "Martin T." <0xCDCDC...@gmx.at> wrote:
>> I found the arguments in the Google Style Guide quite interesting:
>>
>> http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
>
> I agree to a point. For example: (pseudo code)
>
> f(x):
> if x return 1/x
> else throw
>
> Throwing when x is zero is really good here because the user would
> have no good way of knowing the return value is bad (if this code
> was allowed to continue). However, generally, exceptions are not
> good at all. Having an unhandled exception in one module affects
> every other module. If I have functions that throw in my trivial
> module part of a greater system, and the exception is not caught,
> even the important modules kindly stop as the program recognizes
> the exception.
Perhaps they should, rather than trying to increase the power output
to 1/x, for a bad x.
Otherwise, catching the exeption at the module boundary might be a
good idea. Maybe it can be handled there, or translated into something
that the higher lever modules can handle.
>
> On the other hand, if the istream threw a little more often, than I
> wouldn't see, about once a week, a newbie having it explained to
> him/ her about how you have to clear the flag, ignore the buffer,
> and start again. But, this is actually a problem in interface. If
> it were easier to recognize such problems and start again, we might
> not see beginners get this wrong so much and the textbooks might be
> able to explain it better.
>
> So: I don't think throwing is generally good, and its necessity is
> due to bad interface, but when there is no possible other way to
> inform the user of a problem, it's good.
The user isn't supposed to be the recipient of the exception, but
higher level code. The code might actually know what to do about the
problem. Informing the user is one option, in case there is a user
present.
>
> On a note about that, though: Some libraries, like OpenGL, instead
> of throwing create lists of problems stored in strings. It doesn't
> crash my program and it tells me what the problem is so I can just
> rewrite the bad code. Not a bad work-a-round!
OpenGL is a special case, where bad output is sometimes acceptable.
The image looks a bit strange, but the application can often continue
anyway. The next couple of frame updates might hide the problem.
This is not generally the case.
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
|
1/11/2009 1:35:41 PM
|
|
On 11 jan, 01:25, "Balog Pal" <p...@lib.hu> wrote:
> - "Reference Arguments: All parameters passed by reference must be labeled
> const. ...
> In fact it is a very strong convention that input arguments are values or
> const references while output arguments are pointers. ..."
>
> I'm using C++ since about CFront 2.0, first time to hear this idea, and it
> is IMNSHO utter nonsense.
It's discutable, but not utter nonsense.
Personally I avoid usually arguments as output altogether, except for
output iterators.
> - Default Arguments: We do not allow default function parameters.
>
> DOH.
Such functionality can be attained with overloading and usually leads
to more efficient code.
Also, if your default constructors can't work properly, you don't have
much of a choice.
> - Doing Work in Constructors: Do only trivial initialization in a
> constructor. If at all possible, use an Init() method for non-trivial
> initialization. ... If your object requires non-trivial initialization,
> consider having an explicit Init() method and/or adding a member flag that
> indicates whether the object was successfully initialized.
>
> I recon most mentors say to do 2 phase init only when absolutely necessary
> for data flow or use cases (i.e. you need instances before knowing params).
> Make it based on "amount of work"?
No exception implies two-phase initialization, which implies previous
point.
> Anyone has familiarity with Google code? Is it correct and readable? This
> statement rings me a "stay away" warning.
Yes, me too.
>
> > (...) We don't believe that the available alternatives to exceptions,
> > such as error codes and assertions, introduce a significant burden."
>
> Assertion is NOT an alternative to exception (or error code).
Unfortunately, few people use assertions and exceptions in the right
places.
Assertions are programming errors which should *never* happen and are
bugs. That is why it is good to disable them once the software has
been sufficiently tested.
Exceptions are recoverable errors which shouldn't happen in the normal
execution flow, but may well happen.
The common mistake, however, is usually the opposite: using exceptions
for things that really ought to be assertions.
> - Run-Time Type Information (RTTI): We do not use Run Time Type Information
> (RTTI) ... "A query of type during run-time typically means a design
> problem."
>
> Hard to decide whether we should laugh or cry. Dealing with polymorphism
> has a toolset, dynamic_cast is one good element of it. How about teaching
> the ways to pick tools and use REVIEWs to address allegedly "typical
> misuse".
Code that downcasts is badly designed, yes.
A chain of dynamic_cast doesn't scale, you should use some kind of
visitor pattern.
> Who cares difference of direct init and copy-init after all...
Some types are not copiable (some are even non movable).
Some types have explicit constructors.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/11/2009 1:37:13 PM
|
|
Metron wrote:
> I've been thinking several times of using exceptions in new projects
> (done in C++). But ultimately I decided against because when you see a
> function declaration, you cannot see which exceptions might be thrown
> by that function. You have to read and re-read almost every time you
> want to use that function to be sure to catch the right exceptions.
And how are you sure that you are handling the right error-codes?
Those are also not part of the function declaration.
And if you say you just read the function documentation, remember that
you should also be able to find in the exact same place which
exceptions a function might throw.
> Have fun,
> Metron
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bart
|
1/11/2009 1:37:16 PM
|
|
On 11 jan, 01:22, Metron <Metro...@gmail.com> wrote:
> I've been thinking several times of using exceptions in new projects
> (done in C++). But ultimately I decided against because when you see a
> function declaration, you cannot see which exceptions might be thrown
> by that function.
Why do you need to know?
You shouldn't need to know what a function may throw unless you want
to do something meaningful in those specific cases.
The only thing you need to know is whether a function is nothrow and
not; and still, most code shouldn't need to know that some specific
action is nothrow.
> You have to read and re-read almost every time you
> want to use that function to be sure to catch the right exceptions.
If you want to catch the exceptions, that's probably because you want
to handle specific errors that may happen during their execution. If
you know what specific errors may happen and want to handle them, that
probably means that you have read the documentation; said documention
which should mention which exception types may be thrown.
What exceptions may be thrown may be documented as comments too, which
gives you the same information as what you'd like.
> Also, IIRC but please correct me, if I'm wrong, under Windows throwing
> exceptions is problematic once it has to cross DLL borders
DLLs and similar mechanisms were usually designed for C.
Of course, you cannot use C++ mechanisms across mechanisms designed
for another language without those mechanisms...
This isn't really a problem though, just catch any exception and
encode it into another format to pass it.
> That's the point I like in Java: You declare the exceptions the
> function might throw in the function declaration.
That's why most people will use RuntimeException to get around that
annoying mechanism.
Checked exceptions are a highly criticized thing.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/11/2009 1:37:18 PM
|
|
<nbutterworth1953@googlemail.com> :
>> "Balog Pal" <p...@lib.hu>
>> - "Reference Arguments: All parameters passed by reference must be
>> labeled const. ...
>> In fact it is a very strong convention that input arguments are values
>> or const references while output arguments are pointers. ..."
>>
>> I'm using C++ since about CFront 2.0, first time to hear this idea,
>> and it
>
> I can assure you that there were a lot of guidelines sloshing around
> back in CFront days that said you should use references to pass
> information and pointers to pass ownership.
Pass ownership: sure, that is done by passing pointers. Also pointers are
used to have NULL available as special/missing/... value.
This point is not about that, but about [out] or [inout] params. For that I
(and I thought everyone) use nonconst reference. Why on earth would I pass
a pointer to a mandatory output param, allowing someone to pass NULL?
It is also self-documenting: param is nonconst-ref: the function will
modify that object. nonconst pointer: either an opotional output param (to
capture auxillary info) or a passing the pointer for ownership
transfer/registering for later use/...
>> is IMNSHO utter nonsense.
> Nonsense maybe, but not _utter_ nonsense :-)
Not convinced yet. :)
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/12/2009 1:27:54 PM
|
|
On Jan 11, 12:02 pm, Ulrich Eckhardt <dooms...@knuut.de> wrote:
First of all, I was appalled that Google prescribes no exceptions and
no non-const references.
> Any container requires allocation and will throw when that fails, just
> as 'new' does. 'operator+' requires allocation, too, but its returnvalue
> is
> that object already so there is no way to signal that this allocation
> failed, safe using a string that can not only contain various amounts of
> textual data but also have a 'broken' state. Similarly, passing a string
> literal to a function that takes a string type requires an allocation.
> Reading a file into memory requires memory and that opening the file
> works.
Yep.
Ironically, from an aesthetic point of view, exceptions and non-const-
references are two of the most liberating features of C++. They allow
a mode of thinking that is simply impossible in their absence.
It should be a goal, in any design, new code or not, to find good
form, and both these features, exceptions in particular, facilitate
finding good form, IMO. I would be curious to see how Google's object-
cannot-construct-itself looks. There is one possible answer if
exceptions are disallowed: ugly, tedious, and one _still_ does not
know what to do in awkward situations.
And the argument they give for avoiding non-const references...
"References can be confusing, as they have value syntax but pointer
semantics. "
....is highly subjective.
To whom is it confusing? I doubt that there are many people in this
newsgroup that would agree with this statement.
It's like saying that foreigners [w.r.t. USA] should not use
subjunctives because most people in USA have trouble understanding
subjuncitves. It is a riduculous proposition that reeks of
intellectual communism.
I would prefer that Google reconsidered. ;)
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/12/2009 1:30:50 PM
|
|
on Sun Jan 11 2009, Martin T <0xCDCDCDCD-AT-gmx.at> wrote:
> Mathias Gaunard wrote:
>> On 10 jan, 00:21, "Martin T." <0xCDCDC...@gmx.at> wrote:
>>
>>> - I really don't know. I'm all for not introducing exceptions into
>>> existing code bases that are not exception safe. But recommending
>>> against the use of exceptions for new (sub)projects and modules because
>>> the existing code base does not use them seems a bit overreacting.
>>
>> This is probably just justification for the fact that they do not want
>> to learn how to design exception-safe code with RAII.
>
> I think it's unfair to blame them "that they do not want to learn".
> You see, I do not agree with their arguments against exceptions, but
> teaching new staff the right use of exception is a non-trivial affair.
> At our shop my sup. is against the use of exceptions for the very reason
> that he thinks our devs can't use them properly (esp. with regd to
> RAII). As things are I think he's mostly right.
Most devs don't handle errors properly no matter what tools you give
them. At least exceptions make it easier to do the job right.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/12/2009 1:32:07 PM
|
|
on Sun Jan 11 2009, nbutterworth1953-AT-googlemail.com wrote:
>> Hard to decide whether we should laugh or cry. Dealing with
>> polymorphism has a toolset, dynamic_cast is one good element of
>> it. How about teaching the ways to pick tools and use REVIEWs to
>> address allegedly "typical misuse".
>
> This was the main thing in GC guidelines I disagreed with, but to be
> fair, I've only started using RTTI in the past 5 years or so myself. I
> guess it's too much to expect a company full of Python programmers to
> embrace it any quicker!
The cultures of Python and C++ are so different that I'm sometimes
amazed that I manage to be both a C++ and a Python programmer at once.
Just take a look at this thread:
http://markmail.org/message/vvhahc6ccgda3nkt
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/12/2009 1:34:08 PM
|
|
Bart van Bingen Schenau wrote:
> Balog Pal wrote:
>>
>> - "Reference Arguments: All parameters passed by reference must be
>> labeled const. ...
>> In fact it is a very strong convention that input arguments are values
>> or const references while output arguments are pointers. ..."
>>
>> I'm using C++ since about CFront 2.0, first time to hear this idea,
>> and it is IMNSHO utter nonsense.
>
> For me, it is not the first time I hear it (though I don't adhere to
> it).
> The idea is that, at the call-site you can more easily identify the
> arguments that will/should be changed by the function without having to
> lookup the prototype. This is because, like in C, the out-parameters
> are identified by the use of the operator& at the call site.
A big flaw with this logic is that, since the C days, many in-parameters
have been also identified by & at the call site, to avoid call-by-value
(ex. localtime), though new functions written in C++ will more likely
use (const) references instead.
On the other hand, absence of & doesn't tell you that the parameter
won't be changed; strcpy(a, b) says nothing about which one will be
changed and which one won't. Upon meeting swap(a, b) or increment(x),
you will instantly recognise that they will all be changed.
In other cases, what you have received from outside may be a pointer p
from the beginning, and if you want to pass it to a function f that
also takes a pointer form, you'll have to use f(p), not f(&p), no matter
whether f changes the object pointed to by the pointer.
The conclusion that I came to is that using & at the call site to
distinguish in-parameters and out-parameters doesn't work well, and
you really cannot dispense with understanding what the function does
with the arguments.
This has been discussed many times in this group, including one in
March 2005.
--
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
|
1/12/2009 1:34:12 PM
|
|
> You shouldn't need to know what a function may throw unless you want
> to do something meaningful in those specific cases.
>
> The only thing you need to know is whether a function is nothrow and
> not; and still, most code shouldn't need to know that some specific
> action is nothrow.
+1. And even knowing whether some specific function is nothrow is bad
for maintenance (that is, as code evolves, it __will__ go from nothrow
to throw ;-) ).
IMO, the baseline rule is: everything throws, except primitive type
assignments and a small set of operations, most restrictive set being
simply one thing: a non-throwing swap operation on a class. That's a
simple-enough base to reason about exception safety.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Goran
|
1/12/2009 1:37:00 PM
|
|
On 12 jan, 20:34, David Abrahams <d...@boostpro.com> wrote:
> The cultures of Python and C++ are so different that I'm sometimes
> amazed that I manage to be both a C++ and a Python programmer at once.
> Just take a look at this thread:http://markmail.org/message/vvhahc6ccgda3nkt
I'll be straying a bit off-topic here, but I hope this can still be
interesting, and why not bring the Python or other dynamic-languages-
loving masses to C++.
You can code Python-style in C++ (and quite more efficiently) without
much hassle. It's just dynamic duck typing, nothing hard to do,
depending on how well you want it to play with the C++ type system.
I will here talk about how to make it integrate with the existing type
system well.
If you only want to use it on a finite set of types, you can just use
a variant; everytime you do something (like call a method) you visit
your variant, then check whether the call is valid at compile-time
(with SFINAE for expressions, for example): if it is, perform the
action, otherwise raise a runtime error.
It would be possible to do such a thing without a constrained list of
types, but that would require a feature virtually equivalent to
template virtual functions (and certainly, that would make C++ so
versatile it would be the dawn of a new age for the language).
Note, however, that it working on a finite set of types is enough to
express anything you would express in Python: it just won't integrate
too easily with different, non-duck using, C++ code.
Here is, for example, a possible variant (the "variable" type) in ML
syntax, quite representative of dynamic languages and how they are
implemented (it's actually more like PHP, since our arrays are maps):
type variable = Object of object | Function of (variable list ->
variable) | Procedure of (variable list -> unit)
| Int of int | Float of float | Array of (variable,
variable) map | Resource of int ... other native types
and
type object = (string, variable) map
Now here is what a generic duck typing system can look like in C+
+(0x).
Unfortunately, without proper operator. (dot) overloading (and by
proper I mean real name overloading, not just forwarding), or at least
polymorphic lambdas, it's just not practical to use.
We can have something that works like this:
duck<T1, T2, ..., Tn> variable = T1();
variable.call(lambda_method(arg1, arg2, ..., argn));
with
struct lambda_method
{
Arg1 arg1; // Argi being the type of argi
Arg2 arg2;
...
Argn argn;
// the constructor you expect
// ...
template<typename T>
auto operator()(T&& o) -> decltype(o.method(arg1, arg2, ...,
argn))
{
return o.method(arg1, arg2, ..., argn);
}
};
we'd really like to just write
variable.call([&](o) { return o.method(arg1, arg2, ..., argn); });
or ideally the shorter
variable.method(arg1, arg2, ..., argn);
Unfortunately, everytime I offered a mechanism to provide the
necessary operator. overloading on comp.std.c++ (which is simply
making the last line equivalent to the previous one), there was lack
of interest.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/12/2009 11:16:35 PM
|
|
"Balog Pal" <pasa@lib.hu> wrote in news:gkcfs1$19kh$1@news.ett.com.ua:
> Integrating an exceotion-using component also just needs a wrapper, that
> converts the exceptions to retcodes if really that is what yo want.
> Can't recall any problem there. MFC sits in a DLL, throws a dozen
> different exceptions my code gladly caught...
I haven't run into issues with DLLs, provided that all modules use the same
runtime DLL and compiler options.
However, I have run into a bug in VC6 (now retired) when writing an
exception-to-error-code dispatcher using "try { throw; } catch (exception1
&) {} catch (exception2&) {} ...". VC6 will call the exception's destructor
twice, which means you can't use an exception based on std::exception
(which contains a string). The bug trips when attempting to rethrow from a
catch handler.
Instead, I found it necessary to use an RTTI-based dispatcher, assigning
the exception passed by base class reference (ie. std::exception) and
dynamic_cast to each candidate type to find the one that matches a given
error code.
I think most MS users didn't encounter this because MFC encouraged
programmers to throw new'd exception objects and to delete the exception at
the point of catch. As with all scalars, calling a pointer's destructor
twice has no effect.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Kenneth
|
1/12/2009 11:17:53 PM
|
|
Le Chaud Lapin wrote:
> On Jan 11, 12:02 pm, Ulrich Eckhardt <dooms...@knuut.de> wrote:
>
> First of all, I was appalled that Google prescribes no exceptions and
> no non-const references.
>
>> Any container requires allocation and will throw when that fails, just
>> as 'new' does. 'operator+' requires allocation, too, but its returnvalue
>> is
>> that object already so there is no way to signal that this allocation
>> failed, safe using a string that can not only contain various amounts of
>> textual data but also have a 'broken' state. Similarly, passing a string
>> literal to a function that takes a string type requires an allocation.
>> Reading a file into memory requires memory and that opening the file
>> works.
>
> Yep.
>
> Ironically, from an aesthetic point of view, exceptions and non-const-
> references are two of the most liberating features of C++. They allow
> a mode of thinking that is simply impossible in their absence.
(Focusing on exceptions) I agree in spirit, but at the same time I wish
there was a clearer advantage of exception-using code. If there were, I
have no doubt Google would be willing to adopt that technological
advantage. They have a great deal of smart and knowledgeable people, and
at least according to my friends who work there, the culture is rather
open to good ideas. Sadly, we still have to convince ourselves as a
community that C++ code using exceptions is significantly simpler, more
modular, and easier to maintain.
Aside from the commonly mentioned issues, I think (and shared that
thought with this newsgroup) that "try" introducing a new scope is a
rather unfortunate choice, and that RAII fosters proliferation of types
- and types are not easy to define in C++. Also, exceptions have a few
ancillary issues: exception specs are statically declared but
dynamically checked; recursive throws are handled rather bluntly by
terminating the program; std exceptions are polymorphic but don't offer
cloning or visitation primitives, two rather essential ingredients. All
in all, not a very compelling package. In the words of Chris Rock, "Does
that mean I agree? No. But I understand!"
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/12/2009 11:21:52 PM
|
|
On Jan 11, 11:57 am, "Balog Pal" <p...@lib.hu> wrote:
> "Metron" <Metro...@gmail.com> az al�bbiakat �rta a k�vetkezo
> h�r�zenetben:
> bd98f839-dde7-44a3-89d3-134e3f325...@r15g2000prh.googlegroups.com...
> > Also, IIRC but please correct me, if I'm wrong, under Windows throwing
> > exceptions is problematic once it has to cross DLL borders (ie. when
> > you've implemented an interface within the DLL and a function within
> > the implementation has to throw an exception).
>
> Can't recall any problem there. MFC sits in a DLL, throws a dozen
> different
> exceptions my code gladly caught...
Metron might be refering to the heap mismatch problem, where exception
allocates memory during construction of its object using new, the new
that is inside DLL, then EXE attempts to deallocate that same memory
using delete, the delete that is inside EXE. Boom!
This problem is easily solved by making the destructor virtual. In
fact, it solves the general problem where memory allocated by
constructor in one module is deallocated by destructor in different
module. A virtual destructor forces invocation of the delete code that
is actually inside the module containing the new that allocated the
memory from their respective heap.
I often get into fights with C++ jocks who insist, rather emphatically
I might add, that I am wrong about this without checking for
themselves.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/12/2009 11:24:01 PM
|
|
On Jan 12, 11:21 pm, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> Le Chaud Lapin wrote:
> (Focusing on exceptions) I agree in spirit, but at the same time I wish
> there was a clearer advantage of exception-using code. If there were, I
> have no doubt Google would be willing to adopt that technological
> advantage. They have a great deal of smart and knowledgeable people, and
> at least according to my friends who work there, the culture is rather
> open to good ideas. Sadly, we still have to convince ourselves as a
> community that C++ code using exceptions is significantly simpler, more
> modular, and easier to maintain.
What do you do in your personal coding regarding exceptions?
For me, the need is summed up in one word: form. When I design a
system, form supercedes everything.
I shamelessly admit that I will write an entire program with hardly
any (real) error checking to get the form right, then backtrack and
add code for error checking. I do this because premature error-
checking tends to impede the thought process. I prefer to begin with
idealistic expectations since, in a perfect world, perfect form will
be achieved when a system is unencumbered by inherently imperfect
error checking. However, as I go along, I make small notes places
where things can go wrong. I write "// DEFECT..." where error codes
should be returned, and
throw 0;
....where exceptions should be thrown.
Then I backtrack in my perfect-system-but-no-error-checking, grepping
for these notes. As expected, I discover special places where no
amount of error-checking whatsoever will preserve the beauty of form
that has been created by ignoring it in favor of exceptions.
Here is example of big-integer division code that could be buried deep
within a cryptographic operation:
Integer operator / (const Integer ÷nd, const Integer &divisor)
{
Integer quotient, remainder;
if (!Integer::divide (dividend, divisor, quotient, remainder))
throw Integer::DIVISION_BY_ZERO;
return quotient;
}
Integer::divide() returns true for success, false for failure, but
operator / would lose all its beauty if it had to return an error:
My entire system is littered with examples like this, places where the
form would be ruined without exceptions. And in places where I
exceptions could be replaced with error codes, the result would be a
mess, because I would still have to check for errors, only I would be
forced to check right then, right there, deep within the abyss, even
though I would not have a clue of how to proceed. This would force
every piece of code in the the call chain to be prepared to deal with
errors beneath it, creating irregularity everywhere. With exceptions,
I get to pretend everything is mostly perfect, and simply worry about
resource reclamation, which is very easy in my world, as I avoid
unwarranted proliferation of polymorphic objects.
> Aside from the commonly mentioned issues, I think (and shared that
> thought with this newsgroup) that "try" introducing a new scope is a
> rather unfortunate choice, and that RAII fosters proliferation of types
> - and types are not easy to define in C++. Also, exceptions have a few
> ancillary issues: exception specs are statically declared but
> dynamically checked; recursive throws are handled rather bluntly by
> terminating the program; std exceptions are polymorphic but don't offer
> cloning or visitation primitives, two rather essential ingredients. All
> in all, not a very compelling package. In the words of Chris Rock, "Does
> that mean I agree? No. But I understand!"
Hmm..didn't know that std exceptions are polymorphic. Again, overuse
of polymorphism rears its ugly head, IMO.
As far as type proliferation goes, I have always said that defining
new classes is the narcotic of C++.
I believe the relation:
y = number-of-C++-classes-defined(number-of-systems-built)
should be a logarithmic:
y = C * ln(number-of-systems built)
If it is not logarithmic, if means that we have not been finding
classes whose persistence attest to their virtue. For example,
millions, if not billions, of lines of C++ have been written, and we
still do not have obviously fundamental classes like hierarchies and
associative hierarchies.
A more linear relationship might also mean that the concept-space
might be malformed in minds of those who feel need to create types.
All of us is familiar with the engineer down the hall who thinks that
affixing "C" to the beginning of any word justifies its definition as
a class:
class CCheckerForNullPointer {} ;
One day it might help us to stop creating C++ libraries and instead
create a taxonomy of what classes should be in a feature-complete C++
library to bring about this logarithmic relationship.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/13/2009 6:19:51 PM
|
|
On Jan 10, 6:28 pm, "Hak...@gmail.com" <Hak...@gmail.com> wrote:
> On Jan 9, 6:21 pm, "Martin T." <0xCDCDC...@gmx.at> wrote:
> On a note about that, though: Some libraries, like OpenGL, instead of
> throwing create lists of problems stored in strings. It doesn't crash
> my program and it tells me what the problem is so I can just rewrite
> the bad code. Not a bad work-a-round!
I have seen code where roughly 10% of all lines were perror's() and
printf's(), potentially complaining about things that could not
possibly occur if programmer would simply structure the code so that
they could not, at which point, the programmer would become an
engineer rather than a genial confidant to whom the program provides a
detailed report of just how bad it is written...by said programmer,
who, having written the code, should already know.
if (arg < 0)
perror ("Negative value passed to function when positive value
expected!!!"); // silly, IMO :)
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/13/2009 6:23:16 PM
|
|
On Jan 11, 12:25 am, "Balog Pal" <p...@lib.hu> wrote:
> "Martin T." <0xCDCDC...@gmx.at> :
>
> >I found the arguments in the Google Style Guide quite interesting:
>
> >http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
>
> I read through the guide, it has a couple items that is completely nuts:
>
> - "Reference Arguments: All parameters passed by reference must be labeled
> const. ...
> In fact it is a very strong convention that input arguments are values or
> const references while output arguments are pointers. ..."
>
> I'm using C++ since about CFront 2.0, first time to hear this idea, and it
> is IMNSHO utter nonsense.
This is, in fact, the convention I like to use.
The reason for passing output parameters by pointer is that the
ampersand at the call site gives you a hint that something is done to
that argument.
Just to make clear, I don't find useful the idea of references-are-
better-pointers-and-cant-be-null.
[]
> - Doing Work in Constructors: Do only trivial initialization in a
> constructor. If at all possible, use an Init() method for non-trivial
> initialization. ... If your object requires non-trivial initialization,
> consider having an explicit Init() method and/or adding a member flag that
> indicates whether the object was successfully initialized.
> I recon most mentors say to do 2 phase init only when absolutely necessary
> for data flow or use cases (i.e. you need instances before knowing params).
> Make it based on "amount of work"?
Agree on that.
Now I have to check whether a class has Init() member function and
call it. Boring and error-prone.
> - (here comes your observed) Exceptions: We do not use C++ exceptions.
>
> > quote Con(3): "Exception safety requires both RAII and different coding
> > practices. ...
> > - Of course. When people are not using exceptions, they are usually just
> > ignoring the error return codes, so no need for a no-fail commit phase.
> > ::-)
>
> Yea++. RAII and SEME-tolerant layout serves better readability and
> correctness. Wrt anything, including exceptions.
Yep, scoped resource management (aka RAII) makes your code more robust
in the face of errors, and, in general, is way more superior to
garbage collection and try/finally constructs. It does require some
discipline and consistency, however, the results are well worth it.
> > quote Decisinon: "Given that Google's existing code is not
> > exception-tolerant, the costs of using exceptions are somewhat greater
>
> Anyone has familiarity with Google code? Is it correct and readable? This
> statement rings me a "stay away" warning.
One thing for sure: reading google's C++ code won't make you a better
programmer. The same holds true for reading their coding standard.
http://code.google.com/p/google-glog/source/browse/trunk/src/glog/logging.h.in
> > "(...) Because we'd like to use our open-source projects at Google and
> > it's difficult to do so if those projects use exceptions, we need to
> > advise against exceptions in Google open-source projects as well."
>
> > - I really don't know. I'm all for not introducing exceptions into
> > existing code bases that are not exception safe. But recommending
> > against the use of exceptions for new (sub)projects and modules because
> > the existing code base does not use them seems a bit overreacting.
>
> Taking the earlier statement it makes sense -- for Googlers -- if they can't
> see so better make darkness the standard.
> :-((
In my experience, corporations have difficulty hiring quality
developers in numbers. Thus, they lower quality standards and use
"easy" languages like C# and Java.
--
Max
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Maxim
|
1/13/2009 6:25:19 PM
|
|
Le Chaud Lapin <jaibuduvin@gmail.com> wrote in news:1f656241-ba00-44c7-
9dba-9324098f0953@f33g2000vbf.googlegroups.com:
> This problem is easily solved by making the destructor virtual. In
> fact, it solves the general problem where memory allocated by
> constructor in one module is deallocated by destructor in different
> module. A virtual destructor forces invocation of the delete code that
> is actually inside the module containing the new that allocated the
> memory from their respective heap.
>
> I often get into fights with C++ jocks who insist, rather emphatically
> I might add, that I am wrong about this without checking for
> themselves.
That surprises me. I would have thought that the new/delete would be those
of the call sites, not the object definition sites. (Unless the class
overloads new and delete.)
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Kenneth
|
1/13/2009 7:31:12 PM
|
|
On Jan 12, 1:32 pm, David Abrahams <d...@boostpro.com> wrote:
> ...
>
> Most devs don't handle errors properly no matter what tools you give
> them. At least exceptions make it easier to do the job right.
They also tend to convert silent failures into "in your face" crashes
during development and testing. I think most of us would feel that
this is a good thing.
Most development teams seem to at heart prefer the silent kind of
failures though, but will never admit it. These are the ones that end
up with a mix of error codes, return bools variously indicating
'success' or 'failure', and exceptions (some of which come straight
from vanilla C++).
Invariably, the return codes are checked imperfectly, while the
exceptions do their job as designed. People like to blame the
messenger I guess, and exceptions take a bad rap rather than the
underlying causes.
Recent example: http://www.openssl.org/news/secadv_20090107.txt
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/13/2009 7:32:23 PM
|
|
On Jan 12, 11:21 pm, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> ...
> (Focusing on exceptions) I agree in spirit, but at the same time I wish
> there was a clearer advantage of exception-using code.
> ...
How about:
In short, consistent use of exceptions can cut out 50% of dead, buggy
from your codebase:
Error handling code tends to be itself error prone, poorly maintained,
and under-tested. It tends to get mixed in throughout the program on a
line-by-line basis.
C-language APIs typically report failures via return values and status
variables, necessitating conditional checks and error handler blocks
after every significant function call. Use such APIs correctly can
lead to more than half of a program's code being devoted to error
detection and handling.
The vast majority of the time no error is encountered. Therefore, the
vast majority of error handling code is completely dead. Most dead
code is of little value. Instead it acts to obscure that code which
does have value in expressing the programs essential operations.
In addition to degrading the S/N of the program's source, it can
consume valuable computational resources.
Typically the caller has two options for handling a failed call: (1)
it can fail itself, or (2) it can make some modifications and retry
the operation (after waiting some time, freeing some resources, etc.).
The vast majority of places in code where errors can be detected don't
have the ability or motivation to attempt (2).
Exceptions, properly used, make the (1) case automatic and
transparent.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/13/2009 7:33:08 PM
|
|
On 14 jan, 02:31, Kenneth Porter <shiva.blackl...@sewingwitch.com>
wrote:
> That surprises me. I would have thought that the new/delete would be those
> of the call sites, not the object definition sites. (Unless the class
> overloads new and delete.)
Well, obviously, the delete which is invoked is the one that is in the
virtual function table (if the destructor is virtual).
The table the object uses is the one it was provided with during
construction.
So, yes, having a virtual destructor means both new and delete will be
from the same module.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/14/2009 12:36:35 PM
|
|
On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
> if (arg < 0)
> perror ("Negative value passed to function when positive value
> expected!!!"); // silly, IMO :)
They simply don't know about assert(arg >= 0).
Making arg unsigned could also have been a solution (or not).
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/14/2009 12:36:37 PM
|
|
On 14 jan, 01:19, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
> Here is example of big-integer division code that could be buried deep
> within a cryptographic operation:
>
> Integer operator / (const Integer ÷nd, const Integer &divisor)
> {
> Integer quotient, remainder;
> if (!Integer::divide (dividend, divisor, quotient, remainder))
> throw Integer::DIVISION_BY_ZERO;
> return quotient;
>
> }
>
> Integer::divide() returns true for success, false for failure, but
> operator / would lose all its beauty if it had to return an error:
Why not throw directly from Integer::divide?
You could also return a tuple (here, a pair) rather than taking
objects to modify by reference.
This would likely result in more efficient code, for a number of
reasons.
> For example,
> millions, if not billions, of lines of C++ have been written, and we
> still do not have obviously fundamental classes like hierarchies and
> associative hierarchies.
A recursive variant might provide the kind of thing you're looking
for. See Boost.Variant.
This is usually how trees are handled in high-level languages, be it
functional ones (where the variant is explicit) or dynamic ones (where
the variant is implicit, it's the dynamic typing).
Property trees are also a nice way to deal with trees. (You associate
a path in a tree with a value, quite useful for hierarchical
configuration files)
You might want to look at Boost.PropertyTree.
These, however, don't provide a more fine-grained and optimized way to
deal with trees. There is work in the standard (emitted from a
Boost.Tree GSoC) for that.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/14/2009 12:37:42 PM
|
|
on Tue Jan 13 2009, Kenneth Porter <shiva.blacklist-AT-sewingwitch.com> wrote:
> Le Chaud Lapin <jaibuduvin@gmail.com> wrote in news:1f656241-ba00-44c7-
> 9dba-9324098f0953@f33g2000vbf.googlegroups.com:
>
>> This problem is easily solved by making the destructor virtual. In
>> fact, it solves the general problem where memory allocated by
>> constructor in one module is deallocated by destructor in different
>> module. A virtual destructor forces invocation of the delete code that
>> is actually inside the module containing the new that allocated the
>> memory from their respective heap.
>>
>> I often get into fights with C++ jocks who insist, rather emphatically
>> I might add, that I am wrong about this without checking for
>> themselves.
>
> That surprises me. I would have thought that the new/delete would be those
> of the call sites, not the object definition sites. (Unless the class
> overloads new and delete.)
Don't worry, the validity of the hot bunny's statement is completely
dependent on implementation details of the particular compiler in
question.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/14/2009 12:42:30 PM
|
|
Maxim Yegorushkin wrote:
> The reason for passing output parameters by pointer is that the
> ampersand at the call site gives you a hint that something is done to
> that argument.
I have always found this to be a bogus argument, and it is really
surprising me that it still keeps getting repeated after years of
debate. Consider:
// foomangle.h
void mangle(foo* f);
// bar.h
class bar
{
// ...
foo* myfoo;
};
// bar.cpp
void bar::rebuild()
{
// ...
mangle(myfoo); // whoa! no ampersand!
}
Relying on ampersands to signal argument modification (and their absence
to signal the opposite) gives a false sense of security because you get
the wrong signal when the argument is already of pointer type. The
convention, even if rigidly being adhered to, does not relieve you from
checking the documentation of the called function.
> Just to make clear, I don't find useful the idea of references-are-
> better-pointers-and-cant-be-null.
Reducing the number of states of a function is not useful?
--
Gerhard Menzl
Non-spammers may respond to my email address, which is composed of my
full name, separated by a dot, followed by at, followed by "fwz",
followed by a dot, followed by "aero".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
1/14/2009 12:43:18 PM
|
|
On Jan 14, 2:31 am, Kenneth Porter <shiva.blackl...@sewingwitch.com>
wrote:
> Le Chaud Lapin <jaibudu...@gmail.com> wrote in news:1f656241-ba00-44c7-
> 9dba-9324098f0...@f33g2000vbf.googlegroups.com:
>
> > This problem is easily solved by making the destructor virtual. In
> > fact, it solves the general problem where memory allocated by
> > constructor in one module is deallocated by destructor in different
> > module. A virtual destructor forces invocation of the delete code that
> > is actually inside the module containing the new that allocated the
> > memory from their respective heap.
>
> > I often get into fights with C++ jocks who insist, rather emphatically
> > I might add, that I am wrong about this without checking for
> > themselves.
>
> That surprises me. I would have thought that the new/delete would be those
> of the call sites, not the object definition sites. (Unless the class
> overloads new and delete.)
Well... it boils down to: "It has to be destructed where it has been
created."
That's why I tend to make extensive use of the factory pattern mixed
with the facade pattern. To be more precise the parameterized factory
pattern. My extension to this pattern is that the factory not only has
the create function but also the destroy function.
Using the facade pattern (kind of management of several factories) the
facade functions select the appropriate factory for object creation.
Since the identification of the object type to create is part of the
object itself, it is possible to have facade functions that select the
right factory to destroy the object.
NOTE: I'm speaking more of a Windows platform developer. My Linux
development experience tends to zero but still has a little
existens :)
This enables me to have implementation of factories spread in
different DLLs (IE. in a plug-in/add-in environment). And this is
where it's obvious that it can be problematic to destroy objects and
thus that it's difficult to maintain a proper exception handling based
programming style. Either you pass exception objects (but those will
not necessarily pass the DLL boundary depdendent on the compiler
settings) or you pass pointers (in which case you might have to delete
them or not... dependent on the implementation of the exceptions).
This link is an interesting resource: http://www.parashift.com/c++-faq-lite/exceptions.html
Have fun,
Metron
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Metron
|
1/14/2009 12:43:21 PM
|
|
On 2009-01-14 01:25, Maxim Yegorushkin wrote:
> On Jan 11, 12:25 am, "Balog Pal" <p...@lib.hu> wrote:
>> "Martin T." <0xCDCDC...@gmx.at> :
>>
>> >I found the arguments in the Google Style Guide quite interesting:
>>
>> >http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showon...
>>
>> I read through the guide, it has a couple items that is completely nuts:
>>
>> - "Reference Arguments: All parameters passed by reference must be labeled
>> const. ...
>> In fact it is a very strong convention that input arguments are values or
>> const references while output arguments are pointers. ..."
>>
>> I'm using C++ since about CFront 2.0, first time to hear this idea, and it
>> is IMNSHO utter nonsense.
>
> This is, in fact, the convention I like to use.
>
> The reason for passing output parameters by pointer is that the
> ampersand at the call site gives you a hint that something is done to
> that argument.
As others have pointed out this argument is invalid, I work with a
moderately large system written in C and it is not uncommon that the
variable passed to a function is a pointer the current function received
as an argument, or the argument is a pointer for which memory was
allocated before passing on. In fact I'd say that it's very rare to pass
the address of a local variable. The only good indicator of whether the
argument passed can be changed or not is const, and it's not used nearly
as often as it could have.
--
Erik Wikström
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
UTF
|
1/15/2009 8:54:34 AM
|
|
Bo Persson wrote:
> Hakusa@gmail.com wrote:
[...]
>> On a note about that, though: Some libraries, like OpenGL, instead
>> of throwing create lists of problems stored in strings. It doesn't
>> crash my program and it tells me what the problem is so I can just
>> rewrite the bad code. Not a bad work-a-round!
>
> OpenGL is a special case, where bad output is sometimes acceptable.
> The image looks a bit strange, but the application can often continue
> anyway. The next couple of frame updates might hide the problem.
>
> This is not generally the case.
Some clarification is in order:
OpenGL creates a list of problems stored in strings only when you try to
compile or link shaders at runtime. The errors that it needs to report
are bugs in your shader source code.
The programmer is still burdened with fetching that information and
reporting it anyway.
Conforming OpenGL implementations were never allowed to crash your
program no matter what you pass them. Note that crashing your program
and throwing an exception are two different things. OpenGL doesn't crash
your program as a matter of policy. It doesn't throw exceptions as a
matter of being a C API. (Technically it's also a FORTAN API, etc.,
which is why the API also happens to avoid structures.)
(Compiling shaders at runtime always seemed like a stupid idea to me
anyway. (Yes, I know what the arguments for it are.))
As for C++ exceptions in general, they're certainly to be preferred over
'undefined behavior' even when programmers don't bother to catch them.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/15/2009 8:57:09 AM
|
|
On Jan 14, 12:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
wrote:
> Maxim Yegorushkin wrote:
> > The reason for passing output parameters by pointer is that the
> > ampersand at the call site gives you a hint that something is done to
> > that argument.
>
> I have always found this to be a bogus argument, and it is really
> surprising me that it still keeps getting repeated after years of
> debate. Consider:
>
> // foomangle.h
>
> void mangle(foo* f);
>
> // bar.h
>
> class bar
> {
> // ...
> foo* myfoo;
> };
>
> // bar.cpp
>
> void bar::rebuild()
> {
> // ...
> mangle(myfoo); // whoa! no ampersand!
> }
>
> Relying on ampersands to signal argument modification (and their absence
> to signal the opposite) gives a false sense of security because you get
> the wrong signal when the argument is already of pointer type. The
> convention, even if rigidly being adhered to, does not relieve you from
> checking the documentation of the called function.
I smell a serious breach of semantic regularity here. :)
I have written extensively about the fact that pointers are not the
things that they point to, but 1st-class bona-fide objects, scalar
objects in their own right. So technically speaking, "technically" in
the sense that the Earth is round and not flat, Maxim's point of view
is not only correct, but this other point of view is dangerous, IMO.
If someone were to put a gun to my head and ask me, "Quickly, what is
the type of myfoo?" with penalty of death for incorrect answer, I
would examine the code and respond..."Pointer to foo." I would _not_
say, "Foo.", because that would be incorrect.
>From this point of view, myfoo is a pointer, and the label "myfoo"
refers to an object that is on the stack. That object, and not the
object at the call site (another pointer!), will be modified, if
necessary.
You could pass by value an unsigned long int, and the label 'v' would
only allow you to modify anobject that of the same type as v, but
local to mangle, not the object that was used to generate the value of
v upon call:
void mangle(unsigned long int v);
You could pass by reference an unsigned long int, and the label 'v'
would allow you to modify an object that of the same type as v,
external to mangle, the object that is v upon call:
void mangle(unsigned long int &v);
You could pass by reference to const unsigned long int, and the label
'v' would not allow you to modify an object that of the same type as
v, external to mangle, the object that is v upon call:
void mangle(const unsigned long int &v);
You could pass by refrence to const foo, and the label 'f' would not
allow you to modify an object that of the same type as f, namely, a
pointer!, external to mangle, the object that is v upon call:
void mangle(foo* &f);
I know you know all of these things, but B.L. Whorf was right. Words
are critical.
I had a professor of stochastics in college who understood this very
well, and was extremely careful...to the point of appearing to have a
speech impediment at critical utterances, in making sure erroneous
concepts did not enter the minds of the newly initiated. I did the
same with my own students, carefully avoiding ever saying...
"It passes a const reference..." because I know that most students in
class already have erroneous notion that references are pointers and
can be passed around.
Nor do I do what you did above, puting the asterisk (*) right up
against the foo:
> void mangle(foo* f);
Instead I write
void mangle(foo *f);
to emphasize the semantics, and how the compiler thinks, because this
subtle change actually alters the way students think, and it prempts
bad habits in thought process.
Some students see the above line and insist on still calling f a
"foo", after having been admonished that it is not, so I write this on
board:
void mangle (const Foo ****** &f);
and ask, "What is f now?"
They go silent.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/15/2009 9:53:41 AM
|
|
Le Chaud Lapin wrote:
> ... might be refering to the heap mismatch problem, where exception
> allocates memory during construction of its object using new, the new
> that is inside DLL, then EXE attempts to deallocate that same memory
> using delete, the delete that is inside EXE. Boom!
>
> This problem is easily solved by making the destructor virtual. In
> fact, it solves the general problem where memory allocated by
> constructor in one module is deallocated by destructor in different
> module. A virtual destructor forces invocation of the delete code that
> is actually inside the module containing the new that allocated the
> memory from their respective heap.
>
Not believing it myself I have tried it under Visual Studio 2005 and
it's indeed correct :-)
The question is, on which platforms can we rely on this behaviour?
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/15/2009 9:54:30 AM
|
|
On Jan 14, 6:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
wrote:
> Maxim Yegorushkin wrote:
> > The reason for passing output parameters by pointer is that the
> > ampersand at the call site gives you a hint that something is done to
> > that argument.
>
> I have always found this to be a bogus argument, and it is really
> surprising me that it still keeps getting repeated after years of
> debate. Consider:
>
> // foomangle.h
>
> void mangle(foo* f);
>
> // bar.h
>
> class bar
> {
> // ...
> foo* myfoo;
> };
>
> // bar.cpp
>
> void bar::rebuild()
> {
> // ...
> mangle(myfoo); // whoa! no ampersand!
> }
>
> Relying on ampersands to signal argument modification (and their absence
> to signal the opposite) gives a false sense of security because you get
> the wrong signal when the argument is already of pointer type.
myfoo argument does not change here, thus the desired effect achieved.
> The
> convention, even if rigidly being adhered to, does not relieve you from
> checking the documentation of the called function.
It does not indeed.
> > Just to make clear, I don't find useful the idea of references-are-
> > better-pointers-and-cant-be-null.
>
> Reducing the number of states of a function is not useful?
It sounds useful theoretically. Practically, I haven't seen a case
where cant-be-null property of references would make a difference for
function arguments.
--
Max
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Maxim
|
1/15/2009 10:30:18 AM
|
|
On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> It sounds useful theoretically. Practically, I haven't seen a case
> where cant-be-null property of references would make a difference for
> function arguments.
It's very practical. Example:
void Func(SomeObject* obj) // must check for null args usually
{
assert(null != obj)
// do something
}
void Func(SomeObject& obj) // obj can't be null so no need to assert
anything
{
// do something
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
tonytech08
|
1/15/2009 12:08:17 PM
|
|
On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> Practically, I haven't seen a case
> where cant-be-null property of references would make a difference for
> function arguments.
int a = 0, b = 1;
std::swap<int>(a, b); // Probably pretty darn efficient.
swap_c_style(&a, &b);
The implementation of swap_c_style has to either check both args for
null, or produce undefined behavior over its domain of input values.
The compiler probably has a harder job optimizing away the case where
(*a == *b), too.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/15/2009 12:08:46 PM
|
|
>"Gerhard Menzl" <clcppm-poster@this.is.invalid>
>> Maxim Yegorushkin wrote:
>
>> The reason for passing output parameters by pointer is that the
>> ampersand at the call site gives you a hint that something is done to
>> that argument.
>
> I have always found this to be a bogus argument, and it is really
> surprising me that it still keeps getting repeated after years of debate.
Yea, seems as bogus to me even considering the stone age.
But for like a decade, in most useful working environments you just mouse
over the function and it will show the signature. Where types and param
names shall give a good hint. No need to guess.
If still in doubt another click (or key) navigates to declaration,
definition, dox, etc.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/15/2009 7:53:11 PM
|
|
tonytech08 wrote:
> On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> wrote:
>
>> It sounds useful theoretically. Practically, I haven't seen a case
>> where cant-be-null property of references would make a difference for
>> function arguments.
>
>
> It's very practical. Example:
>
> void Func(SomeObject* obj) // must check for null args usually
> {
> assert(null != obj)
>
> // do something
> }
>
> void Func(SomeObject& obj) // obj can't be null so no need to assert
> anything
> {
> // do something
> }
Yah, we just shouldn't forget that the "can't be null" is only assumed,
not enforced, by most of today's implementations. It does clarify
interfaces nicely though. However, a combination of tradition and
language rules makes interface-based programs in C++ still deal in
pointers (bald or raw). Passing an interface by reference does raise
some eyebrows :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
|
1/16/2009 1:51:26 PM
|
|
Le Chaud Lapin wrote:
> On Jan 14, 12:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
> wrote:
>> // foomangle.h
>>
>> void mangle(foo* f);
>>
>> // bar.h
>>
>> class bar
>> {
>> // ...
>> foo* myfoo;
>> };
>>
>> // bar.cpp
>>
>> void bar::rebuild()
>> {
>> // ...
>> mangle(myfoo); // whoa! no ampersand!
>> }
> I smell a serious breach of semantic regularity here. :)
>
> I have written extensively about the fact that pointers are not the
> things that they point to, but 1st-class bona-fide objects, scalar
> objects in their own right. So technically speaking, "technically" in
> the sense that the Earth is round and not flat, Maxim's point of view
> is not only correct, but this other point of view is dangerous, IMO.
>
> If someone were to put a gun to my head and ask me, "Quickly, what is
> the type of myfoo?" with penalty of death for incorrect answer, I
> would examine the code and respond..."Pointer to foo." I would _not_
> say, "Foo.", because that would be incorrect.
>
>>From this point of view, myfoo is a pointer, and the label "myfoo"
> refers to an object that is on the stack. That object, and not the
> object at the call site (another pointer!), will be modified, if
> necessary.
I think you are mixing up the physical and the logical aspects of the
code. Physically, myfoo is, of course, a pointer, and its value is not
going to be changed by mangle(). Logically, however, mangle's
responsibility is to mangle foos. That it does so via a pointer is an
implementation detail, and that there is a choice to use a pointer or a
reference is a peculiarity of the C++ language. What happens to the foo
object is the main focus of interest. Hence, relying on the ampersand is
confusing and misleading.
> Nor do I do what you did above, puting the asterisk (*) right up
> against the foo:
>
>> void mangle(foo* f);
>
> Instead I write
>
> void mangle(foo *f);
>
> to emphasize the semantics, and how the compiler thinks, because this
> subtle change actually alters the way students think, and it prempts
> bad habits in thought process.
The counterargument is that the asterisk is part of the type and not
part of the parameter. A considerable percentage of C++ programmers, as
well as the Standard, stick to the convention I have used.
> Some students see the above line and insist on still calling f a
> "foo", after having been admonished that it is not, so I write this on
> board:
>
> void mangle (const Foo ****** &f);
>
> and ask, "What is f now?"
>
> They go silent.
My code is intended to be read by professionals, not by students. Any
serious practitioner of C++ must be able to recognize both forms, and to
distinguish between the levels of indirection involved.
--
Gerhard Menzl
Non-spammers may respond to my email address, which is composed of my
full name, separated by a dot, followed by at, followed by "fwz",
followed by a dot, followed by "aero".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
1/16/2009 1:58:33 PM
|
|
On Jan 15, 6:08 pm, tonytech08 <tonytec...@gmail.com> wrote:
> On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> wrote:
>
> > It sounds useful theoretically. Practically, I haven't seen a case
> > where cant-be-null property of references would make a difference
> > for
> > function arguments.
>
> It's very practical. Example:
>
> void Func(SomeObject* obj) // must check for null args usually
> {
> assert(null != obj)
> // do something
> }
It is going to dump core on NULL pointer with or without the
assertion. This assertion is plain useless.
--
Max
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Maxim
|
1/18/2009 4:49:33 AM
|
|
On Jan 15, 6:08 pm, Marsh Ray <marsh...@gmail.com> wrote:
> On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> wrote:
>
> > Practically, I haven't seen a case
> > where cant-be-null property of references would make a difference
> > for
> > function arguments.
>
> int a = 0, b = 1;
>
> std::swap<int>(a, b); // Probably pretty darn efficient.
It as efficient as swap_c_style. See below.
> swap_c_style(&a, &b);
>
> The implementation of swap_c_style has to either check both args for
> null, or produce undefined behavior over its domain of input values.
No checks necessary, it should just dump a core file for your
examination.
> The compiler probably has a harder job optimizing away the case where
> (*a == *b), too.
Why would it? A reference argument to a function is the same as a
pointer argument on the binary level.
Let's compare optimised versions of std::swap<int, int>() and
swap_c_style<int>():
[max@truth test]$ g++ --version
g++ (GCC) 4.3.0 20080428 (Red Hat 4.3.0-8)
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There
is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.
[max@truth test]$ cat test.cc
#include <algorithm>
template<class T>
void swap_c_style(T* a, T* b)
{
T t(*a);
*a = *b;
*b = t;
}
int main()
{
int a = 1, b = 2;
std::swap(a, b);
swap_c_style(&a, &b);
}
[max@truth test]$ g++ -O3 -fomit-frame-pointer -fno-inline -ggdb -Wall
-Wextra -o test.o test.cc
[max@truth test]$ objdump --syms test.o | grep swap | c++filt
08048460 w F .text 00000013 void swap_c_style<int>
(int*, int*)
08048440 w F .text 00000013 void std::swap<int>(int&,
int&)
[max@truth test]$ objdump -d --start-address=0x08048460 --stop-
address=0x08048473 test.o | c++filt
test.o: file format elf32-i386
Disassembly of section .text:
08048460 <void swap_c_style<int>(int*, int*)>:
8048460: 53 push %ebx
8048461: 8b 54 24 08 mov 0x8(%esp),%edx
8048465: 8b 4c 24 0c mov 0xc(%esp),%ecx
8048469: 8b 1a mov (%edx),%ebx
804846b: 8b 01 mov (%ecx),%eax
804846d: 89 02 mov %eax,(%edx)
804846f: 89 19 mov %ebx,(%ecx)
8048471: 5b pop %ebx
8048472: c3 ret
[max@truth test]$ objdump -d --start-address=0x08048440 --stop-
address=0x08048453 test.o | c++filt
test.o: file format elf32-i386
Disassembly of section .text:
08048440 <void std::swap<int>(int&, int&)>:
8048440: 53 push %ebx
8048441: 8b 54 24 08 mov 0x8(%esp),%edx
8048445: 8b 4c 24 0c mov 0xc(%esp),%ecx
8048449: 8b 1a mov (%edx),%ebx
804844b: 8b 01 mov (%ecx),%eax
804844d: 89 02 mov %eax,(%edx)
804844f: 89 19 mov %ebx,(%ecx)
8048451: 5b pop %ebx
8048452: c3 ret
Can you spot the difference?
--
Max
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Maxim
|
1/18/2009 4:50:46 AM
|
|
On Jan 16, 1:58 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
wrote:
> Le Chaud Lapin wrote:
> > On Jan 14, 12:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
> > wrote:
> >> // foomangle.h
>
> >> void mangle(foo* f);
>
> >> // bar.h
>
> >> class bar
> >> {
> >> // ...
> >> foo* myfoo;
> >> };
>
> >> // bar.cpp
>
> >> void bar::rebuild()
> >> {
> >> // ...
> >> mangle(myfoo); // whoa! no ampersand!
> >> }
> > I smell a serious breach of semantic regularity here. :)
>
> >>From this point of view, myfoo is a pointer, and the label "myfoo"
> > refers to an object that is on the stack. That object, and not the
> > object at the call site (another pointer!), will be modified, if
> > necessary.
>
> I think you are mixing up the physical and the logical aspects of the
> code. Physically, myfoo is, of course, a pointer, and its value is not
> going to be changed by mangle(). Logically, however, mangle's
> responsibility is to mangle foos. That it does so via a pointer is an
> implementation detail, and that there is a choice to use a pointer or a
> reference is a peculiarity of the C++ language. What happens to the foo
> object is the main focus of interest. Hence, relying on the ampersand is
> confusing and misleading.
Actually I am not mixing up anything.
Of every principle I have ever learned about science and engineering,
this one would definitely be in the top 15. I firmly believe that it
is nothing short of a cardinal sin to promote this point of view.
Note that I am not saying that we simply have a difference of opinion
here. This is _fundamental_, in the most extreme sense. That's why I
alluded the the Earth flat/round argument. There is something going
on here were you have two groups of people, one large, one smaller,
with diametrically opposed points of views, and they cannot both be
right.
I will certainly revisit this topic [pointers are not the things they
point to] in a separate thread when I have more time. It's that
important.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/18/2009 4:55:46 AM
|
|
on Thu Jan 15 2009, Le Chaud Lapin <jaibuduvin-AT-gmail.com> wrote:
> I smell a serious breach of semantic regularity here. :)
>
> I have written extensively about the fact that pointers are not the
> things that they point to, but 1st-class bona-fide objects, scalar
> objects in their own right. So technically speaking, "technically" in
> the sense that the Earth is round and not flat, Maxim's point of view
> is not only correct, but this other point of view is dangerous, IMO.
In that case, technically, there are no output parameters in Maxim's
code.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/18/2009 7:23:18 AM
|
|
Maxim Yegorushkin wrote:
> On Jan 15, 6:08 pm, Marsh Ray <marsh...@gmail.com> wrote:
>> On Jan 15, 10:30 am, Maxim Yegorushkin
>> <maxim.yegorush...@gmail.com> wrote:
>>
>>> Practically, I haven't seen a case
>>> where cant-be-null property of references would make a difference
>>> for
>>> function arguments.
>>
>> int a = 0, b = 1;
>>
>> std::swap<int>(a, b); // Probably pretty darn efficient.
>
> It as efficient as swap_c_style. See below.
>
>> swap_c_style(&a, &b);
>>
>> The implementation of swap_c_style has to either check both args
>> for null, or produce undefined behavior over its domain of input
>> values.
>
> No checks necessary, it should just dump a core file for your
> examination.
This assumes that you have a hardware assisted check for null pointer
references. On other systems there will be an additional cost -
presumably the systems where you can afford this the least.
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
|
1/19/2009 1:46:44 AM
|
|
On 18 jan, 11:50, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> > int a = 0, b = 1;
>
> > std::swap<int>(a, b); // Probably pretty darn efficient.
>
> It as efficient as swap_c_style. See below.
>
> > swap_c_style(&a, &b);
References are slightly easier to optimize than pointers.
A specific implementation may not find it harder, but some others may.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/19/2009 1:47:38 AM
|
|
On 2009-01-18, Maxim Yegorushkin <maxim.yegorushkin@gmail.com> wrote:
> On Jan 15, 6:08 pm, tonytech08 <tonytec...@gmail.com> wrote:
>>
>> void Func(SomeObject* obj) // must check for null args usually
>> {
>> assert(null != obj)
>> // do something
>> }
>
> It is going to dump core on NULL pointer with or without the
> assertion. This assertion is plain useless.
>
Not on systems without memory protection (e.g., in DOS it would just
access the division by zero exception vector).
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Zeljko
|
1/19/2009 1:48:26 AM
|
|
Maxim Yegorushkin wrote:
> On Jan 15, 6:08 pm, tonytech08 <tonytec...@gmail.com> wrote:
>> On Jan 15, 10:30 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
>> wrote:
>>
>>> It sounds useful theoretically. Practically, I haven't seen a case
>>> where cant-be-null property of references would make a difference
>>> for
>>> function arguments.
>> It's very practical. Example:
>>
>> void Func(SomeObject* obj) // must check for null args usually
>> {
>> assert(null != obj)
>> // do something
>> }
>
> It is going to dump core on NULL pointer with or without the
> assertion. This assertion is plain useless.
Letting a program dump core is not an alternative to an assertion.
More than that, an assertion is not an alternative to an actual check
for invalid values.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/19/2009 1:50:37 AM
|
|
"Maxim Yegorushkin" <maxim.yegorushkin@gmail.com>
>> void Func(SomeObject* obj) // must check for null args usually
>> {
>> assert(null != obj)
>> // do something
>> }
>
> It is going to dump core on NULL pointer with or without the
> assertion. This assertion is plain useless.
Says what?
The standard states dereferencing NULL pointer is undefined behavior. So
anything can happen.
On most modern unix systems, in practice, it is really dumping core or just
terminates the process via SIGSEGV, yet it is only one of a zillion possible
outcomes.
Just an example: on WIN32, using the not most recent MSVC NULL-access
generates a SOH exception. And if you have a catch(...) block upper,
execution gets there.
While failed assert does a deterministic and sure call to abort(), you can
rely on.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/19/2009 1:56:19 AM
|
|
on Sun Jan 18 2009, Le Chaud Lapin <jaibuduvin-AT-gmail.com> wrote:
>> > refers to an object that is on the stack. That object, and not the
>> > object at the call site (another pointer!), will be modified, if
>> > necessary.
>>
>> I think you are mixing up the physical and the logical aspects of the
>> code. Physically, myfoo is, of course, a pointer, and its value is not
>> going to be changed by mangle(). Logically, however, mangle's
>> responsibility is to mangle foos. That it does so via a pointer is an
>> implementation detail, and that there is a choice to use a pointer or a
>> reference is a peculiarity of the C++ language. What happens to the foo
>> object is the main focus of interest. Hence, relying on the ampersand is
>> confusing and misleading.
>
> Actually I am not mixing up anything.
>
> Of every principle I have ever learned about science and engineering,
> this one would definitely be in the top 15. I firmly believe that it
> is nothing short of a cardinal sin to promote this point of view.
> Note that I am not saying that we simply have a difference of opinion
> here. This is _fundamental_, in the most extreme sense. That's why I
> alluded the the Earth flat/round argument. There is something going
> on here were you have two groups of people, one large, one smaller,
> with diametrically opposed points of views, and they cannot both be
> right.
>
> I will certainly revisit this topic [pointers are not the things they
> point to] in a separate thread when I have more time. It's that
> important.
I agree that this is an extremely important idea in general, and for
C/C++ programmers in particular (see Stepanov on Regular Types).
However, most other languages I've encountered either have no value
semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
Isn't it strange that such a fundamental principle is inaccessible to
programmers in those languages?
(**) ...except for immutable objects, for which value semantics are
indistinguishable from reference semantics.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/19/2009 1:58:30 AM
|
|
Le Chaud Lapin wrote:
> Actually I am not mixing up anything.
>
> Of every principle I have ever learned about science and engineering,
> this one would definitely be in the top 15. I firmly believe that it
> is nothing short of a cardinal sin to promote this point of view.
> Note that I am not saying that we simply have a difference of opinion
> here. This is _fundamental_, in the most extreme sense. That's why I
> alluded the the Earth flat/round argument. There is something going
> on here were you have two groups of people, one large, one smaller,
> with diametrically opposed points of views, and they cannot both be
> right.
Sorry, but I cannot follow you on this tangent. This is about a highly
subjective coding convention issue, i.e. whether passing modifiable
arguments by pointer really makes code more intuitive and easier to
understand, and all I said is that I doubt this because it is easy to be
misled. In short, the issue is human perception. How you can possibly
contrive a Copernican rift from this is beyond me.
--
Gerhard Menzl
Non-spammers may respond to my email address, which is composed of my
full name, separated by a dot, followed by at, followed by "fwz",
followed by a dot, followed by "aero".
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Gerhard
|
1/19/2009 2:12:09 PM
|
|
Balog Pal wrote:
> "Maxim Yegorushkin" <maxim.yegorushkin@gmail.com>
>>> void Func(SomeObject* obj) // must check for null args usually
>>> {
>>> assert(null != obj)
>>> // do something
>>> }
>> It is going to dump core on NULL pointer with or without the
>> assertion. This assertion is plain useless.
>
> Says what?
> The standard states dereferencing NULL pointer is undefined behavior. So
> anything can happen.
>
> On most modern unix systems, in practice, it is really dumping core or just
> terminates the process via SIGSEGV, yet it is only one of a zillion possible
> outcomes.
> Just an example: on WIN32, using the not most recent MSVC NULL-access
> generates a SOH exception. And if you have a catch(...) block upper,
> execution gets there.
>
> While failed assert does a deterministic and sure call to abort(), you can
> rely on.
Plus, assert gives you the file and line of the violation.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/19/2009 2:19:00 PM
|
|
On Jan 18, 4:50 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> On Jan 15, 6:08 pm, Marsh Ray <marsh...@gmail.com> wrote:
>
[...]
> > std::swap<int>(a, b); // Probably pretty darn efficient.
>
> It as efficient as swap_c_style. See below.
>
> > swap_c_style(&a, &b);
>
> > The implementation of swap_c_style has to either check both args for
> > null, or produce undefined behavior over its domain of input values.
>
> No checks necessary, it should just dump a core file for your
> examination.
Maybe there is a time and place to just go ahead and dereference that
pointer, I mean, what would you do anyway, die with "internal error
1623847" message? So maybe your compiler and target guarantee a good
dump file. And there's a good way to get that dump back to the
developers. And your customers are cool with that (no sensitive data
in it, they have connectivity, don't mind your app crashing and
filling up the disk with core files, etc).
So if the stars align right, you get a nice crash dump landing back in
your (boss's) lap (with your code right at the top of the stack).
Still, doesn't seem like a great way to get a reputation as a
developer of quality code.
If you're not so lucky that day, well:
http://www.google.com/search?q=null+pointer+exploit
Or you could just use references.
> > The compiler probably has a harder job optimizing away the case where
> > (*a == *b), too.
>
> Why would it?
Because:
1. The domain of a pointer type includes this value called 'null',
whereas references do not.
2. Pointer types allow arithmetic, whereas references do not.
These possibilities require a higher level of sophistication on the
part of the compiler to make this particular optimization.
Sometimes it won't even be possible. Like if swap_c_style is called
from code that's compiled (and even linked) separately?
> A reference argument to a function is the same as a
> pointer argument on the binary level.
On some, maybe even most, compilers. But not necessarily in general.
> Let's compare [...]
> Can you spot the difference?
Sure, some compilers will generate identical code for some examples.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/19/2009 2:19:01 PM
|
|
Le Chaud Lapin wrote:
> On Jan 14, 12:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
> wrote:
>> Maxim Yegorushkin wrote:
>>> The reason for passing output parameters by pointer is that the
>>> ampersand at the call site gives you a hint that something is done to
>>> that argument.
>> I have always found this to be a bogus argument, and it is really
>> surprising me that it still keeps getting repeated after years of
>> debate. Consider:
>> ...
>> foo* myfoo;
>> ...
>> mangle(myfoo); // whoa! no ampersand!
>> }
>>
>> Relying on ampersands to signal argument modification (and their absence
>> to signal the opposite) gives a false sense of security because you get
>> the wrong signal when the argument is already of pointer type. The
>> convention, even if rigidly being adhered to, does not relieve you from
>> checking the documentation of the called function.
>
> I smell a serious breach of semantic regularity here. :)
>
> I have written extensively about the fact that pointers are not the
> things that they point to, but 1st-class bona-fide objects, scalar
> objects in their own right. So technically speaking, "technically" in
> the sense that the Earth is round and not flat, Maxim's point of view
> is not only correct, but this other point of view is dangerous, IMO.
>
Yes, pointers are not what they point to. (And I remeber well how long
it took me in college to understand pointers vs. values so you are right
to insist in making a distinction.)
However, I think this is irrelevant to the discussion if using pointers
as out arguments helps in reading the code.
Example:
-- header --
void pf1(const T* o);
void pf2( T* o);
void rf1(const T& o);
void rf2( T& o);
-- code using it --
T* pobj = ...
pf1(pobj); // may not modify pobj, but may modify
// obj pointed to *unless* the signature
// spec. a const pointer => Need to look
// at declaration
pf2(pobj); // same as for pf1 ... I need to know the
// declaration. Otherwise I only know that this function
// does not modify the pointer, which most likely
// I don't care about.
rf1(*pobj); // hm. so i know that this function will certainly
// not modify pobj, but it may well modify
// the object pointed to (need to check)
....
-- --
So you see. Just because the input to a function is a pointer doesn't
tell me anything other that this pointer is not modified. This does not
really help me when I want to know what the function does with it's
arguments.
cheers,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/19/2009 2:24:43 PM
|
|
On Jan 14, 12:42 pm, David Abrahams <d...@boostpro.com> wrote:
> Don't worry, the validity of the hot bunny's statement is completely
> dependent on implementation details of the particular compiler in
> question.
You're right, of course, but I always qualify that my assertion:
"Right now, in the year [year of assertion], on Microsoft Windows OS's
and very likely others, making destructor virtual solves the inter-
module new/delete, DLL/EXE, heap-mismatch problem."
Each time I discuss this statement with fellow engineers, I muse
serendipitously about its possible reasons until I arrived at the
conclusion that, though it is conceivable that the implementation
might be otherwise, it is most-likely not, as the way it is makes much
more sense to compiler-writer, all things considered.
Unfortunately, I do not have the capacity/context of compiler-writer,
so I forget the reasons almost as soon as I discover them, each time. :
(
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/21/2009 12:54:48 PM
|
|
On Jan 14, 12:37 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 14 jan, 01:19, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>
> > Here is example of big-integer division code that could be buried deep
> > within a cryptographic operation:
>
> > Integer operator / (const Integer ÷nd, const Integer &divisor)
> > {
> > Integer quotient, remainder;
> > if (!Integer::divide (dividend, divisor, quotient, remainder))
> > throw Integer::DIVISION_BY_ZERO;
> > return quotient;
>
> > }
>
> > Integer::divide() returns true for success, false for failure, but
> > operator / would lose all its beauty if it had to return an error:
>
> Why not throw directly from Integer::divide?
> You could also return a tuple (here, a pair) rather than taking
> objects to modify by reference.
>
> This would likely result in more efficient code, for a number of
> reasons.
Hmm...I thought I had a reason, but after checking and rechecking my
Integer.cpp file, I cannot find one, so thanks...I will do just
that. :)
> > For example,
> > millions, if not billions, of lines of C++ have been written, and we
> > still do not have obviously fundamental classes like hierarchies and
> > associative hierarchies.
>
> A recursive variant might provide the kind of thing you're looking
> for. See Boost.Variant.
> This is usually how trees are handled in high-level languages, be it
> functional ones (where the variant is explicit) or dynamic ones (where
> the variant is implicit, it's the dynamic typing).
I have several templated hierarchy classes, with operations and O(n)
that one would reasonable expect: not spectactular, but O[log(n)]
where it matters.
> Property trees are also a nice way to deal with trees. (You associate
> a path in a tree with a value, quite useful for hierarchical
> configuration files)
> You might want to look at Boost.PropertyTree.
That would be...
typedef Associative_Monarchy<String<>, Associative_Set<String<>,
String<> > > PropertyTree;
PropertyTree::Path path;
.... in the my model.
I agree that one can get very close to XML-like internal data
structures very quickly with these types of containers.
And the reachability provide by combining such primitives, along with
the regular list<>, set<>, map<>, etc., is staggering.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/21/2009 12:54:49 PM
|
|
On Jan 19, 1:58 am, David Abrahams <d...@boostpro.com> wrote:
> I agree that this is an extremely important idea in general, and for
> C/C++ programmers in particular (see Stepanov on Regular Types).
> However, most other languages I've encountered either have no value
> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
> Isn't it strange that such a fundamental principle is inaccessible to
> programmers in those languages?
Yes, very strange. This is why I think the knowledge barrier for
computer scientists should be pushed all the way down to the point of
transistors, working back up to the language:
1. transistor
2. gate
3. combinatorial device
4. bi-stable device
5. register
6. FSM (CPU + RAM) [study stack and call frames, interrupts, VM, etc.]
7. Bootstrap loader
8. Compiler
9. Language
This sequence is readily accessible, step-by-step. If one understands
it, one will never become confused by pointers, references,
indirection of any kind...and we will never find ourselves faced with
the awkward task of trying to explain why a pointer is not a
reference.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/21/2009 12:54:51 PM
|
|
On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>
> > if (arg < 0)
> > perror ("Negative value passed to function when positive value
> > expected!!!"); // silly, IMO :)
>
> They simply don't know about assert(arg >= 0).
> Making arg unsigned could also have been a solution (or not).
I think it is. This goes along with the philosophy that part of being
a good engineer is structuring the system so that certain awkward
questions never need be asked:
Q: What happens when value of this inherently positve argument is
negative? Should I assert?
A: You should not have declared it int. Make it unsigned int.
Q: How do I return an error code from a constructor if exceptions are
not available:
A: You should use exceptions.
Q: 80% of my classes are abstract. I get lost in memory management?
What should I do?
A: Ease up off the polymorphism, dude! Try a little concrete for
change. Have a map<> or a set<>.
Q: I like new(). I like it so much that I new() concrete objects that
could just as well have not been auto constructed. Should I auto_ptr<>
the returned pointer to help with memory managment?
A: Ease up off the new().
Q: My 2-month old new and fancy universal deep-copy framework is not
quite right though I have exerted Hurculean effort to make it such.
What should I do?
A: Rethink the very notion of "deep copy".
Q: My 2-month old new and fancy C++ introspection framework will not
automate itself. I just know I am missing something because it happens
all the time in Java. Why won't these classes instrospect themselves?
Is C++ defective?
A: C++ is not Java or any other interpreter-assisted language. Think
very carefully about comparing C++ to something that is fundamentally
different from it. If you insist, define an object that is a
hierarchy, where each node of the hierarchy is a mapping of string to
an associative set of string to string. That's the best you're going
to get in C++.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/21/2009 12:57:15 PM
|
|
"Le Chaud Lapin" <jaibuduvin@gmail.com>
>> However, most other languages I've encountered either have no value
>> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
>
> Yes, very strange. This is why I think the knowledge barrier for
> computer scientists should be pushed all the way down to the point of
> transistors, working back up to the language:
>
> 1. transistor
> 2. gate
> 3. combinatorial device
> 4. bi-stable device
> 5. register
> 6. FSM (CPU + RAM) [study stack and call frames, interrupts, VM, etc.]
> 7. Bootstrap loader
> 8. Compiler
> 9. Language
>
> This sequence is readily accessible, step-by-step. If one understands
> it, one will never become confused by pointers, references,
> indirection of any kind...and we will never find ourselves faced with
> the awkward task of trying to explain why a pointer is not a
> reference.
Is this full seq really needed? Those who just started programming assembly
language before using something else sure will have no problems with
pointers -- or anything.
Guess the Knuth school does similar effect with Mix.
Interestingly, I'd guess if you arrive from the opposite direction, the SICP
school, it still work.
The problem is probably what Joel described in 'Perils of Javaschools'...
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/21/2009 9:54:39 PM
|
|
Le Chaud Lapin wrote:
> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>>
>>> if (arg < 0)
>>> perror ("Negative value passed to function when positive value
>>> expected!!!"); // silly, IMO :)
>>
>> They simply don't know about assert(arg >= 0).
>> Making arg unsigned could also have been a solution (or not).
Or not!
>
> I think it is. This goes along with the philosophy that part of
> being a good engineer is structuring the system so that certain
> awkward questions never need be asked:
>
> Q: What happens when value of this inherently positve argument is
> negative? Should I assert?
> A: You should not have declared it int. Make it unsigned int.
void f(unsigned Arg);
Q: So what happens if I now call f(-1)?
A: Oops, we should add an assert(Arg <= max_value).
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
|
1/21/2009 9:55:15 PM
|
|
On 21 jan, 19:57, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>
> > On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>
> > > if (arg < 0)
> > > perror ("Negative value passed to function when positive value
> > > expected!!!"); // silly, IMO :)
>
> > They simply don't know about assert(arg >= 0).
> > Making arg unsigned could also have been a solution (or not).
>
> I think it is. This goes along with the philosophy that part of being
> a good engineer is structuring the system so that certain awkward
> questions never need be asked:
>
> Q: What happens when value of this inherently positve argument is
> negative? Should I assert?
> A: You should not have declared it int. Make it unsigned int.
To be more general, functions expect some preconditions on their
arguments, and they document them.
If the preconditions of a function are not met, then the function does
not guarantee anything about what it will do : it will lead to
undefined behavior.
It thus becomes obvious that a mechanism to detect that the
preconditions were not met is nice to have in order to know the
program is correct. Some preconditions can be checked using the type
system by restricting the input arguments to their valid values, which
will perform a compile-time check.
For others the predicate is a bit more complicated, and needs to be
performed at runtime: that is what asserts are for.
So using the type system to enforce preconditions when possible is
better than asserts, of course.
But for the case of the signed/unsigned ints, there could be a reason
to prefer asserts (or not, it's really a matter of choice): they're
implicitly convertible to each other, and the implicit conversion,
while not losing information, alters its interpretation.
So I can pass -1 to a function expecting an unsigned int, and it will
work fine. But what it does is probably not what I had wanted to do.
That is entirely my fault, of course, not the fault of the function.
Yet this may require quite some work to debug; which wouldn't have
happened if the function had taken an int and done an assert.
An ideal solution would be to use types without those "dangerous"
implicit conversions, but that simply wouldn't be practical I'm
afraid.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/21/2009 9:56:51 PM
|
|
Le Chaud Lapin wrote:
> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>>
>>> if (arg < 0)
>>> perror ("Negative value passed to function when positive value
>>> expected!!!"); // silly, IMO :)
>> They simply don't know about assert(arg >= 0).
>> Making arg unsigned could also have been a solution (or not).
>
> I think it is. This goes along with the philosophy that part of being
> a good engineer is structuring the system so that certain awkward
> questions never need be asked:
Hm, I'd have a couple of comments to these questions. To me it doesn't
quite feel the answer to them is universally agreed upon by good engineers.
> Q: What happens when value of this inherently positve argument is
> negative? Should I assert?
> A: You should not have declared it int. Make it unsigned int.
Unfortunately, that's not going to help that much. Due to C and C++'s
proneness to automatically convert any integral with a pulse to any
other integral with a pulse, unsigned is not much useful as a model of
natural numbers. (I owe understanding of this fact to Gabriel Dos Reis,
and it was one of the latest things I'd learned about C++, which may be
evidence for its subtlety.)
> Q: How do I return an error code from a constructor if exceptions are
> not available:
> A: You should use exceptions.
That's negating the hypothesis :o).
> Q: My 2-month old new and fancy universal deep-copy framework is not
> quite right though I have exerted Hurculean effort to make it such.
> What should I do?
> A: Rethink the very notion of "deep copy".
Funny you should say that. I've seen a video on google from a speaker at
Adobe that promoted the notion that all values should be deep copyable,
and that there should be no aliasing. Does anyone know a pointer to that
talk? It was called "Defining good values" or something like that, but I
couldn't find it again.
> Q: My 2-month old new and fancy C++ introspection framework will not
> automate itself. I just know I am missing something because it happens
> all the time in Java. Why won't these classes instrospect themselves?
> Is C++ defective?
> A: C++ is not Java or any other interpreter-assisted language. Think
> very carefully about comparing C++ to something that is fundamentally
> different from it. If you insist, define an object that is a
> hierarchy, where each node of the hierarchy is a mapping of string to
> an associative set of string to string. That's the best you're going
> to get in C++.
Interpreted has nothing to do with it. It's about introspection, of
which Java has more than C++. I see nothing in C++ that makes it
fundamentally opposed to introspection capabilities.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/21/2009 9:59:10 PM
|
|
On 22 jan, 04:59, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:
> Funny you should say that. I've seen a video on google from a speaker at
> Adobe that promoted the notion that all values should be deep copyable,
> and that there should be no aliasing.
Isn't that universally agreed?
A value needs to have value semantics, which means deep copy.
If you have shallow copy, what you're dealing with is pointers, which
have reference semantics.
The obvious advantage of value semantics is that there is no aliasing,
which makes the code much more robust.
And the thing with C++ is that is is one of the very few languages
that actually allow value semantics.
That is why I personally don't use smart pointers; they still have
reference semantics.
Having the same kind of functionality as smart objects is much better
in my opinion. Even sharing is possible if the object is const.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/23/2009 5:18:09 AM
|
|
Mathias Gaunard wrote:
[...]
> For others the predicate is a bit more complicated, and needs to be
> performed at runtime: that is what asserts are for. [...]
I'm not convinced this is good advice. Asserts are for sanity checks.
They're for making sure your code is doing what you think it's doing.
Asserts are not for checking whether a function is being passed an
invalid value. A function--especially a library function--needs to
explicitly test for invalid values and signal an appropriate error, not
just abort.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/23/2009 5:18:41 AM
|
|
On 22 jan, 04:54, "Balog Pal" <p...@lib.hu> wrote:
> "Le Chaud Lapin" <jaibudu...@gmail.com>
> > 1. transistor
> > 2. gate
> > 3. combinatorial device
> > 4. bi-stable device
> > 5. register
> > 6. FSM (CPU + RAM) [study stack and call frames, interrupts, VM, etc.]
> > 7. Bootstrap loader
> > 8. Compiler
> > 9. Language
>
> Is this full seq really needed? Those who just started programming assembly
> language before using something else sure will have no problems with
> pointers -- or anything.
Anything below a hardware description language like VHDL or Verilog is
electronics, not computer science.
As for a bootstrap loader, it's not really needed unless you're
designing your architecture or OS (and still, I did both of these in
classes and never wrote a bootstrap loader myself).
> Guess the Knuth school does similar effect with Mix.
MIX is far from anything that happens on a real machine of nowadays.
MMIX, its replacement, is also quite different from mainstream
assembly. I find it to be more like low-level C.
So it doesn't start *that* low.
> Interestingly, I'd guess if you arrive from the opposite direction, the SICP
> school, it still work.
Depends what you mean by the SICP school. That kind of course isn't
taught anymore.
You really need a lot of distance when you're used to code in high-
level dynamic languages to realize what is really going on. Starting
with Scheme and Python won't really help unless you then learn from
the bottom-up how these languages work.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/23/2009 5:18:42 AM
|
|
On 19 Jan, 20:19, Marsh Ray <marsh...@gmail.com> wrote:
> On Jan 18, 4:50 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
> wrote:
>
> > On Jan 15, 6:08 pm, Marsh Ray <marsh...@gmail.com> wrote:
>
> [...]
> > > std::swap<int>(a, b); // Probably pretty darn efficient.
>
> > It as efficient as swap_c_style. See below.
>
> > > swap_c_style(&a, &b);
>
> > > The implementation of swap_c_style has to either check both args for
> > > null, or produce undefined behavior over its domain of input values.
>
> > No checks necessary, it should just dump a core file for your
> > examination.
>
> Maybe there is a time and place to just go ahead and dereference that
> pointer, I mean, what would you do anyway, die with "internal error
> 1623847" message? So maybe your compiler and target guarantee a good
> dump file. And there's a good way to get that dump back to the
> developers. And your customers are cool with that (no sensitive data
> in it, they have connectivity, don't mind your app crashing and
> filling up the disk with core files, etc).
>
> So if the stars align right, you get a nice crash dump landing back in
> your (boss's) lap (with your code right at the top of the stack).
> Still, doesn't seem like a great way to get a reputation as a
> developer of quality code.
>
> If you're not so lucky that day, well:
> http://www.google.com/search?q=null+pointer+exploit
>
> Or you could just use references.
You are trying to solve a good problem (NULL-pointer dereference),
however, you are hacking on the leaves, rather than on the root of the
it.
The root of the problem is that pointers, being what they are, require
good skill and care. The only real solution is discipline.
> > > The compiler probably has a harder job optimizing away the case where
> > > (*a == *b), too.
>
> > Why would it?
>
> Because:
> 1. The domain of a pointer type includes this value called 'null',
> whereas references do not.
Where does it help?
It only helps with casts over a hierarchy, because a pointer cast that
adds a positive or negative delta to the address must not adjust NULL
pointer. Whereas doing the same cast using a reference destination
type does not have to check its input for NULL.
> 2. Pointer types allow arithmetic, whereas references do not.
>
> These possibilities require a higher level of sophistication on the
> part of the compiler to make this particular optimization.
>
> Sometimes it won't even be possible. Like if swap_c_style is called
> from code that's compiled (and even linked) separately?
>
> > A reference argument to a function is the same as a
> > pointer argument on the binary level.
>
> On some, maybe even most, compilers. But not necessarily in general.
References were introduced to the language to allow for operator
overloading. It's essentially a constant pointer with automatic
address-of operator application to the initialiser and automatic
dereference. There are also "reference to const can be bound to an r-
value", "reference is assumed to be non-NULL", and "no pointer
arithmetic" relaxations on top of the standard pointer semantics. But
that is about it.
In every other sense a reference is just a mere pointer.
> > Let's compare [...]
> > Can you spot the difference?
>
> Sure, some compilers will generate identical code for some examples.
Which compilers will not?
--
Max
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Maxim
|
1/23/2009 5:18:48 AM
|
|
Andrei Alexandrescu wrote:
> I've seen a video on google from a speaker at
> Adobe that promoted the notion that all values should be deep copyable,
> and that there should be no aliasing. Does anyone know a pointer to that
> talk?
Since I'm convinced that all values should be deep copyable, and that
there should be no aliasing, I am unable to send you a pointer.
Unfortunately, for legal and technical reasons, the video itself is
noncopyable.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
1/23/2009 5:18:48 AM
|
|
Le Chaud Lapin wrote:
>
> Q: What happens when value of this inherently positve argument is
> negative? Should I assert?
> A: You should not have declared it int. Make it unsigned int.
There have been lots and lots of discussion about this, and I feel
I'm opening that can of worms again, but... an unsigned parameter
doesn't prevent you from passing a negative value. Unsigned in C or
C++ is not "restrictive" in any sense; it just provides an alternate
"view" or "interpretation" of the underlying data and operations.
the valid range for signed
bang! ,-----------------------. bang!
3-bit signed: ~~~~~~~~~~~ -4 -3 -2 -1 0 +1 +2 +3 ~~~~~~~~~~~
-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-
3-bit unsigned: 0 +1 +2 +3 0 +1 +2 +3 0 +1 +2 +3 0 +1 +2 +3
Signed means a finite strip on which you'll be doomed if you walk
past the ends, and unsigned means that the ends are put together
so that the strip forms a ring from which you can never fall off.
--
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
|
1/23/2009 5:19:39 AM
|
|
"Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
news:90e9f907-b5cc-42a6-b2b6-23d4c4aa1c87@q9g2000yqc.googlegroups.com...
> Q: How do I return an error code from a constructor if exceptions are
> not available:
> A: You should use exceptions.
A: Don't write constructors that can cause errors. Constructors are mostly
for trivial initialization only. Don't use RAII everywhere: RAII is for
simple little classes like a lock class that will ensure that the lock will
be released when a scope is exited. RAII is not for something like "class
MyApplicationProgram" that launches a chain-reaction of constructors on
instantiation. If something is procedural, code it up that way rather than
shoe-horning everything into an OO extremism. Have more than just a hammer
in your toolbox or else everything will look like a nail.
(Being "a bit" facetious).
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/23/2009 5:21:46 AM
|
|
On Jan 21, 9:59 pm, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> [...] I see nothing in C++ that makes it
> fundamentally opposed to introspection capabilities.
Type erasure and "You don't pay for what you don't use".
How often do we see people wanting "C++ without RTTI", even when its
overhead is pretty darn minimal. I'd be amazed if there was an
implementation of a Java-style introspection facility that would be
complete enough to satisfy those wanting such a thing and lightweight
enough for those who don't. It wouldn't be the first time C++ has
amazed me (in a good way of course) though!
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/23/2009 11:37:32 AM
|
|
On 2009-01-22 04:55, Bo Persson wrote:
> Le Chaud Lapin wrote:
>> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>>> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>>>
>>>> if (arg < 0)
>>>> perror ("Negative value passed to function when positive value
>>>> expected!!!"); // silly, IMO :)
>>>
>>> They simply don't know about assert(arg >= 0).
>>> Making arg unsigned could also have been a solution (or not).
>
> Or not!
>
>>
>> I think it is. This goes along with the philosophy that part of
>> being a good engineer is structuring the system so that certain
>> awkward questions never need be asked:
>>
>> Q: What happens when value of this inherently positve argument is
>> negative? Should I assert?
>> A: You should not have declared it int. Make it unsigned int.
>
> void f(unsigned Arg);
>
> Q: So what happens if I now call f(-1)?
> A: Oops, we should add an assert(Arg <= max_value).
I assume that max_value == numeric_limits<unsigned int>::max(). The
problem with this is of course if max_value is a valid argument to f().
As pointed out by many C++'s habit of freely converting between integer
types can cause a number of problems for those caught unaware.
--
Erik Wikstr�m
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
UTF
|
1/23/2009 11:42:31 AM
|
|
On 19 Jan., 21:24, "Martin T." <0xCDCDC...@gmx.at> wrote:
> Example:
> -- header --
> void pf1(const T* o);
> void pf2( T* o);
> void rf1(const T& o);
> void rf2( T& o);
>
> -- code using it --
> T* pobj = ...
>
> pf1(pobj); // may not modify pobj, but may modify
> // obj pointed to *unless* the signature
> // spec. a const pointer => Need to look
> // at declaration
> rf1(*pobj); // hm. so i know that this function will certainly
> // not modify pobj, but it may well modify
> // the object pointed to (need to check)
No. Check out the following types:
1. "const T*" is a pointer to a const T
2. "T const*" is a pointer to a const T
In the above cases (which are equivalent) you are not allowed to alter
the object of type T within the function. But you can alter the local
copy of the pointer. Still, 'pobj' is not altered because the pointer
is passed by pass-by-value.
3. "T* const" is a const pointer to a T
This is a pointer to an object of type T. The function may alter this
object. The function can't alter the local copy of the pointer itself
because it is declared const.
4. "const T* const" is a const pointer to a const T
5. "T const* const" is a const pointer to a const T
This is a combination of both const qualifiers (again, 4 and 5 are
equivalent). The function can't alter the object of type T (unless you
count const_cast) and can't alter the local copy of the pointer to the
T object because the pointer is also const.
For references it's similar. The only difference is that references
are intrinsically const (the references itself -- not necessarily the
objects they refer to!) and that you don't need to take the address
and use the dereference operator.
6. "const T&" is a reference to a const T
7. "T const&" is a reference to a const T
The function can use the reference to access the original object of
type T. But it is not allowed to change it.
> So you see. Just because the input to a function is a pointer doesn't
> tell me anything other that this pointer is not modified. This does not
> really help me when I want to know what the function does with it's
> arguments.
It does. Well, the argument would be the pointer which is copied. But
the pointer has a type: X*. and X may be const or not in which case
the function can or cannot modify the object pointed to.
Cheers!
SG
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
SG
|
1/23/2009 11:45:23 AM
|
|
On 23 jan, 12:18, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> In every other sense a reference is just a mere pointer.
void bar(int*);
void bar2(int&);
int main()
{
int foo[42] = {0};
bar(&foo[0]);
bar2(foo[0]);
}
The call to bar2 may be optimized to bar2(0);, but the call to bar may
not be optimized in any way. (foo can be made global, but that's it).
I'm not sure the standard allows this, but if it doesn't, it should.
Unfortunately, as far as I know, no compiler makes that kind of
optimizations.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/23/2009 1:00:46 PM
|
|
Mathias Gaunard wrote:
> [...]
> And the thing with C++ is that is is one of the very few languages
> that actually allow value semantics.
This is either not true, or true only for a very misleading notion of
"value semantics."
Earlier, David Abrahams footnoted:
"...except for immutable objects, for which value semantics are
indistinguishable from reference semantics."
This is incorrect. 'Pure' functional programming languages have value
semantics. They do not have reference semantics despite the fact that
they may (and typically do) implement data structures by way of
pointers. Pointers simply allow for more efficient construction and
manipulation of certain kinds of data structures.
In Scheme (and I presume LISP) the fundamental data structure is the
pair, by which lists are constructed (and by which LISP gets its name).
The second element of the pair is typically a pointer to another pair,
whose second element is typically a pointer to another pair, whose...
It just so happens that in Scheme (and I presume LISP) these pairs allow
for destructive update of their contents, thus giving them reference
semantics. (Ironically, compilers can 'unpointerfy' a list as an
optimization technique when reference semantics aren't used.)
In contrast, Standard ML references are explicit. (They're constructed
with the keyword 'ref'.) Everything else in SML has value semantics
(with the qualification that I/O has side effects of course).
Scheme and LISP may allow for reference semantics, but it is possible
(and usually desireable) to adopt a functional programming style whereby
destructive updates are rare to non-existent in one's code. This does
not at all mean avoiding the use of pointers.
The point is that reference semantics implies the use of pointers as an
implementation detail, but value semantics and the use of pointers are
not mutually exclusive.
Bringing this all back to C++...
> That is why I personally don't use smart pointers; they still have
> reference semantics.
When a C++ programmer talks about value semantics and "deep copy," what
they're too often really talking about is a memory management technique.
C++ is different from the above languages not in its support of "value
semantics," but in its lack of support for automatic memory management.
(More than one C++ programmer has claimed that C++'s lack of GC has
little effect on the way they program. The importance of reference
versus value semantics as a design decision in C++ programs is one of
many reasons I don't take this claim too seriously.)
I use smart pointers all the time in situations where reference
semantics are never used, exactly because they make a deep copy unnecessary.
(Again, none of this should be construed as advocating the addition of
GC to C++.)
[...]
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/23/2009 1:03:48 PM
|
|
Marsh Ray wrote:
> On Jan 21, 9:59 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> [...] I see nothing in C++ that makes it
>> fundamentally opposed to introspection capabilities.
>
> Type erasure and "You don't pay for what you don't use".
>
> How often do we see people wanting "C++ without RTTI", even when its
> overhead is pretty darn minimal. I'd be amazed if there was an
> implementation of a Java-style introspection facility that would be
> complete enough to satisfy those wanting such a thing and lightweight
> enough for those who don't. It wouldn't be the first time C++ has
> amazed me (in a good way of course) though!
>
> - Marsh
>
>
Compile-time introspection has no upfront cost. Using it, dynamic
introspection can be enabled for certain types as needed. By the way,
what is the status of C++ XTI?
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/23/2009 1:05:11 PM
|
|
on Wed Jan 21 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
> Le Chaud Lapin wrote:
>> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>>> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>>>
>>>> if (arg < 0)
>>>> perror ("Negative value passed to function when positive value
>>>> expected!!!"); // silly, IMO :)
>>> They simply don't know about assert(arg >= 0).
>>> Making arg unsigned could also have been a solution (or not).
>>
>> I think it is. This goes along with the philosophy that part of being
>> a good engineer is structuring the system so that certain awkward
>> questions never need be asked:
Brilliantly stated!
> Hm, I'd have a couple of comments to these questions. To me it doesn't
> quite feel the answer to them is universally agreed upon by good engineers.
That's true, but even good engineers can be wrong :-)
>> Q: What happens when value of this inherently positve argument is
>> negative? Should I assert?
>> A: You should not have declared it int. Make it unsigned int.
>
> Unfortunately, that's not going to help that much. Due to C and C++'s
> proneness to automatically convert any integral with a pulse to any
> other integral with a pulse, unsigned is not much useful as a model of
> natural numbers. (I owe understanding of this fact to Gabriel Dos Reis,
> and it was one of the latest things I'd learned about C++, which may be
> evidence for its subtlety.)
It doesn't help that much for overall system reliability if you're
trying to make it resilient against programmer errors, but personally I
think that approach is a dead end. Systems that try to be resilient
quickly become messy and unmaintainable due to the extra resiliency
code, which can almost never be properly tested. Rather, I prefer to
concentrate on making it less likely that programmer errors will occur,
and one way to do that is to build as much information about
preconditions as possible into the parameter types.
Using unsigned pushes the question to the boundary between the function
and its caller rather than allowing the question to occur inside the
function where it complicates code. Callers of functions already need
to understand the relationship between argument and parameter types and
watch out for narrowing conversions (which occur even with signed
types), so it doesn't make for a new point of unreliability.
>> Q: How do I return an error code from a constructor if exceptions are
>> not available:
>> A: You should use exceptions.
>
> That's negating the hypothesis :o).
Nice trick, eh?
>> Q: My 2-month old new and fancy universal deep-copy framework is not
>> quite right though I have exerted Hurculean effort to make it such.
>> What should I do?
>> A: Rethink the very notion of "deep copy".
Nice!
> Funny you should say that. I've seen a video on google from a speaker
> at Adobe that promoted the notion that all values should be deep
> copyable,
Well, not exactly. The video (below) lays out an approach to
programming with _values_. I agree that we should rethink the notion of
"deep copy." There is only one notion of "copy" for _values_; you
simply can't have shallow and deep versions if you're going to preserve
equational reasoning. The stuff that's part of the value gets copied
(modulo implementation sharing due to immutability or COW -- you still
have a logical copy). But that's one of those things that none of the
other GC'd everything-is-a-reference languages seem to understand.
> and that there should be no aliasing.
You can have implementation aliasing but no logical aliasing.
> Does anyone know a pointer to that talk? It was called "Defining good
> values" or something like that, but I couldn't find it again.
http://my.adobe.acrobat.com/p53888531/
It's a great talk; I recommend it to everyone.
Also download the pdf at
http://stlab.adobe.com/wiki/index.php/Papers_and_Presentations
>> Q: My 2-month old new and fancy C++ introspection framework will not
>> automate itself. I just know I am missing something because it happens
>> all the time in Java. Why won't these classes instrospect themselves?
>> Is C++ defective?
>> A: C++ is not Java or any other interpreter-assisted language. Think
>> very carefully about comparing C++ to something that is fundamentally
>> different from it. If you insist, define an object that is a
>> hierarchy, where each node of the hierarchy is a mapping of string to
>> an associative set of string to string. That's the best you're going
>> to get in C++.
>
> Interpreted has nothing to do with it. It's about introspection, of
> which Java has more than C++. I see nothing in C++ that makes it
> fundamentally opposed to introspection capabilities.
I agree with you there, Andrei.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/23/2009 1:09:28 PM
|
|
on Wed Jan 21 2009, Le Chaud Lapin <jaibuduvin-AT-gmail.com> wrote:
> On Jan 14, 12:42 pm, David Abrahams <d...@boostpro.com> wrote:
>> Don't worry, the validity of the hot bunny's statement is completely
>> dependent on implementation details of the particular compiler in
>> question.
>
> You're right, of course, but I always qualify that my assertion:
>
> "Right now, in the year [year of assertion], on Microsoft Windows OS's
> and very likely others, making destructor virtual solves the inter-
> module new/delete, DLL/EXE, heap-mismatch problem."
>
> Each time I discuss this statement with fellow engineers, I muse
> serendipitously about its possible reasons until I arrived at the
> conclusion that, though it is conceivable that the implementation
> might be otherwise, it is most-likely not, as the way it is makes much
> more sense to compiler-writer, all things considered.
I believe you can create the problem on Linux when you get into things
like this:
http://mail.python.org/pipermail/python-dev/2002-May/024075.html
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/23/2009 1:10:39 PM
|
|
Maxim Yegorushkin wrote:
> References were introduced to the language to allow for operator
> overloading. It's essentially a constant pointer with automatic
> address-of operator application to the initialiser and automatic
> dereference. There are also "reference to const can be bound to an r-
> value", "reference is assumed to be non-NULL", and "no pointer
> arithmetic" relaxations on top of the standard pointer semantics. But
> that is about it.
>
> In every other sense a reference is just a mere pointer.
>
That is true in Java but I think it is an over simplification in C++.
Java references are pointers, C++ references are commonly implemented as
pointers but their semantics are markedly different from C++ pointers.
Just consider the effect of take the address of a pointer versus taking
the address of a reference. In addition, note that you can have a
pointer to a pointer (and it is a new type) but you cannot have either a
reference to a reference or a pointer to reference.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Francis
|
1/23/2009 1:13:36 PM
|
|
On 23 jan, 12:18, Thant Tessman <thant.tess...@gmail.com> wrote:
> I'm not convinced this is good advice. Asserts are for sanity checks.
> They're for making sure your code is doing what you think it's doing.
> Asserts are not for checking whether a function is being passed an
> invalid value. A function--especially a library function--needs to
> explicitly test for invalid values and signal an appropriate error, not
> just abort.
If you test invalid values and raise an error, then there is no
precondition involved, because you don't invoke undefined behaviour if
you do provide these values.
Think of the operator[] of std::vector as an example. The precondition
is that the index must be between 0 and the vector size minus 1. If
you fail to satisfy that condition, you invoke undefined behaviour.
If your standard library provides a debug mode, it will assert that
this precondition is met in that mode.
A precondition is for something that may NEVER happen. And thus,
checks should only exist in debug mode and the error should not be
recoverable.
If something SHOULDN'T happen, but may well happen, then you have to
check the values and raise an error.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/23/2009 1:13:37 PM
|
|
Tony wrote:
> "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
> news:90e9f907-b5cc-42a6-b2b6-23d4c4aa1c87@q9g2000yqc.googlegroups.com...
>
>> Q: How do I return an error code from a constructor if exceptions
>> are not available:
>> A: You should use exceptions.
>
> A: Don't write constructors that can cause errors. Constructors are
> mostly for trivial initialization only. Don't use RAII everywhere:
> RAII is for simple little classes like a lock class that will
> ensure that the lock will be released when a scope is exited. RAII
> is not for something like "class MyApplicationProgram" that
> launches a chain-reaction of constructors on instantiation. If
> something is procedural, code it up that way rather than
> shoe-horning everything into an OO extremism. Have more than just a
> hammer in your toolbox or else everything will look like a nail.
>
> (Being "a bit" facetious).
>
Yeah, a bit. :-)
If you cannot initialize the application properly, what do you do? Run
half the application?
If you don't have to request all the resources during construction,
don't. If you have to, you have to, and the only option is failing.
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
|
1/24/2009 9:16:47 PM
|
|
Erik Wikstr�m wrote:
> On 2009-01-22 04:55, Bo Persson wrote:
>> Le Chaud Lapin wrote:
>>> On Jan 14, 12:36 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
>>>> On 14 jan, 01:23, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
>>>>
>>>>> if (arg < 0)
>>>>> perror ("Negative value passed to function when positive
>>>>> value expected!!!"); // silly, IMO :)
>>>>
>>>> They simply don't know about assert(arg >= 0).
>>>> Making arg unsigned could also have been a solution (or not).
>>
>> Or not!
>>
>>>
>>> I think it is. This goes along with the philosophy that part of
>>> being a good engineer is structuring the system so that certain
>>> awkward questions never need be asked:
>>>
>>> Q: What happens when value of this inherently positve argument is
>>> negative? Should I assert?
>>> A: You should not have declared it int. Make it unsigned int.
>>
>> void f(unsigned Arg);
>>
>> Q: So what happens if I now call f(-1)?
>> A: Oops, we should add an assert(Arg <= max_value).
>
> I assume that max_value == numeric_limits<unsigned int>::max(). The
> problem with this is of course if max_value is a valid argument to
> f(). As pointed out by many C++'s habit of freely converting
> between integer types can cause a number of problems for those
> caught unaware.
If the previous version of the function was using an int parameter,
the max_value is supposedly <= numeric_limits<int>::max().
Changing the parameter type to unsigned removes the check for negative
values, but introduces one or two other problems:
Can the function now handle parameters larger than
numeric_limits<int>::max() ?
When I call it with f(-1), is it really an advantage to have the call
transformed into f(4 billion) ?
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
|
1/24/2009 9:18:05 PM
|
|
On 23 jan, 20:03, Thant Tessman <thant.tess...@gmail.com> wrote:
> Mathias Gaunard wrote:
> > [...]
> > And the thing with C++ is that is is one of the very few languages
> > that actually allow value semantics.
>
> This is either not true, or true only for a very misleading notion of
> "value semantics."
Just to clarify.
T a;
T b = a;
b.mutate();
a is not mutated -> value semantics
a is mutated -> reference semantics
Of course, when dealing with references/pointers, it is important not
to confuse mutating the object and mutating the pointer. Pointers are
values by themselves so we have to agree on what the value of an
object is on a case-by-case basis: for some it is the pointee, for
some it is the pointer.
>
> Earlier, David Abrahams footnoted:
>
> "...except for immutable objects, for which value semantics are
> indistinguishable from reference semantics."
>
> This is incorrect. 'Pure' functional programming languages have value
> semantics.
In pure functional languages, there is no state. All objects are
immutable.
So value and reference semantics are equivalent to the user.
It's just not interesting to talk about it at all then, except as
implementation strategies.
> In Scheme (and I presume LISP) the fundamental data structure is the
> pair, by which lists are constructed (and by which LISP gets its name).
> The second element of the pair is typically a pointer to another pair,
> whose second element is typically a pointer to another pair, whose...
>
> It just so happens that in Scheme (and I presume LISP) these pairs allow
> for destructive update of their contents, thus giving them reference
> semantics. (Ironically, compilers can 'unpointerfy' a list as an
> optimization technique when reference semantics aren't used.)
So you say it has reference semantics, but if it is proven never to be
mutated, the implementation may fall back to value semantics.
Well, that's exactly what has been said: they're equivalent if there
is no mutability, so it doesn't change anything to the visible
observable behaviour.
I don't know enough about lisp to talk about it however.
> In contrast, Standard ML references are explicit. (They're constructed
> with the keyword 'ref'.) Everything else in SML has value semantics
> (with the qualification that I/O has side effects of course).
You're forgetting records with mutable fields (which are the thing
closest to C++ objects).
a ref is actually the same as a record with a single mutable field.
type t = {mutable value : int};;
let a = {value = 0} in
let b = a in
b.value <- 1;
print_int a.value;; // prints 1
Of course, the let/in syntax makes it obvious a and b are really
aliases for the same thing: {value = 0}.
ML doesn't really have variables like C++.
Since there is nothing else in ML that can be mutable (except for
syntactic sugar in for loops I guess), talking of value vs reference
semantics doesn't make much sense for these.
By the way, I find ML comparison to be fairly weird. = means
structurally equal and == means physically equal.
Here, we have a = b and a == b.
However, if b was defined as to be an alias of the same thing as a,
but not as an alias of a, we would have a = b but not a == b.
== really exhibits an implementation detail IMHO. I would personally
find ML more coherent if all the same values had the same address,
since we should be aliasing immutable symbols in the first place with
let/in. Heh, that'd almost be Haskell.
> Scheme and LISP may allow for reference semantics, but it is possible
> (and usually desireable) to adopt a functional programming style whereby
> destructive updates are rare to non-existent in one's code. This does
> not at all mean avoiding the use of pointers.
>
> The point is that reference semantics implies the use of pointers as an
> implementation detail, but value semantics and the use of pointers are
> not mutually exclusive.
Copy-on-write is typically the kind of implementation for value
semantics that uses reference semantics with GC or similar as an
implementation detail.
One contra-argument against COW is simply that if you copy, it's
because you need to, so you might as well do so eagerly. Implicit
sharing (of which COW is a form) is also very useful when you share
only partial information: but again it can be argued you can have even
better code by being explicit about it.
>
> Bringing this all back to C++...
>
> > That is why I personally don't use smart pointers; they still have
> > reference semantics.
>
> When a C++ programmer talks about value semantics and "deep copy," what
> they're too often really talking about is a memory management technique.
Indeed, using value semantics, additionally to leading to safer code,
does not require complex memory management techniques, since there is
no aliasing[1].
Personally, I find it a better alternative to avoid aliasing than the
"make everything immutable" solution, which is just too restrictive.
[1] This, of course, is only strictly true if you only pass by value.
However, passing by const-reference and passing by const-value are
semantically equivalent, so the observable behaviour is the same; plus
passing a reference to a function is usually not a problem since the
object being passed will necessarily outlive the function unless said
function is called asynchronously, which is quite specific.
> C++ is different from the above languages not in its support of "value
> semantics," but in its lack of support for automatic memory management.
C++ allows a lot of automatic memory management techniques just fine.
(the usage of the stack is one, for example)
Even precise garbage collection *can* be done, albeit non-optimally,
in standard C++ if you use special pointer types (shared_ptr cannot do
it because it takes an already allocated pointer, but another
interface could).
The thing is that C++ doesn't apply garbage collection to the whole
environment, because it is an intrusive mechanism and C++ doesn't want
to put it on people that don't need it.
> (More than one C++ programmer has claimed that C++'s lack of GC has
> little effect on the way they program. The importance of reference
> versus value semantics as a design decision in C++ programs is one of
> many reasons I don't take this claim too seriously.)
You're taking the problem the wrong way.
People use value semantics because it doesn't need a GC.
It's not because they don't have a GC that they use value semantics.
Value semantics also has other potential advantages: safer code (no
aliasing), deterministic resource management that works for any
resource, more efficient code due to locality and avoided
dereferences, etc.
> I use smart pointers all the time in situations where reference
> semantics are never used, exactly because they make a deep copy
unnecessary.
Then you shouldn't use them. Only use shared_ptr if you have shared
ownership going on.
I suppose you use shared_ptr to avoid costly copies when they are
actually not needed. The thing is, if they're not needed, they
shouldn't be done in the first place.
Thankfully, move semantics come to the rescue. Alternatively you can
use COW.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/24/2009 9:21:30 PM
|
|
On Jan 23, 5:18 am, Maxim Yegorushkin <maxim.yegorush...@gmail.com>
wrote:
> [...]
> You are trying to solve a good problem (NULL-pointer dereference),
> however, you are hacking on the leaves, rather than on the root of the
> it.
>
> The root of the problem is that pointers, being what they are, require
> good skill and care. The only real solution is discipline.
One could use that same logic to argue against the usefulness of
'const' or any number of other features provided by the type
system. So "Good skill and care" have to be considered baseline
assumptions in any serious discussion.
Experience shows that some percentage of uses of, say, strcpy/strcat()
are going to have pointer bugs. The amount of skill and care help
determine that percentage, but realistically, std::string will catch
stuff at compile that would have been bugs otherwise.
> > 1. The domain of a pointer type includes this value called 'null',
> > whereas references do not.
>
> Where does it help?
>
> It only helps with casts over a hierarchy, because a pointer cast that
> adds a positive or negative delta to the address must not adjust NULL
> pointer. Whereas doing the same cast using a reference destination
> type does not have to check its input for NULL.
If it doesn't help, it's only because doing anything interesting with
a
null pointer is undefined behavior anyway.
> > > A reference argument to a function is the same as a
> > > pointer argument on the binary level.
C++ doesn't have a 'binary level' definition of references and
pointers
like you imply, though current implementations often enough do.
But as compilers, runtimes, GCs, VMs, CPUs, etc. evolve, there's no
reason to make unnecessary assumptions about that in the future.
Consider
the amount of rework (or outright code abandonment) caused by
programmers
making unnessary assumptions about conversions between pointer and
integral types.
> References were introduced to the language to allow for operator
> overloading.
You may be on to something there.
What's inherent about "string_abc + string_def" that needs references
rather than pointers? Couldn't it have been implemented with pointers
somehow? Could the reasons for references include some useful semantic
differences perhaps? (goes and looks for copy of D&E)
> It's essentially a constant pointer with automatic
> address-of operator application to the initialiser and automatic
> dereference. There are also "reference to const can be bound to an r-
> value", "reference is assumed to be non-NULL", and "no pointer
> arithmetic" relaxations on top of the standard pointer semantics. But
> that is about it.
>
> In every other sense a reference is just a mere pointer.
Ya, other than that ... :-)
It's enough to conclude that using a reference where a reference will
do communicates more precisely to the compiler (and other programmers)
what the code is supposed to do. This would be useful even without
their syntactic conveniences.
Over and over again this has saved me, and sometimes in unforseen ways
in the future as designs and languages evolve.
Having complete and detailed type information at compile time has got
to be one of the most powerful enablers of optimizations. Giving the
compiler a less-restrictive type can never improve things.
> > > Let's compare [...]
> > > Can you spot the difference?
>
> > Sure, some compilers will generate identical code for some examples.
>
> Which compilers will not?
Beats me. There are enough reasons to favor references over pointers
at the
language level that I don't need to research specific compilers.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/24/2009 9:21:46 PM
|
|
on Fri Jan 23 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Mathias Gaunard wrote:
>
>> [...]
>> And the thing with C++ is that is is one of the very few languages
>> that actually allow value semantics.
>
> This is either not true, or true only for a very misleading notion of
"value
> semantics."
>
> Earlier, David Abrahams footnoted:
>
> "...except for immutable objects, for which value semantics are
indistinguishable from
> reference semantics."
>
> This is incorrect.
Disagreed. And, reading ahead, I don't see anything you wrote that
supports you in saying so.
> 'Pure' functional programming languages have value semantics.
And they have immutable data.
> They do not have reference semantics
My point is that as a user you can't tell. When all values are
immutable, data sharing is undetectable unless you explicitly ask about
the addresses where things are stored.
<snip lots>
> The point is that reference semantics implies the use of pointers as
> an implementation detail, but value semantics and the use of pointers
> are not mutually exclusive.
I didn't say they were. Using pointers to implement value semantics is
perfectly possible, even in the presence of mutable data. If the data
is immutable, you can have value semantics with shared data.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/24/2009 9:30:28 PM
|
|
on Fri Jan 23 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Earlier, David Abrahams footnoted:
>
> "...except for immutable objects, for which value semantics are indistinguishable from
> reference semantics."
>
> This is incorrect.
Disagreed. And, reading ahead, I don't see anything you wrote that
supports you in saying so.
> 'Pure' functional programming languages have value semantics.
And they have immutable data.
> They do not have reference semantics
My point is that as a user you can't tell. When all values are
immutable, data sharing is undetectable unless you explicitly ask about
the addresses where things are stored.
<snip lots>
> The point is that reference semantics implies the use of pointers as
> an implementation detail, but value semantics and the use of pointers
> are not mutually exclusive.
I didn't say they were. Using pointers to implement value semantics is
perfectly possible, even in the presence of mutable data. If the data
is immutable, you can have value semantics with shared data.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/24/2009 9:31:32 PM
|
|
On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>> The key is habit and consistency. When I think of a variable for the
>> number of apples in a bin, I immediately think unsigned. At no point
>> does consideration for signed enter my mind. I have been doing this
>> for almost 20 years.
>> [...]
>> With such code, one need only worry about boundary conditions, where
>> negative values can sneak in, like input from command line, etc.
>
> Or anything involving subtraction. You can't rely on (a-1) < a if a is
> unsigned.
>
You also can't rely on that with signed types. The key difference is
that with unsigned types, underflow and overflow have well-defined
results; with signed types they don't.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Pete
|
1/24/2009 9:33:57 PM
|
|
David Abrahams wrote:
> on Wed Jan 21 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:
> It doesn't help that much for overall system reliability if you're
> trying to make it resilient against programmer errors, but personally I
> think that approach is a dead end. Systems that try to be resilient
> quickly become messy and unmaintainable due to the extra resiliency
> code, which can almost never be properly tested. Rather, I prefer to
> concentrate on making it less likely that programmer errors will occur,
> and one way to do that is to build as much information about
> preconditions as possible into the parameter types.
>
> Using unsigned pushes the question to the boundary between the function
> and its caller rather than allowing the question to occur inside the
> function where it complicates code. Callers of functions already need
> to understand the relationship between argument and parameter types and
> watch out for narrowing conversions (which occur even with signed
> types), so it doesn't make for a new point of unreliability.
Meh, problem is that using unsigned in function interfaces has little
effect. Granted, saying:
void fun(unsigned x);
is a rather concise way of saying:
// You better don't pass a negative integer!!! I will consider
// small negative integers large positive numbers!!!
void fun(int x);
because the signature still allows things like:
unsigned int x = 3;
fun(x - 10);
Got integers only? No problem. "Give me your tired, your poor, your
huddled integers of any size and signedness. I'll take'em."
signed int x = 3;
fun(x - 10);
fun accepts pretty much *any* integral with a pulse (except long when
narrowing is an issue), and the code compiles flag-free. On fashionably
rare occasions, the compiler wakes from a coma and mumbles something
about potential signedness issues (most often when there aren't any
issues at all).
So while it is nice that unsigned can be put in the signature as a
concise statement of expectations, indeed that is little else than a
comment, because the compiler does little in the way of enforcing said
expectations.
One problem with unsigned is that small negative integers (which occur
frequently in code) convert automatically to large unsigned numbers.
This problem is partially offset by the fact that large unsigned numbers
are rather rare and can be properly flagged as errors (e.g. when used as
array indices). But when the unsigned is used to do some math or
allocate memory, bizarre results are easily within reach.
What we want to experiment with in D is disabling the most dangerous
conversion (int -> unsigned) and see how restrictive the resulting
conversion graph is. The true solution is to use a flow-sensitive value
range propagation analysis; that will associate with any number a
possible range at any point, which will catch many potential problems
without weeding away many correct uses. That's difficult to implement,
but I guess as soon as I'll bring up the opportunity of slashing in two
the size of any codebase, the motivation will be there :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
|
1/24/2009 9:50:19 PM
|
|
On Jan 23, 5:19 am, Seungbeom Kim <musip...@bawi.org> wrote:
> There have been lots and lots of discussion about this, and I feel
> I'm opening that can of worms again, but... an unsigned parameter
> doesn't prevent you from passing a negative value. Unsigned in C or
> C++ is not "restrictive" in any sense; it just provides an alternate
> "view" or "interpretation" of the underlying data and operations.
>
> the valid range for signed
> bang! ,-----------------------. bang!
> 3-bit signed: ~~~~~~~~~~~ -4 -3 -2 -1 0 +1 +2 +3 ~~~~~~~~~~~
> -+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-
> 3-bit unsigned: 0 +1 +2 +3 0 +1 +2 +3 0 +1 +2 +3 0 +1 +2 +3
>
> Signed means a finite strip on which you'll be doomed if you walk
> past the ends, and unsigned means that the ends are put together
> so that the strip forms a ring from which you can never fall off.
Hi Seungbeom,
This true. However, the goal of using unsigned versus signed is not to
prevent a signed value from being passed to a function expecting
unsigned.
The goal of using unsigned is to enter a new realm of thinking, where
all values that are inherently non-negative use unsigned. As it turns
out, by far, the majority of quantities in a program are naturally non-
negative. This mindset requires that the programmer do two things:
1. define parameters for inherently non-negative quantities to be
unsigned
2. define normal global and stack variables for inherently non-
negative values to be unsigned
So for example:
void foo (unsigned int);
int main ()
{
int x = -1;
foo (unsigned int); // 2^32 - 1 passed on 32-bit 2's comp machine
return 0;
}
I would never, ever, define x to be int. I must repeat: If I had to
write a 1,000,000 program, there would be no place in it, not one,
were you would catch me doing that, because I am just as predisposed
to choose unsigned int for an inherently non-negative quantity as
someone else is to choose int for that same quantity.
It requires a change in mindset to be consistent. My contention is
that not only is it possible to change the mindset and be consistent,
but the mental relief gained is far greater than the mental relief
gained by adding ASSERTs. The code looks better, and the components
actually work together better because they need not be prepared to
defending themselves from code that is fundamentally defective. By
contrast, there are some cases when a programmer might deliberately
allow a malformed value to enter his system, while remaing acutely
aware of the potential for an exception and is ready to catch it:
struct IP_Address
{
IP_Address (const char *); // throws BAD_ARGUMENT
} ;
try
{
IP_Address addr ("127.0.0.$");
}
catch (...)
{
// no big deal, I knew this could happen.
}
I guess the fundamental premise is that I control my code. I control
my system. These signed/unsigned problems never occur in my world
because I make sure they don't. All I see are a huge number of
unsigned entities all working well together.
The mental relief gained is immeasurable.
Q: Yeah...but what happens when the steam starts to rust the inside of
the engine?
A: Who said I was using a ~steam~ engine?
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/24/2009 9:58:00 PM
|
|
Mathias Gaunard wrote:
> On 23 jan, 20:03, Thant Tessman <thant.tess...@gmail.com> wrote:
>> Mathias Gaunard wrote:
>>> [...]
>>> And the thing with C++ is that is is one of the very few languages
>>> that actually allow value semantics.
>> This is either not true, or true only for a very misleading notion of
>> "value semantics."
>
> Just to clarify.
> T a;
> T b = a;
> b.mutate();
>
> a is not mutated -> value semantics
> a is mutated -> reference semantics
> [...]
Right.
>
>> Earlier, David Abrahams footnoted:
>>
>> "...except for immutable objects, for which value semantics are
>> indistinguishable from reference semantics."
>>
>> This is incorrect. 'Pure' functional programming languages have value
>> semantics.
>
> In pure functional languages, there is no state. All objects are
> immutable.
> So value and reference semantics are equivalent to the user.
No. Pure functional programming languages do not provide for reference
semantics. You cannot do what you describe above. It's not that value
semantics and reference semantics become "equivalent." It's that there
are no reference semantics. There are only values.
[...]
>> In contrast, Standard ML references are explicit. (They're constructed
>> with the keyword 'ref'.) Everything else in SML has value semantics
>> (with the qualification that I/O has side effects of course).
>
> You're forgetting records with mutable fields (which are the thing
> closest to C++ objects).
> a ref is actually the same as a record with a single mutable field.
Standard ML doesn't have records with mutable fields. I think you're
talking about OCaml. In SML, you can build a record that has a field
containing a reference, which is mutable. But the fields of the record
itself are not mutable. Only references are mutable. Only references
have reference semantics.
So apparently in OCaml, marking a field mutable gives it reference
semantics. This doesn't address the point I'm trying to make, which is
that reference semantics are a matter of SEMANTICS, not implementation.
Value SEMANTICS and reference SEMANTICS are by definition different
things. They are concepts that have meaning INDEPENDENT of any language
that may or may not manifest them. They don't somehow become the same
thing in pure functional programming languages.
[...]
>> I use smart pointers all the time in situations where reference
>> semantics are never used, exactly because they make a deep copy
> unnecessary.
>
> Then you shouldn't use them. Only use shared_ptr if you have shared
> ownership going on.
> I suppose you use shared_ptr to avoid costly copies when they are
> actually not needed. The thing is, if they're not needed, they
> shouldn't be done in the first place.
Huh? I use smart pointers to share *values* without the need to copy
them, exactly so I don't have to worry about "ownership." Automatic
memory management--sorry--garbage collection makes the notion of
"ownership" unnecessary.
[...]
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/25/2009 3:05:13 AM
|
|
On Jan 21, 9:55 pm, "Bo Persson" <b...@gmb.dk> wrote:
> void f(unsigned Arg);
>
> Q: So what happens if I now call f(-1)?
> A: Oops, we should add an assert(Arg <= max_value).
>
> Bo Persson
The whole premise of our argument is that there are living, breathing,
programmers who never do that. Ever.
We are at least as predisposed to use unsigned int as others are
proposed to use int. One thing we will never do is write code like
that above, because it can never happen in our system that such code
like that gets written.
I ride my motorcyle 225 mph ocassionally, sometimes with a passenger.
A crash would likely result in death. One might ask....but what will
happen if you get nervous and your hand slips, or you lean back too
far and knock the passenger off!!! The simple answer is, it's not
going to happen, because I will not let it. I don't know how else to
put it. You can write code like that above, or you can not. We
don't. :)
Then the only remaining question is, given these two habits and
mindsets, which one results in better engineering experience?
It seems to be a really hard question to answer without having fully
experienced both worlds.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/25/2009 3:05:21 AM
|
|
David Abrahams wrote:
> on Fri Jan 23 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>
>> [...]
>> [Pure functional programming languages] do not have reference semantics
>
> My point is that as a user you can't tell. When all values are
> immutable, data sharing is undetectable unless you explicitly ask about
> the addresses where things are stored.
Data sharing is NOT the same thing as reference semantics. Pure
functional programming languages don't provide reference semantics. As I
said elsewhere, value SEMANTICS and reference SEMANTICS are by
definition different things. They don't somehow become the same thing in
a pure functional programming langauge.
> > The point is that reference semantics implies the use of pointers
> > as an implementation detail, but value semantics and the use of
> > pointers are not mutually exclusive.
> I didn't say they were. Using pointers to implement value
> semantics is perfectly possible, even in the presence of mutable
> data. If the data is immutable, you can have value semantics
> with shared data.
Right. But the point is that as a user you certainly can tell whether a
language supports reference semantics or not. Pure functional
programming languages don't support reference semantics. To claim
otherwise seems to be confusing MEANING (reference semantics) with
implementation details (data sharing).
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/25/2009 3:06:08 AM
|
|
Mathias Gaunard wrote:
[...]
> Think of the operator[] of std::vector as an example. The precondition
> is that the index must be between 0 and the vector size minus 1. If
> you fail to satisfy that condition, you invoke undefined behaviour.
> If your standard library provides a debug mode, it will assert that
> this precondition is met in that mode. [...]
There is a point of view that considers "undefined behavior" to be
something to avoid in the design of a programming language.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/25/2009 3:07:21 AM
|
|
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message
news:KE00y3.t3@beaver.cs.washington.edu...
> Meh, problem is that using unsigned in function interfaces has little
> effect. Granted, saying:
>
> void fun(unsigned x);
>
> is a rather concise way of saying:
>
> // You better don't pass a negative integer!!!
Sounds like a programming language problem to me! (IAALDBNACDOHM). (I "am" a
language designer but not a compiler developer or hardware manufacturer). ;)
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/25/2009 3:08:21 AM
|
|
On Jan 23, 5:21 am, "Tony" <t...@my.net> wrote:
> "Le Chaud Lapin" <jaibudu...@gmail.com> wrote in messagenews:90e9f907-b5cc-42a6-b2b6-23d4c4aa1c87@q9g2000yqc.googlegroups.com...
>
> > Q: How do I return an error code from a constructor if exceptions are
> > not available:
> > A: You should use exceptions.
>
> A: Don't write constructors that can cause errors. Constructors are mostly
> for trivial initialization only. Don't use RAII everywhere: RAII is for
> simple little classes like a lock class that will ensure that the lock will
> be released when a scope is exited. RAII is not for something like "class
> MyApplicationProgram" that launches a chain-reaction of constructors on
> instantiation. If something is procedural, code it up that way rather than
> shoe-horning everything into an OO extremism. Have more than just a hammer
> in your toolbox or else everything will look like a nail.
Well, I do think it is OK for constructors to throw. The best example
in my project is what you might call an IP address. It can construct
itself from a string representation:
struct IP_Address
{
IP_Address (const char *); // throws bad_argument
// 64 other member functions
} ;
The internal structure of this struct is complex. Only it knows best
how to construct itself from a string. It turns out that the best way
to check a string for validity just before the constructor consumes it
is to let the constructor try to construct itself from the string and
throw if there is a problem. Otherwise, there would be double
testing: once to see if string is valid, and again during constuction.
One could argue that the external test obviates testing in the
constructor, but that would require pre-testing against strings in
every use of the constructor against a string.
Also, there are many instances where the complexity is rich-and-large,
so complex that the programmer would be wise not to think
procedurally, but let the C++ OO machinery do what it is good at. Such
an instance might occur during the serialization of a massively-
complex data structure, for which an IP_Address one one of many sub-
objects, embedded deep within the hierarchy.
Then what?
With exceptions from the constructors, nothing is lost. One simply
does what one needs to do, hope for the best, and be ready to catch
any problems.
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
1/25/2009 3:19:19 AM
|
|
"Pete Becker" <pete@versatilecoding.com> wrote in message
news:2009012416213775249-pete@versatilecodingcom...
> On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
>
>> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>>> The key is habit and consistency. When I think of a variable for the
>>> number of apples in a bin, I immediately think unsigned. At no point
>>> does consideration for signed enter my mind. I have been doing for
>>> almost 20 years.
>>> [...]
>>> With such code, one need only worry about boundary conditions, where
>>> negative values can sneak in, like input from command line, etc.
>>
>> Or anything involving subtraction. You can't rely on (a-1) < a if a is
>> unsigned.
>>
>
> You also can't rely on that with signed types. The key difference is that
> with unsigned types, underflow and overflow have well-defined results;
> with signed types they don't.
As the owner of my code, I know my code will not exceed 2 billion (!
uint32_t) objects or elements. What you describe, is the PROBLEM with trying
to write overly-general code (read: for someone else to use MyClass in any
kitchen sink). Example: I wrote the class to handle list of chat friends!
While analyzing the boundaries is important in software development, using
extremes in the way you did was absurd (unless one is in programming 101
class). You must be one of those guys that programmed in C before 1989! ;)
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/25/2009 3:19:19 AM
|
|
Andrei Alexandrescu wrote:
> void fun(unsigned x);
> Got integers only? No problem. "Give me your tired, your poor, your
> huddled integers of any size and signedness. I'll take'em."
>
> signed int x = 3;
> fun(x - 10);
> What we want to experiment with in D is disabling the most dangerous
> conversion (int -> unsigned) and see how restrictive the resulting
> conversion graph is. The true solution is to use a flow-sensitive value
> range propagation analysis; that will associate with any number a
> possible range at any point, which will catch many potential problems
> without weeding away many correct uses. That's difficult to implement,
> but I guess as soon as I'll bring up the opportunity of slashing in two
> the size of any codebase, the motivation will be there :o).
Not sure I followed all that. Do you see any issue with something like
the following?
template<class Value_type>
class only
{
template<class T>
only( T t );
public:
Value_type value;
only( Value_type v )
: value( v ) { }
};
unsigned fun(only<unsigned> n);
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
1/25/2009 12:26:55 PM
|
|
On 25 jan, 10:05, Thant Tessman <thant.tess...@gmail.com> wrote:
> No. Pure functional programming languages do not provide for reference
> semantics. You cannot do what you describe above. It's not that value
> semantics and reference semantics become "equivalent." It's that there
> are no reference semantics. There are only values.
What we've been saying, which I am going to repeat again, is that it
is irrelevant.
Languages where all data is immutable have neither reference nor value
semantics. Or they have both. That terminology is not even used, since
there is no point.
When we're talking of value semantics, we mean mutable value
semantics.
> Standard ML doesn't have records with mutable fields. I think you're
> talking about OCaml. In SML, you can build a record that has a field
> containing a reference, which is mutable. But the fields of the record
> itself are not mutable. Only references are mutable. Only references
> have reference semantics.
The point is that functional languages, where everything is immutable
by default, also provide optional mutability. And when they do so, it
is only using reference semantics.
It is not possible to have (mutable) value semantics in those
languages. Simply because this would require defining what a value is,
which requires overloading of copy, assignment, etc.
> This doesn't address the point I'm trying to make, which is
> that reference semantics are a matter of SEMANTICS, not implementation.
That's what we've been telling you, but it seems you're the one that
likes to focus more on the implementation and optimizations than on
the observable behaviour, which is what semantics really are.
> Value SEMANTICS and reference SEMANTICS are by definition different
> things. They are concepts that have meaning INDEPENDENT of any language
> that may or may not manifest them. They don't somehow become the same
> thing in pure functional programming languages.
It's "does it act like a value, or does it act like a reference?".
"It acts like both" is a perfectly valid answer if said data is
immutable.
So, if you'd prefer, we could say "immutable data exhibits both
reference and value semantics" instead of "when dealing with immutable
data, reference and data semantics are equivalent".
Honestly, I don't see any difference in the phrasing.
> Huh? I use smart pointers to share *values* without the need to copy
> them, exactly so I don't have to worry about "ownership."
Note that if the value is not mutable, then sharing is the same
(logically) as copying, like it has been told a zillion times in a few
messages.
A shared_ptr<const T> is just like a const T, in terms of semantics,
with different efficiencies. This can be extended to non-const types
by using copy-on-write (which, just to be sure you follow, is value
semantics, not reference ones).
Similarly, a flyweight<T> is just like a const T, or a
shared_ptr<const T> with yet other efficiencies. (which cannot be
extended to non-const types, since that would defeat its purpose).
Those forms of sharing are nothing but optimization techniques. So
this is really *not* what I'm talking about.
Pointers and smart pointers give you reference semantics, instead of C+
+ defaults, value semantics. shared_ptr additionally makes all
references own the value; thus allowing sharing of a provisional
mutable value among several independent or concurrent parts of the
code, which can mutate that value at any time, until all parts have
ceased to use it.
So there is really a design choice here. You chose to rely on data
sharing to organize your code and its responsibilities, with all the
hazards that incurs.
Sure, shared ownership is easier to code with in that you don't have
to worry about who owns what. This is however "loosy programming".
In C++, you don't do something just so you don't have to worry about
it. You do it for a reason.
You can express everything in terms of shared ownership, since
exclusive ownership (i.e. no aliasing) is nothing but a special case.
But when it's not needed, you shouldn't use it.
If you don't share and explicitly need those semantics, then don't
deploy the heavy mechanisms required to do so.
Just like you don't use your car to go to the other side of the road.
(bad analogy, I know)
> Automatic
> memory management--sorry--garbage collection
To be precise, garbage collection is a mean to perform automatic
memory management for arbitrarily linked data.
Reference counting with cycle detection is another mean.
Reference counting without cycle detection allows automatic memory
management for non-cyclically linked data.
The execution stack allows another form of automatic memory management
that I don't know how to phrase.
> makes the notion of
> "ownership" unnecessary.
GC is certainly no silver bullet.
You need to make sure you don't hold a reference any longer that you
ought to, otherwise the code will logically leak. Since you have to
think about that anyway, you might as well define proper ownership.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/25/2009 12:26:59 PM
|
|
On 2009-01-24 22:19:19 -0500, "Tony" <tony@my.net> said:
>
> "Pete Becker" <pete@versatilecoding.com> wrote in message
> news:2009012416213775249-pete@versatilecodingcom...
>> On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
>>
>>> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>>>> The key is habit and consistency. When I think of a variable for the
>>>> number of apples in a bin, I immediately think unsigned. At no point
>>>> does consideration for signed enter my mind. I have been doing for
>>>> almost 20 years.
>>>> [...]
>>>> With such code, one need only worry about boundary conditions, where
>>>> negative values can sneak in, like input from command line, etc.
>>>
>>> Or anything involving subtraction. You can't rely on (a-1) < a if a is
>>> unsigned.
>>>
>>
>> You also can't rely on that with signed types. The key difference is that
>> with unsigned types, underflow and overflow have well-defined results;
>> with signed types they don't.
>
> As the owner of my code, I know my code will not exceed 2 billion (!
> uint32_t) objects or elements. What you describe, is the PROBLEM with trying
> to write overly-general code (read: for someone else to use MyClass in any
> kitchen sink). Example: I wrote the class to handle list of chat friends!
>
> While analyzing the boundaries is important in software development, using
> extremes in the way you did was absurd (unless one is in programming 101
> class). You must be one of those guys that programmed in C before 1989! ;)
>
Umm, I didn't use extremes, absurd or otherwise. I responded to a
comment about how unsigned types deal with limits.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Pete
|
1/25/2009 12:28:31 PM
|
|
on Sun Jan 25 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>>> Earlier, David Abrahams footnoted:
>>>
>>> "...except for immutable objects, for which value semantics are
>>> indistinguishable from reference semantics."
>>>
>>> This is incorrect. 'Pure' functional programming languages have value
>>> semantics.
>>
>> In pure functional languages, there is no state. All objects are
>> immutable.
>> So value and reference semantics are equivalent to the user.
>
> No. Pure functional programming languages do not provide for reference
> semantics. You cannot do what you describe above. It's not that value
> semantics and reference semantics become "equivalent."
OK, granted. However, I was speaking in the context of C++, where there
is data mutation. My point, better stated, is that immutable objects
can't exhibit reference semantics.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/25/2009 12:29:44 PM
|
|
On 2009-01-25 10:05, Le Chaud Lapin wrote:
> On Jan 21, 9:55 pm, "Bo Persson" <b...@gmb.dk> wrote:
>> void f(unsigned Arg);
>>
>> Q: So what happens if I now call f(-1)?
>> A: Oops, we should add an assert(Arg <= max_value).
>>
>> Bo Persson
>
> The whole premise of our argument is that there are living, breathing,
> programmers who never do that. Ever.
>
> We are at least as predisposed to use unsigned int as others are
> proposed to use int. One thing we will never do is write code like
> that above, because it can never happen in our system that such code
> like that gets written.
>
> I ride my motorcyle 225 mph ocassionally, sometimes with a passenger.
> A crash would likely result in death. One might ask....but what will
> happen if you get nervous and your hand slips, or you lean back too
> far and knock the passenger off!!! The simple answer is, it's not
> going to happen, because I will not let it. I don't know how else to
> put it. You can write code like that above, or you can not. We
> don't. :)
I find that a particularly bad example considering the number of lethal
motorcycle accidents that occur every year. While you say that it will
never happen the truth is that it happens all the time, perhaps you to
you but it *will* happen for someone else.
Whenever you run into a situation that will never happen (i.e. this
function will never be called with a NULL-pointer) it's a good idea to
put in a check.
--
Erik Wikström
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
UTF
|
1/25/2009 12:29:46 PM
|
|
on Sat Jan 24 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
> David Abrahams wrote:
>> on Wed Jan 21 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
> wrote:
>> It doesn't help that much for overall system reliability if you're
>> trying to make it resilient against programmer errors, but personally I
>> think that approach is a dead end. Systems that try to be resilient
>> quickly become messy and unmaintainable due to the extra resiliency
>> code, which can almost never be properly tested. Rather, I prefer to
>> concentrate on making it less likely that programmer errors will occur,
>> and one way to do that is to build as much information about
>> preconditions as possible into the parameter types.
>>
>> Using unsigned pushes the question to the boundary between the function
>> and its caller rather than allowing the question to occur inside the
>> function where it complicates code. Callers of functions already need
>> to understand the relationship between argument and parameter types and
>> watch out for narrowing conversions (which occur even with signed
>> types), so it doesn't make for a new point of unreliability.
>
> Meh, problem is that using unsigned in function interfaces has little
> effect. Granted, saying:
>
> void fun(unsigned x);
>
> is a rather concise way of saying:
>
> // You better don't pass a negative integer!!! I will consider
> // small negative integers large positive numbers!!!
> void fun(int x);
Exactly.
> because the signature still allows things like:
>
> unsigned int x = 3;
> fun(x - 10);
>
> Got integers only? No problem. "Give me your tired, your poor, your
> huddled integers of any size and signedness. I'll take'em."
>
> signed int x = 3;
> fun(x - 10);
>
> fun accepts pretty much *any* integral with a pulse (except long when
> narrowing is an issue), and the code compiles flag-free. On fashionably
> rare occasions, the compiler wakes from a coma and mumbles something
> about potential signedness issues (most often when there aren't any
> issues at all).
I'm not excusing liberal signed/unsigned inter-conversions. We have to
live with those. What we don't have to live with is complicated checks
inside functions for conditions that are preventable at the function
interface boundary.
> So while it is nice that unsigned can be put in the signature as a
> concise statement of expectations, indeed that is little else than a
> comment, because the compiler does little in the way of enforcing said
> expectations.
Nor does it *ever*, since anyone can define a type with an implicit
conversion to your argument type.
> One problem with unsigned is that small negative integers (which occur
> frequently in code) convert automatically to large unsigned numbers.
> This problem is partially offset by the fact that large unsigned numbers
> are rather rare and can be properly flagged as errors (e.g. when used as
> array indices). But when the unsigned is used to do some math or
> allocate memory, bizarre results are easily within reach.
Sure.
> What we want to experiment with in D is disabling the most dangerous
> conversion (int -> unsigned) and see how restrictive the resulting
> conversion graph is.
Seems like a reasonable tack, but this thread was about what to do in
C++, not how to write a better language.
> The true solution is to use a flow-sensitive value range propagation
> analysis; that will associate with any number a possible range at any
> point, which will catch many potential problems without weeding away
> many correct uses. That's difficult to implement, but I guess as soon
> as I'll bring up the opportunity of slashing in two the size of any
> codebase, the motivation will be there :o).
Yeah, that would be super nice. You could actually do that with C++,
right?
It's too bad that it is considered outside the domain of this library:
http://student.agh.edu.pl/~kawulak/constrained_value/constrained_value/rationale.html#constrained_value.rationale.overflows
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/25/2009 12:31:30 PM
|
|
On 25 jan, 10:07, Thant Tessman <thant.tess...@gmail.com> wrote:
> There is a point of view that considers "undefined behavior" to be
> something to avoid in the design of a programming language.
The languages that really want to not have undefined behaviour just
don't disable the assert in release.
That's an efficiency loss.
Testing should ensure the check can be disabled.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/25/2009 12:32:41 PM
|
|
On 2009-01-24 22:19:19 -0500, "Tony" <tony@my.net> said:
>
> "Pete Becker" <pete@versatilecoding.com> wrote in message
> news:2009012416213775249-pete@versatilecodingcom...
>> On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
>>
>>> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>>>> The key is habit and consistency. When I think of a variable for the
>>>> number of apples in a bin, I immediately think unsigned. At no point
>>>> does consideration for signed enter my mind. I have been doing for
>>>> almost 20 years.
>>>> [...]
>>>> With such code, one need only worry about boundary conditions, where
>>>> negative values can sneak in, like input from command line, etc.
>>>
>>> Or anything involving subtraction. You can't rely on (a-1) < a if a is
>>> unsigned.
>>>
>>
>> You also can't rely on that with signed types. The key difference is that
>> with unsigned types, underflow and overflow have well-defined results;
>> with signed types they don't.
>
> As the owner of my code, I know my code will not exceed 2 billion (!
> uint32_t) objects or elements.
If you only use integers to count objects, fine. But that's a rather
small subset of the things that most programmers use them for.
> What you describe, is the PROBLEM with trying
> to write overly-general code (read: for someone else to use MyClass in any
> kitchen sink).
It's a problem, yes, and it has to be considered unless you're writing
code for a single compiler and you've allowed for its limits.
> Example: I wrote the class to handle list of chat friends!
>
> While analyzing the boundaries is important in software development, using
> extremes in the way you did was absurd (unless one is in programming 101
> class). You must be one of those guys that programmed in C before 1989! ;)
>
No, I'm one of those guys that knows more about writing portable code
than you do.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Pete
|
1/25/2009 12:49:11 PM
|
|
SG wrote:
> On 19 Jan., 21:24, "Martin T." <0xCDCDC...@gmx.at> wrote:
>> On Jan 14, 12:43 pm, Gerhard Menzl <clcppm-pos...@this.is.invalid>
>> wrote:
>>> Maxim Yegorushkin wrote:
>>>> The reason for passing output parameters by pointer is that the
>>>> ampersand at the call site gives you a hint that something is done to
>>>> that argument.
>>> I have always found this to be a bogus argument, and it is really
>>> surprising me that it still keeps getting repeated after years of
>>> debate. Consider:
>>> ...
>>> foo* myfoo;
>>> ...
>>> mangle(myfoo); // whoa! no ampersand!
>>> }
>>>
>>> Relying on ampersands to signal argument modification (and their absence
>>> to signal the opposite) gives a false sense of security because you get
>>> the wrong signal when the argument is already of pointer type. The
>>> (...)
>> Example:
>> -- header --
>> void pf1(const T* o);
>> void pf2( T* o);
>> void rf1(const T& o);
>> void rf2( T& o);
>>
> (...)
>> So you see. Just because the input to a function is a pointer doesn't
>> tell me anything other that this pointer is not modified. This does not
>> really help me when I want to know what the function does with it's
>> arguments.
>
> It does. Well, the argument would be the pointer which is copied. But
> the pointer has a type: X*. and X may be const or not in which case
> the function can or cannot modify the object pointed to.
>
The argument I was trying to make was that looking at the function
*usage*, that is:
// ...
T o;
// ...
pf1(&o); // no modf
pf2(&o); // modf possb
rf1(o); // no modf
rf2(o); // modf possb
// ...
- does *not* tell me if the data I'm working with will be modified by
the function.
Hence the initial argument that "the ampersand at the call site gives
you a hint" is - IMHO - incorrect, as you always need to look at the
function declaration to be sure.
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
1/25/2009 5:02:58 PM
|
|
On Jan 25, 10:06 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> David Abrahams wrote:
> > on Fri Jan 23 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>
> >> [...]
> >> [Pure functional programming languages] do not have reference semantics
>
> > My point is that as a user you can't tell. When all values are
> > immutable, data sharing is undetectable unless you explicitly ask about
> > the addresses where things are stored.
>
> Data sharing is NOT the same thing as reference semantics. Pure
> functional programming languages don't provide reference semantics. As I
> said elsewhere, value SEMANTICS and reference SEMANTICS are by
> definition different things. They don't somehow become the same thing in
> a pure functional programming langauge.
>
The entire point of "reference transparency" is that a user cannot
tell the difference between reference and value semantics.
Sebastian
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
wasti
|
1/25/2009 5:03:31 PM
|
|
On Jan 25, 11:05 am, Le Chaud Lapin <jaibudu...@gmail.com> wrote:
> On Jan 21, 9:55 pm, "Bo Persson" <b...@gmb.dk> wrote:
>
> > void f(unsigned Arg);
>
> > Q: So what happens if I now call f(-1)?
> > A: Oops, we should add an assert(Arg <= max_value).
>
> > Bo Persson
>
> The whole premise of our argument is that there are living, breathing,
> programmers who never do that. Ever.
>
> We are at least as predisposed to use unsigned int as others are
> proposed to use int. One thing we will never do is write code like
> that above, because it can never happen in our system that such code
> like that gets written.
-1 is just an example. You'll have an expression as an argument in the
general case, like f( a*b - c*d ). Now, if you always assert before
every integer operation that the values are in the range you expect,
that the multiplications and additions don't overflow, and that the
subtractions do not underflow, then you obviously have nothing to
worry about. But this doesn't quite lead to less assertions compared
to the signed style - the checks are the same in both worlds.
The interesting case is when you use less assertions than is needed to
formally prove that the code is OK (in the absence of cosmic rays and
bad memory). There is now a probability that the computations have
deviated from your expectations. The question is now: which style is
more likely to catch the error? Answer: a style in which f's domain is
a subset of the domain of the type used for its argument, so that f
has a chance to detect invalid values. This is not limited to signed/
unsigned, it can also apply to cases where one uses unsigned int for
arguments with a valid range for which unsigned char would suffice.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/25/2009 5:04:51 PM
|
|
On 2009-01-25 07:29:43 -0500, brangdon@cix.co.uk (Dave Harris) said:
> pete@versatilecoding.com (Pete Becker) wrote (abridged):
>> The key difference is that with unsigned types, underflow
>> and overflow have well-defined results; with signed types
>> they don't.
>
> That's an advantage for signed types, isn't it? It enables the hardware
> to check for overflow bugs and shut down the program if they happen.
Does your compiler do that? Do you know of any that do?
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Pete
|
1/25/2009 5:05:08 PM
|
|
on Sun Jan 25 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> The interesting case is when you use less assertions than is needed to
> formally prove that the code is OK (in the absence of cosmic rays and
> bad memory). There is now a probability that the computations have
> deviated from your expectations. The question is now: which style is
> more likely to catch the error? Answer: a style in which f's domain is
> a subset of the domain of the type used for its argument,
I think you mean its parameter, not its argument, right?
If f has the same domain as that of its parameter type but the
argument's domain is a superset, that's when you get the narrowing
conversion.
> so that f has a chance to detect invalid values. This is not limited
> to signed/ unsigned, it can also apply to cases where one uses
> unsigned int for arguments with a valid range for which unsigned char
> would suffice.
Oh, that's interesting. So let's take the most common case:
void transmogrify(char* buffer, IntegralType length);
What should I choose for the IntegralType? The domain of transmogrify
is 0 <= length <= SIZE_T_MAX.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/25/2009 10:41:15 PM
|
|
on Sun Jan 25 2009, wasti.redl-AT-gmx.net wrote:
> On Jan 25, 10:06 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>> David Abrahams wrote:
>> > on Fri Jan 23 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>>
>> >> [...]
>> >> [Pure functional programming languages] do not have reference semantics
>>
>> > My point is that as a user you can't tell. When all values are
>> > immutable, data sharing is undetectable unless you explicitly ask about
>> > the addresses where things are stored.
>>
>> Data sharing is NOT the same thing as reference semantics. Pure
>> functional programming languages don't provide reference semantics. As I
>> said elsewhere, value SEMANTICS and reference SEMANTICS are by
>> definition different things. They don't somehow become the same thing in
>> a pure functional programming langauge.
>>
>
> The entire point of "reference transparency" is that a user cannot
> tell the difference between reference and value semantics.
No, Mr. Tessman has a point. Semantics are defined by observable
behavior, not implementation details. If you don't allow people to ask
about the address of objects and all objects are immutable, there's no
observable data sharing, and thus no reference semantics.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/25/2009 10:41:42 PM
|
|
On Jan 26, 6:41 am, David Abrahams <d...@boostpro.com> wrote:
> on Sun Jan 25 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
> > The interesting case is when you use less assertions than is needed to
> > formally prove that the code is OK (in the absence of cosmic rays and
> > bad memory). There is now a probability that the computations have
> > deviated from your expectations. The question is now: which style is
> > more likely to catch the error? Answer: a style in which f's domain is
> > a subset of the domain of the type used for its argument,
>
> I think you mean its parameter, not its argument, right?
Yes I did.
> > so that f has a chance to detect invalid values. This is not limited
> > to signed/ unsigned, it can also apply to cases where one uses
> > unsigned int for arguments with a valid range for which unsigned char
> > would suffice.
>
> Oh, that's interesting. So let's take the most common case:
>
> void transmogrify(char* buffer, IntegralType length);
>
> What should I choose for the IntegralType? The domain of transmogrify
> is 0 <= length <= SIZE_T_MAX.
size_t, because there is no other choice. The guideline in a distilled
form is "if the valid range fits in an int, always use int", and in
this case it doesn't. There are cases in which the logical range is
[0..+inf) and one can choose the extra bit and go with unsigned, or
choose the chance to detect negative values and go with signed. This
is a matter of personal taste. I prefer the latter most of the time.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/26/2009 4:55:41 PM
|
|
On Jan 26, 5:41 am, David Abrahams <d...@boostpro.com> wrote:
> on Sun Jan 25 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
> > The interesting case is when you use less assertions than is needed to
> > formally prove that the code is OK (in the absence of cosmic rays and
> > bad memory). There is now a probability that the computations have
> > deviated from your expectations. The question is now: which style is
> > more likely to catch the error? Answer: a style in which f's domain is
> > a subset of the domain of the type used for its argument,
>
> [...]
> > so that f has a chance to detect invalid values. This is not limited
> > to signed/ unsigned, it can also apply to cases where one uses
> > unsigned int for arguments with a valid range for which unsigned char
> > would suffice.
>
> Oh, that's interesting. So let's take the most common case:
>
> void transmogrify(char* buffer, IntegralType length);
>
> What should I choose for the IntegralType? The domain of transmogrify
> is 0 <= length <= SIZE_T_MAX.
>
AFAIK the c++ standard requires that the difference between two
pointers be represented by ptrdiff_t, which is a signed integral type
(i.e., the domain can't really be what you say it is). So you should
use ptrdiff_t as IntegralType in your example (all negative values
will of course be invalid values).
Anyways, can the domain really be [buffer, buffer+SIZE_T_MAX) in
practical applications on real systems?
On most systems the theoretical upper bound is usually SIZE_T_MAX/2 or
SIZE_T_MAX*2/3 (on 64 bit systems is much, much less!). In practice it
might be even smaller.
The only exception that I can think of are embedded systems with a
small address space, and on those systems you might have integral
types larger than size_t.
--
Giovanni P. Deretta
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
gpderetta
|
1/26/2009 4:58:01 PM
|
|
On Jan 25, 9:41 pm, David Abrahams <d...@boostpro.com> wrote:
> [...] Semantics are defined by observable
> behavior, not implementation details. If you don't allow people to ask
> about the address of objects and all objects are immutable, there's no
> observable data sharing, and thus no reference semantics.
Well put. And more than this, understanding reference semantics is so
important in C and C++ that it becomes hard to think of value
semantics as having an existence independent of reference semantics.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
thant
|
1/26/2009 4:58:50 PM
|
|
Jeff Schwab wrote:
> Andrei Alexandrescu wrote:
>
>> void fun(unsigned x);
>
>> Got integers only? No problem. "Give me your tired, your poor, your
>> huddled integers of any size and signedness. I'll take'em."
>>
>> signed int x = 3;
>> fun(x - 10);
>
>> What we want to experiment with in D is disabling the most dangerous
>> conversion (int -> unsigned) and see how restrictive the resulting
>> conversion graph is. The true solution is to use a flow-sensitive value
>> range propagation analysis; that will associate with any number a
>> possible range at any point, which will catch many potential problems
>> without weeding away many correct uses. That's difficult to implement,
>> but I guess as soon as I'll bring up the opportunity of slashing in two
>> the size of any codebase, the motivation will be there :o).
> Not sure I followed all that. Do you see any issue with something like
> the following?
>
> template<class Value_type>
> class only
> {
> template<class T>
> only( T t );
>
> public:
>
> Value_type value;
>
> only( Value_type v )
> : value( v ) { }
> };
>
> unsigned fun(only<unsigned> n);
>
That's a nice template. It ain't going to save you from passing
vector.size() - 1 for an empty vector.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/26/2009 5:01:28 PM
|
|
David Abrahams wrote:
> I'm not excusing liberal signed/unsigned inter-conversions. We have to
> live with those. What we don't have to live with is complicated checks
> inside functions for conditions that are preventable at the function
> interface boundary.
Yah, that I agree with. In my experience you can weed out one half of
the check. Consider:
template <class T> struct Vector
{
...
T & operator[](int n) { enforce(n >= 0 && n < length_); ... }
}
versus:
template <class T> struct Vector
{
...
T & operator[](size_t n) { enforce(n < length_); ... }
}
If I use unsigned, I get rid of one of the tests. I don't think it's a
huge deal, but it does feel cleaner. Besides, I can use a vector > 2GB
in size on a 32-bit system, at least in theory. The underlying
assumption is that there is no combination of a vector > 2GB and a large
negative integer at the same time, which is reasonable.
However, I get nervous when I got rid of *all* tests when an unsigned
comes around. Then something is bound to run amok somewhere.
double pow(double base, unsigned exponent)
{
// look, ma! No checks!
double result = 1;
while (exponent)
{
if (exponent & 1)
{
result *= base;
--exponent;
}
else
{
result *= result;
exponent /= 2;
}
}
return result;
}
Well this function is rather prone to returning zero or infinity for
negative small exponents, which are frequent in a program and accepted
no problem. Do I still define it that way? Yes :o). I'm just more
miserable over it than 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
|
1/26/2009 5:04:44 PM
|
|
on Mon Jan 26 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
> However, I get nervous when I got rid of *all* tests when an unsigned
> comes around. Then something is bound to run amok somewhere.
>
> double pow(double base, unsigned exponent)
> {
> // look, ma! No checks!
> double result = 1;
> while (exponent)
> {
> if (exponent & 1)
> {
> result *= base;
> --exponent;
> }
> else
> {
> result *= result;
> exponent /= 2;
> }
> }
> return result;
> }
>
> Well this function is rather prone to returning zero or infinity for
> negative small exponents, which are frequent in a program and accepted
> no problem. Do I still define it that way? Yes :o). I'm just more
> miserable over it than others.
For that misery we should have
double pow(double base, strict<unsigned> exponent)
where strict<unsigned> doesn't admit implicit conversion from signed
types.
typedef strict<unsigned> unsigned_int;
;-)
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/27/2009 12:28:41 AM
|
|
"Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
news:687e89e9-918a-4791-9b9f-10e40b23b7d6@41g2000yqf.googlegroups.com...
> On Jan 23, 5:21 am, "Tony" <t...@my.net> wrote:
>> "Le Chaud Lapin" <jaibudu...@gmail.com> wrote in
>> messagenews:90e9f907-b5cc-42a6-b2b6-23d4c4aa1c87@q9g2000yqc.googlegroups.com...
>>
>> > Q: How do I return an error code from a constructor if exceptions are
>> > not available:
>> > A: You should use exceptions.
>>
>> A: Don't write constructors that can cause errors. Constructors are
>> mostly
>> for trivial initialization only. Don't use RAII everywhere: RAII is for
>> simple little classes like a lock class that will ensure that the lock
>> will
>> be released when a scope is exited. RAII is not for something like "class
>> MyApplicationProgram" that launches a chain-reaction of constructors on
>> instantiation. If something is procedural, code it up that way rather
>> than
>> shoe-horning everything into an OO extremism. Have more than just a
>> hammer
>> in your toolbox or else everything will look like a nail.
>
> Well, I do think it is OK for constructors to throw. The best example
> in my project is what you might call an IP address. It can construct
> itself from a string representation:
>
> struct IP_Address
> {
> IP_Address (const char *); // throws bad_argument
> // 64 other member functions
> } ;
It looks to me like the throw should be a simple assertion (precondition
argument checks are assertions rule). No exceptions needed: it's
development-time problem, not a runtime one.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/27/2009 9:19:12 AM
|
|
"Pete Becker" <pete@versatilecoding.com> wrote in message
news:2009012510075475249-pete@versatilecodingcom...
> On 2009-01-24 22:19:19 -0500, "Tony" <tony@my.net> said:
>
>>
>> "Pete Becker" <pete@versatilecoding.com> wrote in message
>> news:2009012416213775249-pete@versatilecodingcom...
>>> On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
>>>
>>>> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>>>>> The key is habit and consistency. When I think of a variable for the
>>>>> number of apples in a bin, I immediately think unsigned. At no point
>>>>> does consideration for signed enter my mind. I have been doing for
>>>>> almost 20 years.
>>>>> [...]
>>>>> With such code, one need only worry about boundary conditions, where
>>>>> negative values can sneak in, like input from command line, etc.
>>>>
>>>> Or anything involving subtraction. You can't rely on (a-1) < a if a is
>>>> unsigned.
>>>>
>>>
>>> You also can't rely on that with signed types. The key difference is
>>> that
>>> with unsigned types, underflow and overflow have well-defined results;
>>> with signed types they don't.
>>
>> As the owner of my code, I know my code will not exceed 2 billion (!
>> uint32_t) objects or elements.
>
> If you only use integers to count objects, fine. But that's a rather small
> subset of the things that most programmers use them for.
>
>> What you describe, is the PROBLEM with trying
>> to write overly-general code (read: for someone else to use MyClass in
>> any
>> kitchen sink).
>
> It's a problem, yes, and it has to be considered unless you're writing
> code for a single compiler and you've allowed for its limits.
>
>> Example: I wrote the class to handle list of chat friends!
>>
>> While analyzing the boundaries is important in software development,
>> using
>> extremes in the way you did was absurd (unless one is in programming 101
>> class). You must be one of those guys that programmed in C before 1989!
>> ;)
>>
>
> No, I'm one of those guys that knows more about writing portable code than
> you do.
But I'm seeking salvation from that abomination, so I don't accept the
status quo. "portability" is way over-rated. It's a problem to be solved
rather than something to be dealt with on each line of code written. YMMV.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/27/2009 9:19:13 AM
|
|
David Abrahams wrote:
> on Mon Jan 26 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
>
>> However, I get nervous when I got rid of *all* tests when an unsigned
>> comes around. Then something is bound to run amok somewhere.
>>
>> double pow(double base, unsigned exponent)
>> {
>> // look, ma! No checks!
>> double result = 1;
>> while (exponent)
>> {
>> if (exponent & 1)
>> {
>> result *= base;
>> --exponent;
>> }
>> else
>> {
>> result *= result;
>> exponent /= 2;
>> }
>> }
>> return result;
>> }
>>
>> Well this function is rather prone to returning zero or infinity for
>> negative small exponents, which are frequent in a program and accepted
>> no problem. Do I still define it that way? Yes :o). I'm just more
>> miserable over it than others.
>
> For that misery we should have
>
> double pow(double base, strict<unsigned> exponent)
>
> where strict<unsigned> doesn't admit implicit conversion from signed
> types.
>
> typedef strict<unsigned> unsigned_int;
>
> ;-)
>
Yah, that's a nice trick. As I wrote in reply to Jeff Schwab's post,
that still won't stop you from passing e.g. the difference between an
unsigned and a signed integer into pow. That's because of the rule "If
an unsigned is within a radius of one mile, then the result of an
operator is unsigned, unless that unsigned within a radius of one mile
has size smaller than int, in which case the value-preserving conversion
rule applies by first converting that unsigned within a radius of one
mile to signed int, followed by recursing to this rule."
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
1/27/2009 5:18:11 PM
|
|
on Mon Jan 26 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> On Jan 26, 6:41 am, David Abrahams <d...@boostpro.com> wrote:
>> So let's take the most common case:
>>
>> void transmogrify(char* buffer, IntegralType length);
>>
>> What should I choose for the IntegralType? The domain of transmogrify
>> is 0 <= length <= SIZE_T_MAX.
>
> size_t, because there is no other choice. The guideline in a distilled
> form is "if the valid range fits in an int, always use int",
Why? There's always long, and there's usually long long or some
equivalent.
> and in this case it doesn't. There are cases in which the logical
> range is [0..+inf) and one can choose the extra bit and go with
> unsigned, or choose the chance to detect negative values and go with
> signed. This is a matter of personal taste. I prefer the latter most
> of the time.
So you end up adding documentation that says the value must be positive?
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/27/2009 5:18:23 PM
|
|
Andrei Alexandrescu wrote:
> Jeff Schwab wrote:
>> template<class Value_type>
>> class only
>> {
>> template<class T>
>> only( T t );
>>
>> public:
>>
>> Value_type value;
>>
>> only( Value_type v )
>> : value( v ) { }
>> };
>>
>> unsigned fun(only<unsigned> n);
>>
>
> That's a nice template. It ain't going to save you from passing
> vector.size() - 1 for an empty vector.
It's not meant to save you from yourself. The goal is to prevent
unintended conversions, not to detect underflow.
The result of 0U - 1 is independent of whether it's being passed to a
function. Whether subtraction from unsigned values should be
disallowed, or should imply run-time overhead, is a broader issue, and
one the current C++ standard seems to handle nicely.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
1/27/2009 5:18:55 PM
|
|
Peter Dimov wrote:
> The interesting case is when you use less assertions than is needed to
> formally prove that the code is OK (in the absence of cosmic rays and
> bad memory). There is now a probability that the computations have
> deviated from your expectations. The question is now: which style is
> more likely to catch the error? Answer: a style in which f's domain is
> a subset of the domain of the type used for its argument, so that f
> has a chance to detect invalid values. This is not limited to signed/
> unsigned, it can also apply to cases where one uses unsigned int for
> arguments with a valid range for which unsigned char would suffice.
If you don't catch all invalid values, what's the point of checking at all?
E.g. if you do allocate memory based on some value you read in from some
network? There may still be some security vulnerabilty! Catching some
errors is not much better than catching less errors. Therefore in your
example the programmer has to check a, b, c and d, and therefore making
the "<0" assertion redundant.
> f( a*b - c*d ).
This could lead to wrong/undefined behaviour no matter what you check after
the calculation and independent of int/unsigned int, unless a, b, c and d
are converted from e.g. 32 bit to 64 bit ints. (In this case, the new type
has to have at least 2n bits, if the original data type has n bits.) The
whole possible range of calculation outcomes can be stored and checked,
then.
Bernhard
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bernhard
|
1/27/2009 5:19:26 PM
|
|
On 2009-01-27 04:19:13 -0500, "Tony" <tony@my.net> said:
>
> "Pete Becker" <pete@versatilecoding.com> wrote in message
> news:2009012510075475249-pete@versatilecodingcom...
>> On 2009-01-24 22:19:19 -0500, "Tony" <tony@my.net> said:
>>
>>>
>>> "Pete Becker" <pete@versatilecoding.com> wrote in message
>>> news:2009012416213775249-pete@versatilecodingcom...
>>>> On 2009-01-24 08:19:24 -0500, brangdon@cix.co.uk (Dave Harris) said:
>>>>
>>>>> jaibuduvin@gmail.com (Le Chaud Lapin) wrote (abridged):
>>>>>> The key is habit and consistency. When I think of a variable for the
>>>>>> number of apples in a bin, I immediately think unsigned. At no point
>>>>>> does consideration for signed enter my mind. I have been doing for
>>>>>> almost 20 years.
>>>>>> [...]
>>>>>> With such code, one need only worry about boundary conditions, where
>>>>>> negative values can sneak in, like input from command line, etc.
>>>>>
>>>>> Or anything involving subtraction. You can't rely on (a-1) < a if a is
>>>>> unsigned.
>>>>>
>>>>
>>>> You also can't rely on that with signed types. The key difference is
>>>> that
>>>> with unsigned types, underflow and overflow have well-defined results;
>>>> with signed types they don't.
>>>
>>> As the owner of my code, I know my code will not exceed 2 billion (!
>>> uint32_t) objects or elements.
>>
>> If you only use integers to count objects, fine. But that's a rather small
>> subset of the things that most programmers use them for.
>>
>>> What you describe, is the PROBLEM with trying
>>> to write overly-general code (read: for someone else to use MyClass in
>>> any
>>> kitchen sink).
>>
>> It's a problem, yes, and it has to be considered unless you're writing
>> code for a single compiler and you've allowed for its limits.
>>
>>> Example: I wrote the class to handle list of chat friends!
>>>
>>> While analyzing the boundaries is important in software development,
>>> using
>>> extremes in the way you did was absurd (unless one is in programming 101
>>> class). You must be one of those guys that programmed in C before 1989!
>>> ;)
>>>
>>
>> No, I'm one of those guys that knows more about writing portable code than
>> you do.
>
> But I'm seeking salvation from that abomination, so I don't accept the
> status quo. "portability" is way over-rated. It's a problem to be solved
> rather than something to be dealt with on each line of code written. YMMV.
>
Well, duh. If you're dealing with portability on each line of code
written you don't know what you're doing. But that doesn't mean you can
ignore portability. Part of writing portable code is thinking about the
range of value that a variable should store, and ensuring that it has a
type that can handle that range. Now, that certainly means dealing with
portability when you choose the type for an integral variable, but
that's a decision you have to make anyway. You can certainly choose to
ignore some factors that ought to go into that decision, and then you
can live with whatever consequences that choice may have.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Pete
|
1/27/2009 5:19:58 PM
|
|
On Jan 27, 8:28 am, David Abrahams <d...@boostpro.com> wrote:
> on Mon Jan 26 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
....
> > double pow(double base, unsigned exponent)
....
> For that misery we should have
>
> double pow(double base, strict<unsigned> exponent)
>
> where strict<unsigned> doesn't admit implicit conversion from signed
> types.
>
> typedef strict<unsigned> unsigned_int;
>
> ;-)
pow is a pretty good example of the tradeoffs. Try the three versions
on some common examples such as pow(x,2), n*pow(x,n-1) and judge for
yourself. I prefer pow(double,int) and do not feel that its inability
to raise x to 2^32-1 is a major loss.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/27/2009 5:20:27 PM
|
|
On Jan 25, 11:07 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> There is a point of view that considers "undefined behavior" to be
> something to avoid in the design of a programming language.
In C++, the code "undefined behavior" is actually a way of placing the
blame when something doesn't work. In ad-hoc code when programmer A
calls f(-1) and f has been written by programmer B, there can be
endless disputes over who has to fix what. For our purposes we can
think of "undefined behavior" as "your program stops dead". It's
important to distinguish this from "the operation raises an exception
which you can then handle and continue happily" which is what
"completely defined behavior" usually means.
Getting back to our asserts and your statement that they are a way to
validate one's own assumptions: true, and if one does that, it doesn't
matter whether the function "invokes undefined behavior" or has an
assert of its own.
// caller
assert( x >= 0 );
int r = f( x );
assert( r == -x );
// callee
// pre: x >= 0
// post: r == -x
int f( int x )
{
assert( x >= 0 );
int r = -x;
assert( r == -x );
return r;
}
The inner assert( x >= 0 ) is shadowed by the outer assert. It only
matters when the caller forgets to validate his assumptions. The
reverse happens with the postcondition check: the outer assert only
matters when the callee forgets.
To state it differently, the asserts that catch other people's bugs
only matter when those other people have undetected bugs in their
code. Which they do. :-) Nobody has complete assert coverage.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/27/2009 5:20:32 PM
|
|
On Jan 28, 1:19 am, Bernhard Jungk <bernh...@projectstarfire.de>
wrote:
> If you don't catch all invalid values, what's the point of checking at all?
That's like saying "if your tests do not prove with certainty that
your program is correct, what's the point of testing at all?"
The point is to have less bugs.
> E.g. if you do allocate memory based on some value you read in from some
> network? There may still be some security vulnerabilty! Catching some
> errors is not much better than catching less errors.
Yes it is. In the "allocate memory" case, the assert firing would
indicate that the programmer who parsed the network packet had a bug
in his validation code, a bug which his unit test (coupled with an
absence of asserts) did not catch. This does happen.
> Therefore in your
> example the programmer has to check a, b, c and d, and therefore making
> the "<0" assertion redundant.
Sure. If everyone writes perfect code, all memory bits are completely
stable, there are no calling convention mismatches, and there are no
dangling pointers (which is, on second thought, implied by the perfect
code), assertions are redundant. I myself prefer the redundancy.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/28/2009 12:05:53 AM
|
|
On Jan 28, 1:18 am, David Abrahams <d...@boostpro.com> wrote:
> on Mon Jan 26 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
> > On Jan 26, 6:41 am, David Abrahams <d...@boostpro.com> wrote:
> >> So let's take the most common case:
>
> >> void transmogrify(char* buffer, IntegralType length);
>
> >> What should I choose for the IntegralType? The domain of transmogrify
> >> is 0 <= length <= SIZE_T_MAX.
>
> > size_t, because there is no other choice. The guideline in a distilled
> > form is "if the valid range fits in an int, always use int",
>
> Why? There's always long, and there's usually long long or some
> equivalent.
Because 0..SIZE_T_MAX pretty much implies size_t in portable code. If
you can spare a bit you can go with ptrdiff_t but your statement of
the problem does not allow it. :-)
> So you end up adding documentation that says the value must be positive?
A "Requires" clause, yes. In many cases the "must be positive/non-
negative" is implied. In your example you'll probably want to say that
[buffer, buffer+length) is a valid range. Of course, if you have a
more elaborate way to check the real requirement, there's no need to
resort to the crude length >= 0 test.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/28/2009 12:06:50 AM
|
|
Mathias Gaunard wrote:
>
> void bar(int*);
> void bar2(int&);
>
> int main()
> {
> int foo[42] = {0};
>
> bar(&foo[0]);
> bar2(foo[0]);
> }
>
> The call to bar2 may be optimized to bar2(0);, but the call to bar may
> not be optimized in any way. (foo can be made global, but that's it).
> I'm not sure the standard allows this, but if it doesn't, it should.
How could the argument to bar2 be optimized to a constant 0,
when the compiler could not be sure whether the previous call to bar
might have changed foo[0]? (bar takes int*, not const int*.)
--
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
|
1/28/2009 12:07:24 AM
|
|
on Tue Jan 27 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
> David Abrahams wrote:
>> on Mon Jan 26 2009, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
>>
>>> However, I get nervous when I got rid of *all* tests when an unsigned
>>> comes around. Then something is bound to run amok somewhere.
>>>
>>> double pow(double base, unsigned exponent)
>>> {
>>> // look, ma! No checks!
>>> double result = 1;
>>> while (exponent)
>>> {
>>> if (exponent & 1)
>>> {
>>> result *= base;
>>> --exponent;
>>> }
>>> else
>>> {
>>> result *= result;
>>> exponent /= 2;
>>> }
>>> }
>>> return result;
>>> }
>>>
>>> Well this function is rather prone to returning zero or infinity for
>>> negative small exponents, which are frequent in a program and accepted
>>> no problem. Do I still define it that way? Yes :o). I'm just more
>>> miserable over it than others.
>>
>> For that misery we should have
>>
>> double pow(double base, strict<unsigned> exponent)
>>
>> where strict<unsigned> doesn't admit implicit conversion from signed
>> types.
>>
>> typedef strict<unsigned> unsigned_int;
>>
>> ;-)
>>
>
> Yah, that's a nice trick. As I wrote in reply to Jeff Schwab's post,
> that still won't stop you from passing e.g. the difference between an
> unsigned and a signed integer into pow.
Sure it will. Subtracting a signed_int from an insigned_int is a
compile-time error :-)
> That's because of the rule "If
> an unsigned is within a radius of one mile, then the result of an
> operator is unsigned, unless that unsigned within a radius of one mile
> has size smaller than int, in which case the value-preserving conversion
> rule applies by first converting that unsigned within a radius of one
> mile to signed int, followed by recursing to this rule."
I'm impressed. I've never bothered to learn how to express the rules
because they're just too complicated to handle safely unless you're
wearing a hazmat suit. Up to now, I've use explicit casts to make
everything rational before I combine any of these things. If I hadn't
been born so lazy I'd have written Boost.Strict already.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/28/2009 12:09:43 AM
|
|
Peter Dimov wrote:
> [...] For our purposes we can
> think of "undefined behavior" as "your program stops dead". [...]
The problem with "undefined behavior" is that the above is not at all
what it means. "Undefined behavior" means exactly that: undefined
behavior. It means your program might merely produce erroneous results
which you may or may not discover before someone relies on those results.
Throwing an exception when a program overflows or underflows or attempts
to index beyond the end of an array is not always necessarily about
catching it and "continuing happily." And it's not about shifting the
bug burden. It's about making sure a program never exhibits "undefined
behavior."
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/29/2009 1:53:42 AM
|
|
On 28 jan, 07:07, Seungbeom Kim <musip...@bawi.org> wrote:
> Mathias Gaunard wrote:
>
> > void bar(int*);
> > void bar2(int&);
>
> > int main()
> > {
> > int foo[42] = {0};
>
> > bar(&foo[0]);
> > bar2(foo[0]);
> > }
>
> > The call to bar2 may be optimized to bar2(0);, but the call to bar may
> > not be optimized in any way. (foo can be made global, but that's it).
> > I'm not sure the standard allows this, but if it doesn't, it should.
>
> How could the argument to bar2 be optimized to a constant 0,
> when the compiler could not be sure whether the previous call to bar
> might have changed foo[0]? (bar takes int*, not const int*.)
I was assuming bar did not get called before bar2.
I meant in my code to compare the optimizations possible when calling
bar(&foo[0]) or calling bar2(foo[0]). Not to call the two one after
the other.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/29/2009 1:54:25 AM
|
|
On Jan 26, 4:41 am, David Abrahams <d...@boostpro.com> wrote:
> No, Mr. Tessman has a point. Semantics are defined by observable
> behavior, not implementation details. If you don't allow people to ask
> about the address of objects and all objects are immutable, there's no
> observable data sharing, and thus no reference semantics.
I would still argue that it is a meaningless question, since
functional languages don't have the mechanism to observe it. I would
argue that this summs up the difference:
SomeType a, b;
b = a;
a.mutate();
if(b.is_mutated())
cout << "Rference semantics\n";
else
cout << "Value semantics\n";
Since pure languages dissallow a.mutate(), the entire point is moot.
-Ed
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Edward
|
1/29/2009 1:54:36 AM
|
|
on Wed Jan 28 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> On Jan 28, 1:18 am, David Abrahams <d...@boostpro.com> wrote:
>> on Mon Jan 26 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>>
>> > On Jan 26, 6:41 am, David Abrahams <d...@boostpro.com> wrote:
>> >> So let's take the most common case:
>>
>> >> void transmogrify(char* buffer, IntegralType length);
>>
>> >> What should I choose for the IntegralType? The domain of transmogrify
>> >> is 0 <= length <= SIZE_T_MAX.
>>
>> > size_t, because there is no other choice. The guideline in a distilled
>> > form is "if the valid range fits in an int, always use int",
>>
>> Why? There's always long, and there's usually long long or some
>> equivalent.
>
> Because 0..SIZE_T_MAX pretty much implies size_t in portable code. If
> you can spare a bit you can go with ptrdiff_t but your statement of
> the problem does not allow it. :-)
Sorry, I was asking why isn't the rule, "if the valid range fits in a
long, always use long?" What's special about int?
>> So you end up adding documentation that says the value must be positive?
>
> A "Requires" clause, yes. In many cases the "must be positive/non-
> negative" is implied. In your example you'll probably want to say that
> [buffer, buffer+length) is a valid range. Of course, if you have a
> more elaborate way to check the real requirement, there's no need to
> resort to the crude length >= 0 test.
Oh, yeah, why do something crude and simple when you can do something
elaborate? :-)
But I'm sure that's not what you meant, so I guess I don't follow.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/29/2009 2:05:12 AM
|
|
On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
> Throwing an exception when a program overflows or underflows or attempts
> to index beyond the end of an array is not always necessarily about
> catching it and "continuing happily."
Those errors should not be recoverable (especially if you want to
disable the checks to gain efficiency in release builds). Hence you
shouldn't throw exceptions but abort.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
1/29/2009 4:24:47 PM
|
|
On Jan 29, 1:54 am, Edward Rosten <Edward.Ros...@gmail.com> wrote:
> SomeType a, b;
> b = a;
>...
> a.mutate();
>...
> Since pure languages dissallow a.mutate(), the entire point is moot.
Even the first two lines lack a direct equivalent in C++:
SomeType a, b; // A ctor without arguments isn't nearly as useful.
b = a; // No re-assignment operator.
Another way to think about it is that pure functional languages lack
the whole distinction between 'object identity' and 'value equality'.
Thus no 'objects' and no 'storage'. No 'pointers', no 'references'.
Just names in the program text standing in for values.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
1/29/2009 9:36:31 PM
|
|
Edward Rosten wrote:
[...]
> I would still argue that it is a meaningless question, since
> functional languages don't have the mechanism to observe it. I would
> argue that this summs up the difference:
>
> SomeType a, b;
> b = a;
>
> a.mutate();
>
> if(b.is_mutated())
> cout << "Rference semantics\n";
> else
> cout << "Value semantics\n";
>
>
> Since pure languages dissallow a.mutate(), the entire point is moot.
I responded to a poster who wrote: "And the thing with C++ is that [it]
is one of the very few languages that actually allow value semantics."
What he meant is that C++ allows for "deep copy." And he considered this
a desirable feature of C++ because it avoids aliasing.
My point was that there are plenty of languages that avoid aliasing
(i.e. support "value semantics"). They just don't use deep copy to do
it. The reason they don't use deep copy to avoid aliasing is because
they don't have to. They share data by way of pointers, but only as an
implementation detail--an optimization, not as a means of providing for
mutability. This is practical because they also have garbage collection.
Deep copy is not a 'feature,' it's a memory-management technique.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/29/2009 9:37:03 PM
|
|
On Jan 29, 9:53 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Peter Dimov wrote:
> > [...] For our purposes we can
> > think of "undefined behavior" as "your program stops dead". [...]
>
> The problem with "undefined behavior" is that the above is not at all
> what it means. "Undefined behavior" means exactly that: undefined
> behavior. It means your program might merely produce erroneous results
> which you may or may not discover before someone relies on those results.
This is the technical meaning of the term, but the "undefined
behavior" design philosophy is different. It means that there do exist
programs that are formally "incorrect". Technically, when a program is
determined to be incorrect, its behavior is outside the realm of the
language and library guarantees. Ideally, when a program is determined
to be incorrect, it ought to be stopped dead without being given the
opportunity to inflict any further damage. This is what assert does,
and this is why people who follow the "UB" library design philosophy
prefer asserts for contract violations instead of a well-defined
response (be it an error code or an exception, both of which return
control to the program, which has already been determined to be
incorrect.)
Avoiding "UB" at the language and library level makes all programs
_formally_ correct, although it of course does not and cannot make
them _actually_ correct.
"UB" is in quotes here because it is possible to have a language whose
behavior is completely defined but which adheres to the "UB"
philosophy. (Why this is not practical for C++ should be obvious, but
it is possible.) Returning control to the program is not the only way
to define behavior on contract violations, yet this is mostly what is
meant when one says that UB is something to avoid.
> Throwing an exception when a program overflows or underflows or attempts
> to index beyond the end of an array is not always necessarily about
> catching it and "continuing happily." And it's not about shifting the
> bug burden. It's about making sure a program never exhibits "undefined
> behavior."
The correct use of returning control to the program on contract
violations is when one absolutely has to attempt recovery and continue
(well, maybe not happily). Two examples are a program that has
physical side effects or a multithreaded server. I prefer independent
processes for both, but one doesn't always have the choice.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/29/2009 9:38:45 PM
|
|
On Jan 29, 10:05 am, David Abrahams <d...@boostpro.com> wrote:
> on Wed Jan 28 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
> > On Jan 28, 1:18 am, David Abrahams <d...@boostpro.com> wrote:
> >> on Mon Jan 26 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
> >> > On Jan 26, 6:41 am, David Abrahams <d...@boostpro.com> wrote:
> >> >> So let's take the most common case:
>
> >> >> void transmogrify(char* buffer, IntegralType length);
>
> >> >> What should I choose for the IntegralType? The domain of transmogrify
> >> >> is 0 <= length <= SIZE_T_MAX.
>
> >> > size_t, because there is no other choice. The guideline in a distilled
> >> > form is "if the valid range fits in an int, always use int",
>
> >> Why? There's always long, and there's usually long long or some
> >> equivalent.
>
> > Because 0..SIZE_T_MAX pretty much implies size_t in portable code. If
> > you can spare a bit you can go with ptrdiff_t but your statement of
> > the problem does not allow it. :-)
>
> Sorry, I was asking why isn't the rule, "if the valid range fits in a
> long, always use long?" What's special about int?
Practically speaking, int is for arithmetic and ptrdiff_t is for
addressing. 16 bit ints are rare nowadays so long has no portable
range advantage. You can use long or long long if you like. :-)
Neither is required to be able to hold SIZE_T_MAX though.
> >> So you end up adding documentation that says the value must be positive?
>
> > A "Requires" clause, yes. In many cases the "must be positive/non-
> > negative" is implied. In your example you'll probably want to say that
> > [buffer, buffer+length) is a valid range. Of course, if you have a
> > more elaborate way to check the real requirement, there's no need to
> > resort to the crude length >= 0 test.
>
> Oh, yeah, why do something crude and simple when you can do something
> elaborate? :-)
>
> But I'm sure that's not what you meant, so I guess I don't follow.
I meant that (1) the Requires clause of your function already implies
a non-negative length, (2) if you can write an assert for the actual
Requires clause you can use an unsigned length and avoid the length >=
0 check.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/29/2009 9:39:02 PM
|
|
Mathias Gaunard wrote:
> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>
>> Throwing an exception when a program overflows or underflows or attempts
>> to index beyond the end of an array is not always necessarily about
>> catching it and "continuing happily."
>
> Those errors should not be recoverable (especially if you want to
> disable the checks to gain efficiency in release builds). Hence you
> shouldn't throw exceptions but abort.
That's like taking your life jacket off before you go to sea. "You
should just drown." Not all software can be allowed to fail gracelessly
in the field.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
1/29/2009 9:40:54 PM
|
|
Tony wrote:
> "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
> news:687e89e9-918a-4791-9b9f-10e40b23b7d6@41g2000yqf.googlegroups.com...
>> Well, I do think it is OK for constructors to throw. The best example
>> in my project is what you might call an IP address. It can construct
>> itself from a string representation:
>>
>> struct IP_Address
>> {
>> IP_Address (const char *); // throws bad_argument
>> // 64 other member functions
>> } ;
>
> It looks to me like the throw should be a simple assertion (precondition
> argument checks are assertions rule). No exceptions needed: it's
> development-time problem, not a runtime one.
Suppose you write a program that takes an IP address from the user.
How do you ensure that the user input is always valid? Before calling
the constructor? Then we're back to the double testing problem.
I don't think assert is the right one to use here.
--
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
|
1/29/2009 9:43:17 PM
|
|
Mathias Gaunard wrote:
> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>
>> Throwing an exception when a program overflows or underflows or attempts
>> to index beyond the end of an array is not always necessarily about
>> catching it and "continuing happily."
>
> Those errors should not be recoverable (especially if you want to
> disable the checks to gain efficiency in release builds). Hence you
> shouldn't throw exceptions but abort.
Whether those errors are recoverable or not depends entirely on what
you're trying to do.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/29/2009 9:43:43 PM
|
|
Mathias Gaunard wrote:
> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>
>> Throwing an exception when a program overflows or underflows or attempts
>> to index beyond the end of an array is not always necessarily about
>> catching it and "continuing happily."
>
> Those errors should not be recoverable (especially if you want to
> disable the checks to gain efficiency in release builds). Hence you
> shouldn't throw exceptions but abort.
I wrote:
"Whether those errors are recoverable or not depends entirely on what
you're trying to do."
But more importantly, these should be signaled as errors without the
programmer being relied upon to provide an assert.
And yes, there are situations where those checks should be forgone for
the sake of performance, but those situations are rare now days in the
context of general application development.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/29/2009 9:43:59 PM
|
|
"Thant Tessman" <thant.tessman@gmail.com> wrote in message
news:gltmj2$1d3$1@news.xmission.com...
> Mathias Gaunard wrote:
>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>>> Throwing an exception when a program overflows or underflows or attempts
>>> to index beyond the end of an array is not always necessarily about
>>> catching it and "continuing happily."
>>
>> Those errors should not be recoverable (especially if you want to
>> disable the checks to gain efficiency in release builds). Hence you
>> shouldn't throw exceptions but abort.
>
> I wrote:
>
> "Whether those errors are recoverable or not depends entirely on what
> you're trying to do."
>
> But more importantly, these should be signaled as errors without the
> programmer being relied upon to provide an assert.
Give example with context please (make it as simple as you can!). Aside:
never use "signal" in talking about EH unless you intend to introduce UNIX
signals in a followup. Set the stage with context, scenario, example.
Precondition checking (arg s != null) of a string function library comes to
mind as the mildest example.
> And yes, there are situations where those checks should be forgone for
> the sake of performance, but those situations are rare now days in the
> context of general application development.
PLHF/SBCK/SBIKBW/GWS/D.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/30/2009 12:44:56 PM
|
|
"Peter Dimov" <pdimov@gmail.com> wrote in message
news:5ecb7202-bdee-43f2-831c-32782f1adea0@t39g2000prh.googlegroups.com...
> On Jan 29, 9:53 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>> Peter Dimov wrote:
>> > [...] For our purposes we can
>> > think of "undefined behavior" as "your program stops dead". [...]
>>
>> The problem with "undefined behavior" is that the above is not at all
>> what it means. "Undefined behavior" means exactly that: undefined
>> behavior. It means your program might merely produce erroneous results
>> which you may or may not discover before someone relies on those results.
>
> This is the technical meaning of the term, but the "undefined
> behavior" design philosophy is different. It means that there do exist
> programs that are formally "incorrect". Technically, when a program is
> determined to be incorrect, its behavior is outside the realm of the
> language and library guarantees. Ideally, when a program is determined
> to be incorrect, it ought to be stopped dead without being given the
> opportunity to inflict any further damage. This is what assert does,
> and this is why people who follow the "UB" library design philosophy
> prefer asserts for contract violations instead of a well-defined
> response (be it an error code or an exception, both of which return
> control to the program, which has already been determined to be
> incorrect.)
>
> Avoiding "UB" at the language and library level makes all programs
> _formally_ correct, although it of course does not and cannot make
> them _actually_ correct.
>
> "UB" is in quotes here because it is possible to have a language whose
> behavior is completely defined but which adheres to the "UB"
> philosophy. (Why this is not practical for C++ should be obvious, but
> it is possible.) Returning control to the program is not the only way
> to define behavior on contract violations, yet this is mostly what is
> meant when one says that UB is something to avoid.
>
Slicing French bread for bruschetta (did I just sin?) with a knife because
you are an adult and know the safety issues, vs. putting the bread in a
compartment in the wall of your "childproof" kitchen and getting back the
loaf of bread sliced.
I have a high-performance/bare bones Bitset class that is completely naked.
I also have a Bitset2 class that is for higher-level code and has built-in
protection (Yowza!) from inappropriate use. Probably not a good example
though, cuz they're both built like a brick.... nevermind! (Wrong window?).
;)
>> Throwing an exception when a program overflows or underflows or attempts
>> to index beyond the end of an array is not always necessarily about
>> catching it and "continuing happily." And it's not about shifting the
>> bug burden. It's about making sure a program never exhibits "undefined
>> behavior."
>
> The correct use of returning control to the program on contract
> violations is when one absolutely has to attempt recovery and continue
> (well, maybe not happily). Two examples are a program that has
> physical side effects or a multithreaded server. I prefer independent
> processes for both, but one doesn't always have the choice.
Context matters. But, UB is "an exercise left to the reader", yes? Isn't
harnessing UB one of the programming tasks (I'm finding solutions for such)?
I think I remember someone saying that "the committee" was adressing this as
much as they could/can anyway... I mean if you define a language so abstract
and general, as best as they can.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/30/2009 12:46:49 PM
|
|
"Thant Tessman" <thant.tessman@gmail.com> wrote in message
news:glsjd0$9kg$1@news.xmission.com...
> Edward Rosten wrote:
>
> [...]
>
>> I would still argue that it is a meaningless question, since
>> functional languages don't have the mechanism to observe it. I would
>> argue that this summs up the difference:
>>
>> SomeType a, b;
>> b = a;
>>
>> a.mutate();
>>
>> if(b.is_mutated())
>> cout << "Rference semantics\n";
>> else
>> cout << "Value semantics\n";
>>
>>
>> Since pure languages dissallow a.mutate(), the entire point is moot.
>
> I responded to a poster who wrote: "And the thing with C++ is that [it]
> is one of the very few languages that actually allow value semantics."
> What he meant is that C++ allows for "deep copy." And he considered this
> a desirable feature of C++ because it avoids aliasing.
>
> My point was that there are plenty of languages that avoid aliasing
> (i.e. support "value semantics"). They just don't use deep copy to do
> it. The reason they don't use deep copy to avoid aliasing is because
> they don't have to. They share data by way of pointers, but only as an
> implementation detail--an optimization, not as a means of providing for
> mutability. This is practical because they also have garbage collection.
>
> Deep copy is not a 'feature,' it's a memory-management technique.
And that's not a conclusion or dismissal of any sort. You surely weren't
suggesting that "deep copy" is a bad thing, were you?
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/30/2009 12:47:52 PM
|
|
"Seungbeom Kim" <musiphil@bawi.org> wrote in message
news:glte63$r5t$1@news.stanford.edu...
> Tony wrote:
>> "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
>> news:687e89e9-918a-4791-9b9f-10e40b23b7d6@41g2000yqf.googlegroups.com...
>>> Well, I do think it is OK for constructors to throw. The best example
>>> in my project is what you might call an IP address. It can construct
>>> itself from a string representation:
>>>
>>> struct IP_Address
>>> {
>>> IP_Address (const char *); // throws bad_argument
>>> // 64 other member functions
>>> } ;
>>
>> It looks to me like the throw should be a simple assertion (precondition
>> argument checks are assertions rule). No exceptions needed: it's
>> development-time problem, not a runtime one.
>
> Suppose you write a program that takes an IP address from the user.
> How do you ensure that the user input is always valid?
Umm... validate it?!
> Before calling
> the constructor?
What constructor? I don't see a constructor. Did someone see a constructor.
APB out for the constructor!
> Then we're back to the double testing problem.
>
> I don't think assert is the right one to use here.
So... we'll spend a decade talking around it instead of solving the problem?
(Ref: any EH thread on usenet. Same for GC).
That's why I say and say that when discussing error handling (EH), context
matters. "Academic" discussions are just beertalk.
Tony
(I've never seen the word 'beertalk'. Is it in Webster's or other
dictionary? Nevermind, that is not to be my legacy. I release the word
'beertalk' into the public domain and relinquish all copyrights to the word
'beertalk'. Yes, free, as in beer!).
..... I don't even like beer!
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/30/2009 12:57:59 PM
|
|
"Jeff Schwab" <jeff@schwabcenter.com> wrote in message
news:vpqdnUMcX47Gsx_UnZ2dnUVZ_u-dnZ2d@giganews.com...
> Mathias Gaunard wrote:
>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>>> Throwing an exception when a program overflows or underflows or attempts
>>> to index beyond the end of an array is not always necessarily about
>>> catching it and "continuing happily."
>>
>> Those errors should not be recoverable (especially if you want to
>> disable the checks to gain efficiency in release builds). Hence you
>> shouldn't throw exceptions but abort.
>
> That's like taking your life jacket off before you go to sea. "You
> should just drown." Not all software can be allowed to fail gracelessly
> in the field.
I think he was saying what I too was thinking: that those things are
assertions that should be out of the software before release (weeded out
during testing or before). If the assertions are left in for release builds,
then they should indeed most probably terminate the program for the
violation is a serious logical error and anything might happen if one were
to continue because there is just no way of knowing the state of anything at
that point. (Hopefully terminate() will work!). It's a very context-specific
problem. You handle it to the degree necessary. Error handling is so
context-specific and has so many potential levels, that all the chat about
exceptions seems frivolous in comparison. I've read that F-15 fighter planes
had multiple orthogonally developed (orthogonal in every way: companies,
hardware, software... though there is a more appropriate word than
'orthogonal' that escapes me right now, but you get the gist) systems that
controlled of the plane by agreement of a majority of the systems for every
critical calculation. That's like punching "2 + 2" into your HP calculator,
AND into your TI calculator AND your cellphone... to verify that 4 is indeed
the correct result before you believe it and act on it.
I don't believe a discussion about error handling can be had unless the
context and criticality of the software is known a priori. Without that, all
one can do is describe the various mechanisms such as C++ exception handling
which won't even get you a robust server application program (it's just an
intra-program EH mechanism).
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
1/30/2009 1:02:49 PM
|
|
>Jeff Schwab" <jeff@schwabcenter.com>
>>
>>> Throwing an exception when a program overflows or underflows or attempts
>>> to index beyond the end of an array is not always necessarily about
>>> catching it and "continuing happily."
>>
>> Those errors should not be recoverable (especially if you want to
>> disable the checks to gain efficiency in release builds). Hence you
>> shouldn't throw exceptions but abort.
>
> That's like taking your life jacket off before you go to sea. "You
> should just drown." Not all software can be allowed to fail gracelessly
> in the field.
Provided your "life jacket", seeing water, has ability to kill your
neighbors or initiate WW3, it can be used as analogy.
But then the "You should just drown" part may not look like the worst thing
could happen.
To give some IRL examples, one of most frequent reauest is to "allow user to
save work". He doesn't want to lose the last 15 minutes work does he?
I recall the era using WinWord versions around 6.0 -- they had bugs and also
ran along in many cases. So I could save the work. A few days later I
discovered my .DOC files are seriously corrupt. And by that time overwrote
earlier versions that were yet clean. Losing several days/weeks worth of
work, instead of minutes.
Similar cases with games. Save looks like working -- but a few levels ahead
you get stuck. Then possibly don't have early enough saves. Or have to keep
excess amount of savefiles.
I rather chose "correct or nothing". With real life cases the data is
complex enough so you have no realistic way to tell near miss from actual
miss facing UB. So avoid UB and drop dead on sight of it.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/30/2009 1:38:51 PM
|
|
"Seungbeom Kim" <musiphil@bawi.org>
>
> Suppose you write a program that takes an IP address from the user.
> How do you ensure that the user input is always valid? Before calling
> the constructor? Then we're back to the double testing problem.
>
> I don't think assert is the right one to use here.
As we know "All input is evil". You have to gat it somehow. Where your IP
address class lives, inside the gates or outside? For the first case it can
assert. If it is for both purposes, you can have it another ctor that do
gating and throws.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/30/2009 1:39:21 PM
|
|
On Jan 30, 5:40 am, Jeff Schwab <j...@schwabcenter.com> wrote:
> Mathias Gaunard wrote:
> > On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>
> >> Throwing an exception when a program overflows or underflows or attempts
> >> to index beyond the end of an array is not always necessarily about
> >> catching it and "continuing happily."
>
> > Those errors should not be recoverable (especially if you want to
> > disable the checks to gain efficiency in release builds). Hence you
> > shouldn't throw exceptions but abort.
>
> That's like taking your life jacket off before you go to sea. "You
> should just drown." Not all software can be allowed to fail gracelessly
> in the field.
The software has already failed. The question is what is the proper
response to that failure _by default_. The programmer can always
override the default by using his own asserts (or throws). The follow-
up question is what programming style and software quality does this
default response promote or encourage.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
1/30/2009 7:14:40 PM
|
|
Balog Pal wrote:
>> Jeff Schwab" <jeff@schwabcenter.com>
>>>> Throwing an exception when a program overflows or underflows or attempts
>>>> to index beyond the end of an array is not always necessarily about
>>>> catching it and "continuing happily."
>>> Those errors should not be recoverable (especially if you want to
>>> disable the checks to gain efficiency in release builds). Hence you
>>> shouldn't throw exceptions but abort.
>> That's like taking your life jacket off before you go to sea. "You
>> should just drown." Not all software can be allowed to fail gracelessly
>> in the field.
> I recall the era using WinWord versions around 6.0 -- they had bugs and also
> ran along in many cases. So I could save the work. A few days later I
> discovered my .DOC files are seriously corrupt. And by that time overwrote
> earlier versions that were yet clean. Losing several days/weeks worth of
> work, instead of minutes.
Better a figher pilot should get misleading data from his controls,
along with a notice that the control system is fubar, than fall out of
the sky immediately.
> I rather chose "correct or nothing". With real life cases the data is
> complex enough so you have no realistic way to tell near miss from actual
> miss facing UB. So avoid UB and drop dead on sight of it.
You're using the phrase "drop dead" as a metaphor. I'm not. Throwing
an exception gives the client code a chance to recover, if imperfectly.
The fact that catch blocks might not do the right thing does not imply
that the client should be treated like an untrustworthy child, even if
you think you've found an error in their code.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
1/30/2009 7:15:38 PM
|
|
On Jan 30, 11:47 am, "Tony" <t...@my.net> wrote:
> "Thant Tessman" <thant.tess...@gmail.com> wrote in message
[...]
> > Deep copy is not a 'feature,' it's a memory-management technique.
>
> And that's not a conclusion or dismissal of any sort. You surely weren't
> suggesting that "deep copy" is a bad thing, were you?
Within the context of C++, of course not. But the original poster was
talking about value semantics within the context of programming
languages in general and fallaciously attributing the benefits of
value semantics exclusively to "deep copy," thus in turn fallaciously
attributing to C++ benefits which are by no means unique to C++.
To the contrary, the fact that "deep copy" is as useful as it is in
helping produce error-free code suggests that one might be able to do
better than C++ when designing a programming language. (This is not to
criticize any specific aspect of C++ given the decision to extend C.
This is to criticize the decision to extend C in the first place.)
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
thant
|
1/30/2009 7:16:03 PM
|
|
On Jan 30, 11:44 am, "Tony" <t...@my.net> wrote:
> "Thant Tessman" <thant.tess...@gmail.com> wrote in message
>
> news:gltmj2$1d3$1@news.xmission.com...
>
>
>
> > Mathias Gaunard wrote:
> >> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>
> >>> Throwing an exception when a program overflows or underflows or attempts
> >>> to index beyond the end of an array is not always necessarily about
> >>> catching it and "continuing happily."
>
> >> Those errors should not be recoverable (especially if you want to
> >> disable the checks to gain efficiency in release builds). Hence you
> >> shouldn't throw exceptions but abort.
>
> > I wrote:
>
> > "Whether those errors are recoverable or not depends entirely on what
> > you're trying to do."
>
> > But more importantly, these should be signaled as errors without the
> > programmer being relied upon to provide an assert.
>
> Give example with context please (make it as simple as you can!). [...]
What? You'd want to recover from those errors for the same reason
you'd want to recover from any thrown exception. I don't want my
program to necessarily die just because it was given bad data.
> PLHF/SBCK/SBIKBW/GWS/D.
?
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
thant
|
1/30/2009 7:16:13 PM
|
|
Tony wrote:
> "Seungbeom Kim" <musiphil@bawi.org> wrote in message
> news:glte63$r5t$1@news.stanford.edu...
>> Tony wrote:
>>> "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
>>> news:687e89e9-918a-4791-9b9f-10e40b23b7d6@41g2000yqf.googlegroups.com...
>>>> Well, I do think it is OK for constructors to throw. The best example
>>>> in my project is what you might call an IP address. It can construct
>>>> itself from a string representation:
>>>>
>>>> struct IP_Address
>>>> {
>>>> IP_Address (const char *); // throws bad_argument
>>>> // 64 other member functions
>>>> } ;
>>> It looks to me like the throw should be a simple assertion (precondition
>>> argument checks are assertions rule). No exceptions needed: it's
>>> development-time problem, not a runtime one.
>> Suppose you write a program that takes an IP address from the user.
>> How do you ensure that the user input is always valid?
>
> Umm... validate it?!
We certainly do. What matters here is, where.
(1) Check before construction and assert only valid inputs gets into
the constructor.
(2) Check during construction and throw on invalid inputs.
Suppose the user gave the IP address in argv[i]. Do you mean:
if (IP_Address::valid(argv[i])) {
IP_Address host(argv[i]);
// use host
}
else {
// error
}
IP_Address::IP_Address(const char* s)
{
assert(valid(s));
// construct *this
}
Here, we have double calls to IP_Address::valid, and a similar logic
might be repeated even in the part "// construct *this".
I find this a lot simpler:
try {
IP_Address host(argv[i]);
// use host
}
catch (invalid_argument& e) {
// error
}
IP_Address::IP_Address(const char* s)
{
// construct *this, throwing invalid_argument on error
}
>
>> Before calling the constructor?
>
> What constructor? I don't see a constructor. Did someone see a constructor.
> APB out for the constructor!
I do see IP_Address::IP_Address(const char *) above.
What's APB?
--
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
|
1/30/2009 7:16:38 PM
|
|
Balog Pal wrote:
> "Seungbeom Kim" <musiphil@bawi.org>
>> Suppose you write a program that takes an IP address from the user.
>> How do you ensure that the user input is always valid? Before calling
>> the constructor? Then we're back to the double testing problem.
>>
>> I don't think assert is the right one to use here.
>
> As we know "All input is evil". You have to gat it somehow. Where your IP
> address class lives, inside the gates or outside? For the first case it can
> assert. If it is for both purposes, you can have it another ctor that do
> gating and throws.
What do you mean by 'where the class lives'?
I understand that some *data* (or an *object*) live inside the gates
and others come from outside, but I'm not sure if a *class* necessarily
either lives inside or comes from outside, or if we need one for each
(IP_Address_inside and IP_Address_outside).
--
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
|
1/30/2009 7:16:48 PM
|
|
on Thu Jan 29 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Edward Rosten wrote:
>
> [...]
>
>> I would still argue that it is a meaningless question, since
>> functional languages don't have the mechanism to observe it. I would
>> argue that this summs up the difference:
>>
>> SomeType a, b;
>> b = a;
>>
>> a.mutate();
>>
>> if(b.is_mutated())
>> cout << "Rference semantics\n";
>> else
>> cout << "Value semantics\n";
>>
>>
>> Since pure languages dissallow a.mutate(), the entire point is moot.
>
> I responded to a poster who wrote: "And the thing with C++ is that [it]
> is one of the very few languages that actually allow value semantics."
> What he meant is that C++ allows for "deep copy."
Not really; as noted elsewhere, I don't even like that term. What I
meant was that C++ allows you to distinguish the pointers that refer to
parts from those that denote relatioships.
> And he considered this a desirable feature of C++ because it avoids
> aliasing.
>
> My point was that there are plenty of languages that avoid aliasing
> (i.e. support "value semantics"). They just don't use deep copy to do
> it. The reason they don't use deep copy to avoid aliasing is because
> they don't have to. They share data by way of pointers, but only as an
> implementation detail--an optimization, not as a means of providing for
> mutability. This is practical because they also have garbage collection.
>
> Deep copy is not a 'feature,' it's a memory-management technique.
I have to disagree there. Copying is only a meaningful concept in a
system that allows data mutation. Otherwise, you're dealing with pure
values in a mathematical sense (all 42s are exactly equivalent --
copying 42 is meaningless). The fact of data mutation exists in the
underlying machines, and pure functional languages do their best to
avoid exposing that fact. But then, there are things that are just too
hard to do efficiently in pure functional languages (or nobody's figured
out how to write a smart enough compiler), and some things are just too
awkward to do concisely, so there's still a place for C and C++. Once
you expose data mutation, the ability to distinguish pointers that refer
to parts from pointers that denote relationships, and treat them
appropriately when copying, *is* a feature. That ability is -- I think
-- what you're calling "deep copy"
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/30/2009 7:17:18 PM
|
|
on Thu Jan 29 2009, Jeff Schwab <jeff-AT-schwabcenter.com> wrote:
> Mathias Gaunard wrote:
>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>>> Throwing an exception when a program overflows or underflows or attempts
>>> to index beyond the end of an array is not always necessarily about
>>> catching it and "continuing happily."
>>
>> Those errors should not be recoverable (especially if you want to
>> disable the checks to gain efficiency in release builds). Hence you
>> shouldn't throw exceptions but abort.
>
> That's like taking your life jacket off before you go to sea. "You
> should just drown." Not all software can be allowed to fail gracelessly
> in the field.
If you give a person a lifejacket, he may be able to stay afloat long
enough to improvise a response to an unanticipated emergency situation,
in which case full recovery is a possibility. Since software can only
handle the situations the programmer can anticipate, the same reasoning
doesn't apply.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/30/2009 7:17:42 PM
|
|
on Thu Jan 29 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Mathias Gaunard wrote:
>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>>> Throwing an exception when a program overflows or underflows or attempts
>>> to index beyond the end of an array is not always necessarily about
>>> catching it and "continuing happily."
>>
>> Those errors should not be recoverable (especially if you want to
>> disable the checks to gain efficiency in release builds). Hence you
>> shouldn't throw exceptions but abort.
>
> I wrote:
>
> "Whether those errors are recoverable or not depends entirely on what
> you're trying to do."
>
> But more importantly, these should be signaled as errors without the
> programmer being relied upon to provide an assert.
There are preconditions at every level of the program. Even if the
language itself can catch and signal every condition that the the
language itself defines to be erroneous, there will still be good cause
for assertions in user code.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
1/30/2009 7:17:44 PM
|
|
Seungbeom Kim <musiphil@bawi.org> wrote:
> Tony wrote:
> > "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote:
> > > Well, I do think it is OK for constructors to throw. The best
> > > example in my project is what you might call an IP address. It
> > > can construct itself from a string representation:
> > >
> > > struct IP_Address
> > > {
> > > IP_Address (const char *); // throws bad_argument
> > > // 64 other member functions
> > > } ;
> >
> > It looks to me like the throw should be a simple assertion
> > (precondition argument checks are assertions rule). No exceptions
> > needed: it's development-time problem, not a runtime one.
>
> Suppose you write a program that takes an IP address from the user.
> How do you ensure that the user input is always valid? Before
> calling the constructor? Then we're back to the double testing
> problem.
>
> I don't think assert is the right one to use here.
Is the constructor's job to validate user input, construct an IP_Address
object or both? If your answer is the latter, then I think you need to
brush up on the single responsibility principle...
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Daniel
|
1/31/2009 9:09:27 AM
|
|
David Abrahams wrote:
> on Thu Jan 29 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>
[...]
>> What he meant is that C++ allows for "deep copy."
>
> Not really; as noted elsewhere, I don't even like that term.
Sorry, in that post I was referring Mathias Gaunard's statement, not
yours (which I disagreed with for different, but related reasons that I
thought would illustrate why someone might conflate "value semantics"
with "deep copy").
>> Deep copy is not a 'feature,' it's a memory-management technique.
>
> I have to disagree there. Copying is only a meaningful concept in a
> system that allows data mutation. [...]
Right. But most functional languages[1] are not 'pure.' I only brought
up pure functional languages as an extreme example of languages other
than C++ that support "value semantics." The thing is, these non-pure
functional programming languages certainly allow for deep copy and
reference semantics, but rarely need it exactly because they have
automatic..er..garbage collection. Here's a C++ implementation of a
(mostly) balanced tree dictionary done in the pure functional style:
http://www.thant.com/Env.hpp
The approach I took is described here:
http://www.eecs.usma.edu/webs/people/okasaki/jfp99.ps
The amazing thing about this approach is that the insert function (or in
my case the 'extend' function) returns a new dictionary without
disturbing any value set to any previous version of the dictionary, and
it does so extremely efficiently.
This implementation of a purely functional balanced tree dictionary
relies on a reference-counting smart pointer. In this case that's all
that's needed because no circular references are created. In general,
this is not enough. The point is that when you have genuine garbage
collection, you almost never need deep copy. You only introduce
reference semantics in the situations where you actually want reference
semantics. It becomes the exception, not the rule.
-thant
----
[1] For practical purposes, we can define "functional languages" as
languages that allow functions to create other functions in a way that
implies the use of closures.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/31/2009 9:10:35 AM
|
|
David Abrahams wrote:
> on Thu Jan 29 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>
>> Mathias Gaunard wrote:
>>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>>
>>>> Throwing an exception when a program overflows or underflows or attempts
>>>> to index beyond the end of an array is not always necessarily about
>>>> catching it and "continuing happily."
>>> Those errors should not be recoverable (especially if you want to
>>> disable the checks to gain efficiency in release builds). Hence you
>>> shouldn't throw exceptions but abort.
>> I wrote:
>>
>> "Whether those errors are recoverable or not depends entirely on what
>> you're trying to do."
>>
>> But more importantly, these should be signaled as errors without the
>> programmer being relied upon to provide an assert.
>
> There are preconditions at every level of the program. Even if the
> language itself can catch and signal every condition that the the
> language itself defines to be erroneous, there will still be good cause
> for assertions in user code.
Yeah, one would think. I use assertions in C++ all the time, but as I
said, not to assert that the caller of a function has passed in valid
values. The right thing to do in the case of invalid values is to report
an error, not simply abort. I use assertions to give myself confidence
that a program is doing what I think it's doing.
But Standard ML is a language that indeed catches and signals every
condition that it defines to be erroneous, and when programming in SML,
I don't miss asserts at all.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
1/31/2009 9:10:49 AM
|
|
>"Jeff Schwab" <jeff@schwabcenter.com>
>> Balog Pal wrote:
>>> Jeff Schwab" <jeff@schwabcenter.com>
>>> That's like taking your life jacket off before you go to sea. "You
>>> should just drown." Not all software can be allowed to fail gracelessly
>>> in the field.
>
> Better a figher pilot should get misleading data from his controls,
> along with a notice that the control system is fubar, than fall out of
> the sky immediately.
You're drifting the subject under the scenes!
Is the "control data measuring" system the same software as the "control
system"?
The control shall not necessarily fail due to receptor malfunction -- but
the receptor should. Instead of reporting non-truth, really sending the
plane who knows where.
If the control system itself fails, why you think it can signal any kind of
notice?
Also why you think that control system shutdown means falling out of sky? It
could get out of the way leaving the pilot a chance to fly manually. Why
should we think it is worse than a wild control working in erratic way?
In critical systems dhe design uses separation and backups, so failure of
any component is localised as much as possible, the wrong part is withdrawn
and somethig else gets used.
>> I rather chose "correct or nothing". With real life cases the data is
>> complex enough so you have no realistic way to tell near miss from actual
>> miss facing UB. So avoid UB and drop dead on sight of it.
>
> You're using the phrase "drop dead" as a metaphor. I'm not. Throwing
> an exception gives the client code a chance to recover, if imperfectly.
Says what? In such generality, at least. In a system infested with asserts
a failure most probably indicates UB somewhere. Then from that point
anything can happen, and your "chance to recover" is as likely a "chance to
more harm".
Even if the behavior is still defined the state is something no programmer
expected. How you measure your recovery chance in such situtation?
And "drop dead" is applied to the component.
> The fact that catch blocks might not do the right thing does not imply
> that the client should be treated like an untrustworthy child, even if
> you think you've found an error in their code.
So now you say the 'client' has generally more knowledge of the system
internals than its creators?
Real world shows that bg majority of clients can't make fair decisions on
security-related alerts. No matter now little or much information you show.
(stuff like using unsigned activex, executing untrusted whatever...)
If a program shows you a box 'argument must be positive', how do you
proceed?
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/31/2009 9:13:12 AM
|
|
>"Seungbeom Kim" <musiphil@bawi.org>
>> Balog Pal wrote:
>> As we know "All input is evil". You have to gat it somehow. Where your
>> IP
>> address class lives, inside the gates or outside? For the first case it
>> can
>> assert. If it is for both purposes, you can have it another ctor that do
>> gating and throws.
>
> What do you mean by 'where the class lives'?
> I understand that some *data* (or an *object*) live inside the gates
> and others come from outside, but I'm not sure if a *class* necessarily
> either lives inside or comes from outside, or if we need one for each
> (IP_Address_inside and IP_Address_outside).
By class here I meant the code of the class' implementation.
By 'inside' here I mean functions that can expect clean (data as) input,
preconditions correctly observed by the caller.
The granulatity is certainly on function level, not on class level.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
1/31/2009 9:13:19 AM
|
|
Jeff Schwab wrote:
> Balog Pal wrote:
>> I recall the era using WinWord versions around 6.0 -- they had bugs
>> and also
>> ran along in many cases. So I could save the work. A few days later
>> I discovered my .DOC files are seriously corrupt. And by that time
>> overwrote earlier versions that were yet clean. Losing several
>> days/weeks worth of work, instead of minutes.
>
> Better a figher pilot should get misleading data from his controls,
> along with a notice that the control system is fubar, than fall out of
> the sky immediately.
I would rather give the fighter pilot a half second of full systems loss
due to a reboot than that he has to fly blind for a significant amount
of time because the readings can't be trusted.
Getting misleading data in an airplane cockpit can be very deadly, while
a short interruption can be recovered from unless you are basically
flying at treetop level. Falling from the sky takes time, you know.
>
>> I rather chose "correct or nothing". With real life cases the data
>> is complex enough so you have no realistic way to tell near miss from
>> actual
>> miss facing UB. So avoid UB and drop dead on sight of it.
>
> You're using the phrase "drop dead" as a metaphor. I'm not. Throwing
> an exception gives the client code a chance to recover, if
> imperfectly.
> The fact that catch blocks might not do the right thing does not
> imply
> that the client should be treated like an untrustworthy child, even if
> you think you've found an error in their code.
So, you don't regard it as a problem when, if continuing after a failed
error check, a jumbo-jet full of holiday makers is misinterpreted as an
enemy plane taking aggressive action towards you.
Or that a programming error caused your artificial horizon to be flipped
upside down.
And if your routine is presented with faulty inputs, that careful code
review and testing have shown to be impossible, how do you know there
is still enough data intact to be able to recover? Isn't it better to
just let the software stop dead in its tracks and let a different
system take over (which could mean, do a complete power/reset cycle)?
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bart
|
1/31/2009 9:14:02 AM
|
|
On Jan 31, 3:16 am, thant.tess...@gmail.com wrote:
> What? You'd want to recover from those errors for the same reason
> you'd want to recover from any thrown exception. I don't want my
> program to necessarily die just because it was given bad data.
"Was given" allows you to sidestep the question of who gave your
function the bad data, and in general, the philosophy of throwing
exceptions on what would ordinarily be precondition violations leads
to one not separating precondition checking from input validation.
These two are not the same. In the first case you yourself (or someone
on your team) gave the function bad data because of a bug in the code.
In the second the source of bad data was external to the program and
the invalid input wasn't caused by a bug.
Most functions in the program do not deal with external input, though,
and for them, seeing bad input is a very good indication of a bug.
External input is typically validated and bounced on the "security
perimeter".
There is no need to write every function as if it were receiving input
from an untrustworthy source, and it's expensive to do so. I notice,
for example, that your Env constructor does not validate the l and r
parameters for correctness and does not throw an exception when given
"bad data". Env::Env is implicitly part of the trusted core, so it
doesn't guard against its caller. If you really believe that "the
right thing to do in the case of invalid values is to report an error,
not simply abort", Env::Env is broken. But you don't, and it isn't.
Precondition checking is only superficially related to input
validation. Its siblings are invariant and postcondition checking.
These at least have the advantage of not looking like input validation
and you can't use the "was given bad data" phrase for them. All three
are about detecting bugs, not validating untrusted input (or output);
and in all three cases, some programs might want to try to recover
instead of aborting.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/1/2009 1:43:11 AM
|
|
<thant.tessman@gmail.com> wrote in message
news:f5fb66f2-2dd6-411f-a3eb-051b5eb0102c@i20g2000prf.googlegroups.com...
> On Jan 30, 11:44 am, "Tony" <t...@my.net> wrote:
>> "Thant Tessman" <thant.tess...@gmail.com> wrote in message
>>
>> news:gltmj2$1d3$1@news.xmission.com...
>>
>>
>>
>> > Mathias Gaunard wrote:
>> >> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>> >>> Throwing an exception when a program overflows or underflows or
>> >>> attempts
>> >>> to index beyond the end of an array is not always necessarily about
>> >>> catching it and "continuing happily."
>>
>> >> Those errors should not be recoverable (especially if you want to
>> >> disable the checks to gain efficiency in release builds). Hence you
>> >> shouldn't throw exceptions but abort.
>>
>> > I wrote:
>>
>> > "Whether those errors are recoverable or not depends entirely on what
>> > you're trying to do."
>>
>> > But more importantly, these should be signaled as errors without the
>> > programmer being relied upon to provide an assert.
>>
>> Give example with context please (make it as simple as you can!). [...]
>
> What? You'd want to recover from those errors for the same reason
> you'd want to recover from any thrown exception. I don't want my
> program to necessarily die just because it was given bad data.
Those are not expected errors that can be recovered from. They are
indication of a logic error in the program. You can try to do something like
put up a dialog box in a GUI program to send error information to the
developer, but even that may fail because the state of the program is
unknown when a precondition violation is encountered (assuming the asserts
were left in the release code but handled differently). You have to ensure
that bad data is not passed around. If you have a precondition violation,
there's no telling why. Testing is supposed to weed all that out. Checks
should be in place at a level before a precondition violation occurs because
at the precondition level, a violation is proof that the program is
incorrect (a bug).
>
>> PLHF/SBCK/SBIKBW/GWS/D.
>
> ?
Hehe... Picking the low hanging fruit, should be common knowledge, should be
in the knowledge base or wiki, ???, duh. ;)
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/1/2009 1:46:20 AM
|
|
"Thant Tessman" <thant.tessman@gmail.com> wrote in message
news:gm0oug$2d2$1@news.xmission.com...
> David Abrahams wrote:
>> on Thu Jan 29 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>>
>>> Mathias Gaunard wrote:
>>>> On 29 jan, 08:53, Thant Tessman <thant.tess...@gmail.com> wrote:
>>>>
>>>>> Throwing an exception when a program overflows or underflows or
>>>>> attempts
>>>>> to index beyond the end of an array is not always necessarily about
>>>>> catching it and "continuing happily."
>>>> Those errors should not be recoverable (especially if you want to
>>>> disable the checks to gain efficiency in release builds). Hence you
>>>> shouldn't throw exceptions but abort.
>>> I wrote:
>>>
>>> "Whether those errors are recoverable or not depends entirely on what
>>> you're trying to do."
>>>
>>> But more importantly, these should be signaled as errors without the
>>> programmer being relied upon to provide an assert.
>>
>> There are preconditions at every level of the program. Even if the
>> language itself can catch and signal every condition that the the
>> language itself defines to be erroneous, there will still be good cause
>> for assertions in user code.
>
> Yeah, one would think. I use assertions in C++ all the time, but as I
> said, not to assert that the caller of a function has passed in valid
> values.
Why not?
> The right thing to do in the case of invalid values is to report
> an error, not simply abort.
Assuming you can and can take the risk of executing more code in a program
you know is already in an undefined state.
> I use assertions to give myself confidence
> that a program is doing what I think it's doing.
Most people do, as an aid during development time. Thorough testing through
all code paths with all expected ranges of data is still necessary though.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/1/2009 1:47:11 AM
|
|
Bart van Ingen Schenau wrote:
> Isn't it better to
> just let the software stop dead in its tracks and let a different
> system take over (which could mean, do a complete power/reset cycle)?
No. Robust systems, by definition, need to be robust. Spontaneous
reboots in the field are really not acceptable. The idea that some
local, deeply nested part of the code should presume to crash the whole
application seems irresponsible to me.
If the high-level decision is made that a particular application should
terminate on error, then the place for that decision to be reflected in
code is in the high-level catch block, not in some numeric calculation
routine that got a negative number it didn't expect.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
2/1/2009 1:49:14 AM
|
|
"Seungbeom Kim" <musiphil@bawi.org> wrote in message
news:glvu28$q5k$1@news.stanford.edu...
> Tony wrote:
>> "Seungbeom Kim" <musiphil@bawi.org> wrote in message
>> news:glte63$r5t$1@news.stanford.edu...
>>> Tony wrote:
>>>> "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote in message
>>>> news:687e89e9-918a-4791-9b9f-10e40b23b7d6@41g2000yqf.googlegroups.com...
>>>>> Well, I do think it is OK for constructors to throw. The best example
>>>>> in my project is what you might call an IP address. It can construct
>>>>> itself from a string representation:
>>>>>
>>>>> struct IP_Address
>>>>> {
>>>>> IP_Address (const char *); // throws bad_argument
>>>>> // 64 other member functions
>>>>> } ;
>>>> It looks to me like the throw should be a simple assertion
>>>> (precondition
>>>> argument checks are assertions rule). No exceptions needed: it's
>>>> development-time problem, not a runtime one.
>>> Suppose you write a program that takes an IP address from the user.
>>> How do you ensure that the user input is always valid?
>>
>> Umm... validate it?!
>
> We certainly do. What matters here is, where.
> (1) Check before construction and assert only valid inputs gets into
> the constructor.
> (2) Check during construction and throw on invalid inputs.
>
> Suppose the user gave the IP address in argv[i]. Do you mean:
>
> if (IP_Address::valid(argv[i])) {
> IP_Address host(argv[i]);
> // use host
> }
> else {
> // error
> }
>
> IP_Address::IP_Address(const char* s)
> {
> assert(valid(s));
> // construct *this
> }
>
> Here, we have double calls to IP_Address::valid, and a similar logic
> might be repeated even in the part "// construct *this".
You can have the assert in the IP_Address constructor be a No Op in release
code because it is handled correctly in the mainline. That would be the
traditional approach.
>
> I find this a lot simpler:
>
> try {
> IP_Address host(argv[i]);
> // use host
> }
> catch (invalid_argument& e) {
> // error
> }
>
> IP_Address::IP_Address(const char* s)
> {
> // construct *this, throwing invalid_argument on error
> }
>
That's fine too, it's the C++-ish way. Either is fine. It's just personal or
shop preference/policy. I like the traditional (first) way in this scenario
even if it means moving the validate function out of the address class (you
appear to have it as a static class function) and into a standalone function
because it appears to be a user program where you'll be interacting with the
user some specific way upon error and I avoid hiding in instantiations what
seems better suited to procedural style. Obviously the input is going to
have to be validated, so why hide that in an instantiation? Overall, it just
depends on what you like looking at in the way of code (and if you want to
bring exceptions into the program at all).
>>
>>> Before calling the constructor?
>>
>> What constructor? I don't see a constructor. Did someone see a
>> constructor.
>> APB out for the constructor!
>
> I do see IP_Address::IP_Address(const char *) above.
>
> What's APB?
All Points Bulletin. Police departments issue those when they are looking
for someone. I was asking for an example in a crafty/remote way.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/1/2009 1:57:03 AM
|
|
"Daniel T." <daniel_t@earthlink.net> wrote in message
news:daniel_t-6B895A.22303230012009@earthlink.vsrv-sjc.supernews.net...
> Seungbeom Kim <musiphil@bawi.org> wrote:
>> Tony wrote:
>> > "Le Chaud Lapin" <jaibuduvin@gmail.com> wrote:
>
>> > > Well, I do think it is OK for constructors to throw. The best
>> > > example in my project is what you might call an IP address. It
>> > > can construct itself from a string representation:
>> > >
>> > > struct IP_Address
>> > > {
>> > > IP_Address (const char *); // throws bad_argument
>> > > // 64 other member functions
>> > > } ;
>> >
>> > It looks to me like the throw should be a simple assertion
>> > (precondition argument checks are assertions rule). No exceptions
>> > needed: it's development-time problem, not a runtime one.
>>
>> Suppose you write a program that takes an IP address from the user.
>> How do you ensure that the user input is always valid? Before
>> calling the constructor? Then we're back to the double testing
>> problem.
>>
>> I don't think assert is the right one to use here.
>
> Is the constructor's job to validate user input, construct an IP_Address
> object or both? If your answer is the latter, then I think you need to
> brush up on the single responsibility principle...
When the OP wrote "throws bad_argument", I assumed he was doing a check for
a null pointer rather than IP address validation. The latter can mean
different things for different programs.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/1/2009 1:57:27 AM
|
|
On 2009-02-01 08:49, Jeff Schwab wrote:
> Bart van Ingen Schenau wrote:
>
>> Isn't it better to
>> just let the software stop dead in its tracks and let a different
>> system take over (which could mean, do a complete power/reset cycle)?
>
> No. Robust systems, by definition, need to be robust. Spontaneous
> reboots in the field are really not acceptable. The idea that some
> local, deeply nested part of the code should presume to crash the whole
> application seems irresponsible to me.
>
> If the high-level decision is made that a particular application should
> terminate on error, then the place for that decision to be reflected in
> code is in the high-level catch block, not in some numeric calculation
> routine that got a negative number it didn't expect.
As far as I know (which is not much, on this subject) most robust
systems (at least in safety critical applications) achieve robustness
through redundancy. A classical example is Triple Modular Redundancy
where you perform the same actions/calculations on three different units
(preferably with different implementations) and then have a voting
element which compares the result and terminates/reboots any unit which
gives a diverging example.
It is also common to terminate at the first sign of error since it is
often impossible to determine where the error was introduced. A
calculation error usually depend on bad input, but how do you tell where
the input became bad? How far do you have to backtrack to be sure that
you work with good data? It is safer to just abort and reboot and come
back in a known good state and trust to the redundancy to continue the
operation.
--
Erik Wikstr�m
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
UTF
|
2/1/2009 9:52:56 PM
|
|
Peter Dimov wrote:
> On Jan 31, 3:16 am, thant.tess...@gmail.com wrote:
>
>> What? You'd want to recover from those errors for the same reason
>> you'd want to recover from any thrown exception. I don't want my
>> program to necessarily die just because it was given bad data.
>
> "Was given" allows you to sidestep the question of who gave your
> function the bad data, and in general, the philosophy of throwing
> exceptions on what would ordinarily be precondition violations leads
> to one not separating precondition checking from input validation. [...]
Again I'm stunned that what I'm saying is controversial. Any program or
library you write that you expect to be used by other people can and
will be fed data that might be bad for reasons totally beyond your control.
I regularly use exceptions to signal that the data is bad, i.e. as a
part of "input validation." If this happens deep within a library that
tends to get used by more than one program, a great way to signal these
errors is by throwing an exception. This guarantees that a program won't
continue on with bad data, because the user of the library can't choose
to ignore an error signaled in such a way. And sometimes (but not
always) this is what you really want. At the same time, throwing an
exception allows the user of the library to deal with the fact that
there is bad data without bringing the program to a screeching halt if
they so choose.
How can this possibly be considered controversial?
[...]
> Most functions in the program do not deal with external input, though,
> and for them, seeing bad input is a very good indication of a bug. [...]
If the function is totally internal to some library or utility, then
sure, there's no reason to write it as if it were receiving input from
an untrustworthy source. But I tend to write programs in such a way as
to facilitate reuse as much as possible.
> [...] I notice,
> for example, that your Env constructor does not validate the l and r
> parameters for correctness and does not throw an exception when given
> "bad data". [...]
You're absolutely right. That constructor should be private to the
extend function, and there needs to be a way to safely create an initial
empty environment (dictionary). But at the time I wrote this, the C++
compiler I was using didn't provide any way to declare friends correctly
within templates, because C++ is a big mess of a language and it's
difficult to implement correctly. So yes I should have put an assert
there or thrown an exception.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/1/2009 9:54:44 PM
|
|
On Feb 1, 1:43 am, Peter Dimov <pdi...@gmail.com> wrote:
> Most functions in the program do not deal with external input, though,
> and for them, seeing bad input is a very good indication of a bug.
> External input is typically validated and bounced on the "security
> perimeter".
I would like to steal this phrase and rewrite it as "system perimeter"
if you don't mind.
> There is no need to write every function as if it were receiving input
> from an untrustworthy source, and it's expensive to do so. I notice,
> for example, that your Env constructor does not validate the l and r
> parameters for correctness and does not throw an exception when given
> "bad data". Env::Env is implicitly part of the trusted core, so it
> doesn't guard against its caller. If you really believe that "the
> right thing to do in the case of invalid values is to report an error,
> not simply abort", Env::Env is broken. But you don't, and it isn't.
This is the essence of what I have been trying to say. There is a
difference between bad input at the perimeter and bad design within
the core. This is why I use unsigned int freely within the core. I
know that there is no such thing as a "user" bit-twiddling my objects
and functions.
Note that software engineers have a "luxury" not found in any other
engineering discipline. They are permitted to engage in bad
engineering with relative impunity. Example:
int create_stack (int stack_depth)
{
if (stack_depth < 0)
perror ("Yada yada...");
...
return sd;
}
Suppose this function is deep within the system perimeter. How could
it possibly receive a negative stack depth? Only programmer-error (bad
engineering) can lead to this situation.
An analogue in electrical engineering (no pun intended) would be to
block an over-voltage that could never, ever happen in a properly-
designed circuit:
----[voltage limiter]-----> [voltage-senstive stage]----->
If an electrical engineer were to subscribe to the...
"Use code to check if system is poorly designed"
....princple, there would be voltage limiters everywhere. The circuit
would be a mess. And all these "error checking" would confuse the
other engineers and obscure the structure of the system.
So the very proposition of thorough "error checking" within the core
in the realm of competent engineering is absurd. It says that an
engineer is allowed to:
1. Create a poorly designed program (circuit).
2. Use more code (circuitry) to determine if the program (circuit) was
poorly designed, something that the designed should already know.
The difference here is that the material cost of software is zero. But
the penalty of mindset is still present, and that penalty is a gradual
erosion of the notion that the an engineer must be competent and
deliberate in his actions.
Engineer 1: "You didn't add any blocking diodes here...your circuit is
faulty."
Engineer 2: "No it isn't."
Engineer 1: "Yes it is...one overshoot and this entire output stage is
toast."
Engineer 2: "No overshoot is going to happen."
Engineer 1: "It might. You never know."
Engineer 2: "Actually, I do know."
Engineer 1: "How can you say that?"
Engineer 2: "Because (A), I understand what I am doing, and (B), I
designed the circuit so that it will not happen."
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
2/1/2009 9:56:58 PM
|
|
On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
> The idea that some local, deeply nested part of the code should presume
> to crash the whole application seems irresponsible to me.
Whether a function has the "right" to abort when its precondition is
violated is an interesting question. I don't agree with the absolute
characterization that giving it this right is irresponsible. A
contract that doesn't have teeth decreases the incentive of the caller
to get the preconditions right, so one might argue that it encourages
irresponsibility on the part of the caller.
Out of curiosity, does your style of programming also involve throwing
exceptions on postcondition and invariant violations?
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/1/2009 9:57:53 PM
|
|
David Abrahams wrote:
> I agree that this is an extremely important idea in general, and for
> C/C++ programmers in particular (see Stepanov on Regular Types).
> However, most other languages I've encountered either have no value
> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
> Isn't it strange that such a fundamental principle is inaccessible to
> programmers in those languages?
Perhaps I don't understand what you mean.
UL-USER> (defparameter *x* (cons 'key 13))
*X*
UL-USER> *x*
(KEY . 13)
UL-USER> (defun f (cons) (incf (cdr cons)))
F
UL-USER> (f *x*)
14
UL-USER> *x*
(KEY . 14)
UL-USER>
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Damien
|
2/1/2009 9:59:42 PM
|
|
Daniel T. wrote:
> Seungbeom Kim <musiphil@bawi.org> wrote:
>> Suppose you write a program that takes an IP address from the user.
>> How do you ensure that the user input is always valid? Before
>> calling the constructor? Then we're back to the double testing
>> problem.
>>
>> I don't think assert is the right one to use here.
>
> Is the constructor's job to validate user input, construct an IP_Address
> object or both? If your answer is the latter, then I think you need to
> brush up on the single responsibility principle...
The constructor's job is to construct an object, which may entail
validating the input needed to construct the object. Since the two
may not be those that can be carried out very independently, this
seems like a wrong situation for the single responsibility principle.
For example, what do you think of the question: is the job of fopen()
to open the file, or to check for the ability to open it? Of course,
its main job is to open the file, but that process entails various
checks such as whether the file exists, whether the user has the
permission to open the file, and so on. Arguing for the "separation
of responsibilities" here makes a design that yields a silly code like:
const char* fn = argv[i];
if (!fexists(fn)) {
// error: file not exists
}
else if (!privileged(PRIV_OPEN_READ, fn)) {
// error: the user does not have the permission
}
else ...
else {
// finally we know we can open the file
// but if somehow we cannot, because for example we failed
// to check for something, or something changed between the
// checking and the opening, then the behaviour is undefined!!
FILE* fp = fopen(fn, "r");
// let's hope this succeeded
assert(fp);
// use fp
fclose(fp);
}
The try-first approach leads to a much safer and much more efficient code:
const char* fn = argv[i];
if (FILE* fp = fopen(fn, "r")) {
// succeeded to open the file
// use fp
fclose(fp);
}
else {
// figure out the reason for the error (as necessary)
switch (errno) {
case ... : ...
default : ...
}
}
--
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
|
2/2/2009 1:22:51 PM
|
|
"Jeff Schwab" <jeff@schwabcenter.com> wrote in message
news:3e2dnUj4aLi64hnUnZ2dnUVZ_rjinZ2d@giganews.com...
> Bart van Ingen Schenau wrote:
>
>> Isn't it better to
>> just let the software stop dead in its tracks and let a different
>> system take over (which could mean, do a complete power/reset cycle)?
>
> No. Robust systems, by definition, need to be robust. Spontaneous
> reboots in the field are really not acceptable. The idea that some
> local, deeply nested part of the code should presume to crash the whole
> application seems irresponsible to me.
>
> If the high-level decision is made that a particular application should
> terminate on error, then the place for that decision to be reflected in
> code is in the high-level catch block, not in some numeric calculation
> routine that got a negative number it didn't expect.
How do you know whether it's an input error, or a logic error, or a
strange magnetic field that flipped some bits because your yacht is
traveling through the Bermuda triangle?
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/2/2009 1:22:52 PM
|
|
Peter Dimov wrote:
> On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
>
>> The idea that some local, deeply nested part of the code should presume
>> to crash the whole application seems irresponsible to me.
>
> Whether a function has the "right" to abort when its precondition is
> violated is an interesting question. I don't agree with the absolute
> characterization that giving it this right is irresponsible.
I'll note that positing that said right is a given is an equally
absolute characterization.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/2/2009 1:23:03 PM
|
|
Thant Tessman wrote:
> Peter Dimov wrote:
>> On Jan 31, 3:16 am, thant.tess...@gmail.com wrote:
>>
>>> What? You'd want to recover from those errors for the same reason
>>> you'd want to recover from any thrown exception. I don't want my
>>> program to necessarily die just because it was given bad data.
>>
>> "Was given" allows you to sidestep the question of who gave your
>> function the bad data, and in general, the philosophy of throwing
>> exceptions on what would ordinarily be precondition violations leads
>> to one not separating precondition checking from input validation. [...]
>
> Again I'm stunned that what I'm saying is controversial. Any program or
> library you write that you expect to be used by other people can and
> will be fed data that might be bad for reasons totally beyond your control.
>
The problem is that it seems it got a bit lost what you said :-)
As I see it we have to distinguish between (unrecoverable) errors on the
C++ language level and errors we can recover from on our code level.
On the language level, C++ defines UB for certain situations and as far
as I can understand this, every compiler is free to just cause
process-abort if such a situation is encountered. This is C++ -- and we
can do noting about it at the moment. (As you - IMHO - correctly stated:
"There is a point of view that considers "undefined behavior" to be
something to avoid in the design of a programming language.")
However, it seems to me that this "mindset" also is used by a lot of
people here to justify not checking for errors "that must not happen"
(Notice how I use "must not" and not "can not").
For me, the classical example is std::vector<>::operator[] - it does not
check it's parameter and will just a) happily corrupt your program state
or b) cause detectable UB on the language level, i.e. try to access
memory that is not accessible and thus (most likely and in this case
also for the best) cause process-abort/core dump.
Many people here seem to consider this to be a good default behaviour
for all internal functions because "if someone passes garbage it's a
program bug and we have to process-abort".
I do not agree that "we have to" because *if* we check our input
parameters for validity we have *not* entered the realm of UB and as
such are free to do whatever we want - possibly throwing logic_error.
This checking comes at a cost however and I think the controversies stem
from the fact that it's highly project dependent if you go for
no-checking-but-faster or checking-but-slower approach.
Another take at it: You *will* have bugs in some parts of your program,
no matter how much you test. It's not always acceptable to abort or
likely cause a crash in such situations. For these cases you check the
input parameters "that must not be wrong".
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
2/2/2009 1:24:23 PM
|
|
On 1 f�v, 08:49, Jeff Schwab <j...@schwabcenter.com> wrote:
> Bart van Ingen Schenau wrote:
>
> > Isn't it better to
> > just let the software stop dead in its tracks and let a different
> > system take over (which could mean, do a complete power/reset cycle)?
>
> No. Robust systems, by definition, need to be robust. Spontaneous
> reboots in the field are really not acceptable. The idea that some
> local, deeply nested part of the code should presume to crash the whole
> application seems irresponsible to me.
It's simple: such systems are either tested enough to prove
empirically the precondition is never violated, or they actually use
static analysis to really prove that it cannot be (for critical
systems).
Of course, no program should ever violate a precondition. It's a bug,
not a runtime error.
{ Mod comment: Donald Knuth once wrote: "Beware of bugs in the above code; I
have only proved it correct, not tried it." -mod/aps }
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Mathias
|
2/2/2009 1:29:51 PM
|
|
Peter Dimov wrote:
> Out of curiosity, does your style of programming also involve throwing
> exceptions on postcondition and invariant violations?
No. I use asserts there, purely as documentation. It's a good
question, though.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
2/2/2009 1:30:28 PM
|
|
Tony wrote:
> "Thant Tessman" <thant.tessman@gmail.com> wrote in message
> news:gm0oug$2d2$1@news.xmission.com...
>> [...] I use assertions in C++ all the time, but as I
>> said, not to assert that the caller of a function has passed in valid
>> values.
>
> Why not?
Because...
>> The right thing to do in the case of invalid values is to report
>> an error, not simply abort.
>
> Assuming you can and can take the risk of executing more code in a program
> you know is already in an undefined state.
If the invalid value genuinely produces an undefined state, then throw
an exception. If the invalid value produces a state defined to be merely
erroneous, report an error and continue on. For example, in OpenGL,
there are many ways of not providing correct values, in which case the
wrong thing--or nothing--will appear on the screen. But OpenGL is not
allowed to crash your program no matter what you pass it.
[...]
Note that all the above applies only to functions where I want to
promote reuse. If a function is private to a utility or library, then
yes, what I describe above can be overkill.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/2/2009 1:30:48 PM
|
|
On Feb 2, 5:54 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Again I'm stunned that what I'm saying is controversial. Any program or
> library you write that you expect to be used by other people can and
> will be fed data that might be bad for reasons totally beyond your control.
So, a program must be robust in the face of bad or unexpected input.
Fine. But you then make the leap that every(*) function of this
program must obviously be robust in the face of bad input, and that
the only way to write a robust program is to compose it from robust
functions. _This_ is controversial, and you present no evidence for
this claim. I maintain that a robust program can be composed from
components that are intolerant of bad input, and that (taking into
account the complete program life cycle) this approach leads to more
robust programs, on average.
(*) You actually qualify this with "used by other people", and clarify
that
> If the function is totally internal to some library or utility, then
> sure, there's no reason to write it as if it were receiving input from
> an untrustworthy source. But I tend to write programs in such a way as
> to facilitate reuse as much as possible.
But - objectively - it shouldn't matter whether two functions are
written by the same person. If you have a program in which function F
calls function G, passing it bad input, an objective metric or style
guideline cannot depend on whether F and G have different authors.
Note that the intuitive reasoning that you should throw an exception
to the party responsible for the bad data breaks down when the
exception propagates upwards from F to its caller, who has no idea
what this exception is about because he didn't pass bad data to F.
There may well be psychological reasons to prefer G throwing an
exception such as empirical evidence that this style leads to more
robust programs when the authors are different, but these reasons
cannot be derived from first principles and cannot be claimed to be
obvious.
You also continue to ignore the other two parts of design by contract:
postconditions and invariants. Do you throw an exception when a
postcondition or an invariant is violated? Does this make sense to
you? Again, the intuitive reasoning of throwing an exception at the
party responsible for the bad data doesn't work in this context.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/2/2009 1:31:06 PM
|
|
Seungbeom Kim <musiphil@bawi.org> writes:
>if (!fexists(fn)) {
> // error: file not exists
(...)
>else {
> // finally we know we can open the file
> FILE* fp = fopen(fn, "r");
> // let's hope this succeeded
The filesystem might have changed in the meantime.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
ram
|
2/3/2009 12:06:25 AM
|
|
On Feb 2, 9:23 pm, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:
> Peter Dimov wrote:
> > On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
>
> >> The idea that some local, deeply nested part of the code should presume
> >> to crash the whole application seems irresponsible to me.
>
> > Whether a function has the "right" to abort when its precondition is
> > violated is an interesting question. I don't agree with the absolute
> > characterization that giving it this right is irresponsible.
>
> I'll note that positing that said right is a given is an equally
> absolute characterization.
No. The opposite of "no function has the right to crash" is "some
functions (namely, those with preconditions) have the right to crash".
The difference in absolutism between the two should be obvious.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/3/2009 12:07:07 AM
|
|
David Abrahams wrote:
> If you give a person a lifejacket, he may be able to stay afloat long
> enough to improvise a response to an unanticipated emergency situation,
> in which case full recovery is a possibility. Since software can only
> handle the situations the programmer can anticipate, the same reasoning
> doesn't apply.
Hardware (including lifejackets) and software (including exceptions) are
no different in this sense. The presence of a life jacket proves that
someone foresaw the potential need for it. The same is true of a
throw-expression.
We don't give people life jackets in the hope that the bearers will
somehow innovate their way out of watery deaths. We hope that they
might be rescued. Sometimes this works, and sometimes it doesn't.
Similarly, even crippled software can still be useful, if only to
support interactive debugging. Just as a sick patient has a far greater
chance of recovery than a dead one, an ailing application beats the heck
out of a core dump. Debugging a core dump is the software equivalent of
an autopsy. Once a program dies, the most you can hope for is to learn
enough from the death to avoid similar problems in the future.
When the Remote Agent software on Deep Space 1 detected an internal
inconsistency, the engineers on the ground were able to debug the cause
(a race condition), and salvage the mission. Had the original
programmers chosen to terminate the application, rather than entering an
interactive debugger, the $100M mission might have been a complete loss.
The opinion some people express, when pressed, is that somehow
they don't really mean the whole system should die, only the
misbehaving portion. I'm guessing most of those people have never done
defense work, nor embedded systems development, nor even Unix device
drivers. Intentionally terminating the executable is rarely the best
option.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
2/3/2009 12:12:24 AM
|
|
Seungbeom Kim <musiphil@bawi.org> wrote:
> Daniel T. wrote:
> > Seungbeom Kim <musiphil@bawi.org> wrote:
> > > Suppose you write a program that takes an IP address from the
> > > user. How do you ensure that the user input is always valid?
> > > Before calling the constructor? Then we're back to the double
> > > testing problem.
> > >
> > > I don't think assert is the right one to use here.
> >
> > Is the constructor's job to validate user input, construct an
> > IP_Address object or both? If your answer is the latter, then I
> > think you need to brush up on the single responsibility
> > principle...
>
> The constructor's job is to construct an object, which may entail
> validating the input needed to construct the object. Since the two
> may not be those that can be carried out very independently, this
> seems like a wrong situation for the single responsibility principle.
Validating *user input* can and should be carried out independently of
constructing any particular object.
> For example, what do you think of the question: is the job of
> fopen() to open the file, or to check for the ability to open it?
But is it fopen's job to validate user input? I think not.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Daniel
|
2/3/2009 12:13:06 AM
|
|
Peter Dimov wrote:
> On Feb 2, 5:54 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>
>> Again I'm stunned that what I'm saying is controversial. Any program or
>> library you write that you expect to be used by other people can and
>> will be fed data that might be bad for reasons totally beyond your control.
>
> So, a program must be robust in the face of bad or unexpected input.
> Fine. But you then make the leap that every(*) function of this
> program must obviously be robust in the face of bad input, and that
> the only way to write a robust program is to compose it from robust
> functions. _This_ is controversial, and you present no evidence for
> this claim.
You're restating things in terms of absolutes to make it sound like I'm
saying something I'm not.
Let's try again:
Sometimes (usually) it is important for a program to be robust in the
face of bad or unexpected input.
Sometimes (often) a program is not robust in the face of bad or
unexpected input because one of its components is not robust in the face
of bad or unexpected input.
Therefore, one way to help make a program more robust in the face of bad
or unexpected input is to make sure its components are robust in the
face of bad or unexpected input.
One last point: All else being equal, a more robust program is better
than a less robust program.
> I maintain that a robust program can be composed from
> components that are intolerant of bad input, and that (taking into
> account the complete program life cycle) this approach leads to more
> robust programs, on average.
I would claim instead that it is impractical in C++ to insure that a
program's components are all robust in the face of bad or unexpected
input. Consequently, C++ programmers have developed alternative
techniques for helping insure programs written in C++ are as robust as
they can practically be in those situations when the importance of
robustness rises above the work needed to achieve it.
I would also claim that there are other languages (SML) in which it is
*not* impractical to insure that a program's components are all robust
in the face of bad or unexpected input. The weird thing is that SML's
type system almost forces you to build your programs this way.
> But - objectively - it shouldn't matter whether two functions are
> written by the same person. [...]
I was going to add a footnote that, for this discussion's sake, "two
people" can be one person separated by a sufficiently significant period
of time, but I thought the point was simple enough that this was
unnecessary.
> Note that the intuitive reasoning that you should throw an exception
> to the party responsible for the bad data breaks down when the
> exception propagates upwards from F to its caller, who has no idea
> what this exception is about because he didn't pass bad data to F.
Who said the exception is being thrown to the party responsible for the
bad data?
You're tasked with writing a library that will read in a file (or stream
or whatever) and do something useful with it. Say that it is possible
that the data could contain various kinds of errors such that it would
be a Bad Thing (TM) to continue on with whatever computation the library
happened to be performing. Say that such errors happen deep within the
library and that trying to propagate the error up through the call stack
would have a perverse effect on the otherwise beautiful code you've
written. More than that, the error is so bad, you really don't want
other programmers who use your library to get away with ignoring the error.
Say you want this library to be as useful as possible in as many
situations as possible. It might get used in a one-shot utility that has
no reason to do anything other than halt upon encountering bad data. It
might get used in a gigantic, continuous, elaborately interactive
application that better not go down just because it tried to read in
some file produced by someone else's buggy application.
As I said before, the great thing about exceptions is that they abort a
computation, but in a way that allows the program to do something about
it IF THE PROGRAMMER SO CHOOSES. Whether the caller is also the one
responsible for the bad data or not is irrelevant. In fact, the caller
will usually NOT be the one responsible for the bad data.
It is expected that the exceptions that a library can throw are
well-documented parts of its API so you can do different things based on
different errors, but you certainly also want the option to catch all
exceptions regardless of how unexpected they are. Just because an
exception might be triggered by a genuine bug in the code doesn't mean
the programmer using your library doesn't want to be able to recover.
[...]
> You also continue to ignore the other two parts of design by contract:
> postconditions and invariants. [...]
Design by contract is nothing but a band-aid for the lack of a good type
system.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/3/2009 12:15:12 AM
|
|
On Feb 3, 12:06 am, r...@zedat.fu-berlin.de (Stefan Ram) wrote:
> Seungbeom Kim <musip...@bawi.org> writes:
> >if (!fexists(fn)) {
> > // error: file not exists
> (...)
> >else {
> > // finally we know we can open the file
> > FILE* fp = fopen(fn, "r");
> > // let's hope this succeeded
>
> The filesystem might have changed in the meantime.
That's why Seungbeom wrote:
// finally we know we can open the file
// but if somehow we cannot, because for example we failed
// to check for something, or something changed between the
// checking and the opening, then the behaviour is
undefined!!
....to say that the file system might have changed in the meantime. :)
Also, Seungbeom's argument is equally applicable in the context of
serialization:
Socket s;
Foo f; // A Foo is massively complex data structure containing member
lists, maps, etc.
s >> f;
What now? Should f refuse to construct itself from the data coming out
of s until it has verified such data is "safe"?
Even if the answer were "yes", what would the code look like?
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Le
|
2/3/2009 9:09:06 AM
|
|
Daniel T. wrote:
> Seungbeom Kim <musiphil@bawi.org> wrote:
>> Daniel T. wrote:
>>> Seungbeom Kim <musiphil@bawi.org> wrote:
>
>>>> Suppose you write a program that takes an IP address from the
>>>> user. How do you ensure that the user input is always valid?
>>>> Before calling the constructor? Then we're back to the double
>>>> testing problem.
>>>>
>>>> I don't think assert is the right one to use here.
>>> Is the constructor's job to validate user input, construct an
>>> IP_Address object or both? If your answer is the latter, then I
>>> think you need to brush up on the single responsibility
>>> principle...
>> The constructor's job is to construct an object, which may entail
>> validating the input needed to construct the object. Since the two
>> may not be those that can be carried out very independently, this
>> seems like a wrong situation for the single responsibility principle.
>
> Validating *user input* can and should be carried out independently of
> constructing any particular object.
>
>> For example, what do you think of the question: is the job of
>> fopen() to open the file, or to check for the ability to open it?
>
> But is it fopen's job to validate user input? I think not.
>
Both the fopen example and the IP address example seem to me cases where
indeed the function responsible for opening/constructing *are*
validating the user input (buffer length stuff aside). Because in both
cases it's possible a function gets a string directly entered by the
user and will try to "open" something. If it fails for whatever reason
the program can then process the error.
What validation would you add that fopen does not already do?
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
2/3/2009 9:09:30 AM
|
|
Peter Dimov wrote:
> On Feb 2, 9:23 pm, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> wrote:
>> Peter Dimov wrote:
>>> On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
>>>> The idea that some local, deeply nested part of the code should presume
>>>> to crash the whole application seems irresponsible to me.
>>> Whether a function has the "right" to abort when its precondition is
>>> violated is an interesting question. I don't agree with the absolute
>>> characterization that giving it this right is irresponsible.
>> I'll note that positing that said right is a given is an equally
>> absolute characterization.
>
> No. The opposite of "no function has the right to crash" is "some
> functions (namely, those with preconditions) have the right to crash".
> The difference in absolutism between the two should be obvious.
Well the difference in absolutism becomes less obvious if you rephrase
the statements as:
"a program has the right to call a function without potentially crashing"
"no program has the right to call a function without potentially crashing"
It all depends whether the focus is the program or the function. To
invalidate one or another seductive conjecture that falls into one
extreme, it's enough to encounter exceptions to either rule; I have.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/3/2009 9:14:07 AM
|
|
On Feb 3, 8:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Sometimes (usually) it is important for a program to be robust in the
> face of bad or unexpected input.
A program should always be robust in the face of bad or unexpected
input, but this is not what the discussion is about. It is about
programs that receive good input (input that is not documented as
invalid) but fail because a component of the program receives bad
input as the result of a bug or a logic error. The question is which
is the better way to fail: loudly, with a "crash", or less loudly,
with throwing an exception upstream.
> You're tasked with writing a library that will read in a file (or stream
> or whatever) and do something useful with it.
This library has no preconditions (except being passed a valid stream
object or whatever), so it's not allowed to crash (except when passed
an invalid stream object), no matter what the contents of the stream.
It is also not allowed to produce invalid results, produce no results
at all, hang or corrupt the memory space. It may well do those things
if it contains bugs, and the question is which of our two design
approaches is the better way to produce a library that doesn't contain
bugs.
> Design by contract is nothing but a band-aid for the lack of a good type
> system.
Is it? One learns something new every day. :-)
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/4/2009 12:34:24 AM
|
|
Thant Tessman wrote:
> Peter Dimov wrote:
>> On Feb 2, 5:54 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>>
>>> Again I'm stunned that what I'm saying is controversial. Any program
>>> or library you write that you expect to be used by other people can
>>> and will be fed data that might be bad for reasons totally beyond
>>> your control.
>>
>> So, a program must be robust in the face of bad or unexpected input.
>> Fine. But you then make the leap that every(*) function of this
>> program must obviously be robust in the face of bad input, and that
>> the only way to write a robust program is to compose it from robust
>> functions. _This_ is controversial, and you present no evidence for
>> this claim.
>
> You're restating things in terms of absolutes to make it sound like
> I'm saying something I'm not.
>
> Let's try again:
>
> Sometimes (usually) it is important for a program to be robust in the
> face of bad or unexpected input.
Complete program, yes. Single function, where input == function
arguments, not usually but only sometimes.
>
> Sometimes (often) a program is not robust in the face of bad or
> unexpected input because one of its components is not robust in the
> face of bad or unexpected input.
Here you can easily replace 'Sometimes (often)' with 'Always'.
If all components making up a system are robust against faulty input,
then the entire system must also be robust.
>
> Therefore, one way to help make a program more robust in the face of
> bad or unexpected input is to make sure its components are robust in
> the face of bad or unexpected input.
>
> One last point: All else being equal, a more robust program is better
> than a less robust program.
The problem is, robustness usually comes at a price.
And if that price is so high that I am unable to pay it, I would rather
sacrifice some robustness of internal components (those that don't
interact with the outside world) and double-check the robustness and
correct functioning of the components on the outside.
<snip>
>> Note that the intuitive reasoning that you should throw an exception
>> to the party responsible for the bad data breaks down when the
>> exception propagates upwards from F to its caller, who has no idea
>> what this exception is about because he didn't pass bad data to F.
>
> Who said the exception is being thrown to the party responsible for
> the bad data?
>
> You're tasked with writing a library that will read in a file (or
> stream or whatever) and do something useful with it. Say that it is
> possible that the data could contain various kinds of errors such that
> it would be a Bad Thing (TM) to continue on with whatever computation
> the library happened to be performing. Say that such errors happen
> deep within the library and that trying to propagate the error up
> through the call stack would have a perverse effect on the otherwise
> beautiful code you've written. More than that, the error is so bad,
> you really don't want other programmers who use your library to get
> away with ignoring the error.
I don't think there is any disagreement about using exceptions is these
situations, where the erroneous input is generated outside the
development team's control.
Now, in the exact same library, it is found that an internal calculation
for an index has an off-by-one error. The calculation for the index is
in no way affected by the data read in from the file (or whatever).
Do you say that this off-by-one error must also be reported, with an
exception, to the application using the library? Or would you rather
have a bug report from the developers of that application that your
library has a problem?
<snip>
> -thant
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bart
|
2/4/2009 12:35:23 AM
|
|
On Feb 3, 5:14 pm, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:
> Well the difference in absolutism becomes less obvious if you rephrase
> the statements as:
>
> "a program has the right to call a function without potentially crashing"
>
> "no program has the right to call a function without potentially crashing"
The actual statement (from the assert/DbC school point of view) is
"A correct program has the right to call a function without crashing."
where a correct program is defined as one without contract violations.
The exception point of view is
"Every program has the right to call a function without crashing."
(These of course exclude the obvious counterexample of a function
whose sole documented purpose is to crash.)
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/4/2009 12:36:31 AM
|
|
>"Jeff Schwab" <jeff@schwabcenter.com> Hardware (including lifejackets) and
>software (including exceptions) are
> no different in this sense. The presence of a life jacket proves that
> someone foresaw the potential need for it. The same is true of a
> throw-expression.
>
> We don't give people life jackets in the hope that the bearers will
> somehow innovate their way out of watery deaths. We hope that they
> might be rescued. Sometimes this works, and sometimes it doesn't.
But lifejackets do not cause danger of their own, while a C++ software
getting in an unforseen situation CAN be.
> Similarly, even crippled software can still be useful, if only to
> support interactive debugging.
We here are talking about live systems used by users. For real tasks like
processing money on a bank account, receptor data in system control, etc.
Interactive debugging is out of question.
> Just as a sick patient has a far greater
> chance of recovery than a dead one,
So does he more chance to spread smallpox or ebola is left walking.
> an ailing application beats the heck
> out of a core dump. Debugging a core dump is the software equivalent of
> an autopsy. Once a program dies, the most you can hope for is to learn
> enough from the death to avoid similar problems in the future.
Err, assert failure dumps core alright (or can be made so). If you throw
exception you destroy the environment by unwinding the stack and walking up
to a far "handler". That may even let the program go ahead. What more you
learn from that?
> When the Remote Agent software on Deep Space 1 detected an internal
> inconsistency, the engineers on the ground were able to debug the cause
> (a race condition), and salvage the mission. Had the original
> programmers chosen to terminate the application, rather than entering an
> interactive debugger, the $100M mission might have been a complete loss.
This supports our point -- they did not let the program proceed.
You seem to misunderstand or flex the terms we are using.
1. "unexpected". That supposed to mean condition the design said will not
happen.
The cases where exceptions are thrown are "expected", and included in the
defined behavior of the function. Sure, there are such
funcitons/situations, we're talking about the *rest*.
2. "terminate immediately" means the normal execution in the original
environment does not continue. It can still mean doing other safe
actions -- i.e hibernate the process, using assembly to call some OS
functions, flip I/O ports, switch to another VM, etc.
> The opinion some people express, when pressed, is that somehow
> they don't really mean the whole system should die, only the
> misbehaving portion.
Sure, we don't want annihilate Earth or start a new big bang. :) The
problem is that in a C/C++ system there is too little chance to isolate a
misbehaving part. The baseline is to think the process being the the unit
to kill on a unix/win32-like system with process separation. If you could
create a true sandbox that is smaller, sure, go ahead with the rest.
> I'm guessing most of those people have never done
> defense work, nor embedded systems development, nor even Unix device
> drivers.
I'm quite sure most people who contributed to this thread did average 2+
decades high-demand work, including in those very environments or close
analogs and more.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Balog
|
2/4/2009 12:40:48 AM
|
|
On Feb 2, 10:12 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
> David Abrahams wrote:
> > If you give a person a lifejacket, he may be able to stay afloat long
> > enough to improvise a response to an unanticipated emergency situation,
> > in which case full recovery is a possibility. Since software can only
> > handle the situations the programmer can anticipate, the same reasoning
> > doesn't apply.
>
> Hardware (including lifejackets) and software (including exceptions) are
> no different in this sense. The presence of a life jacket proves that
> someone foresaw the potential need for it. The same is true of a
> throw-expression.
>
> We don't give people life jackets in the hope that the bearers will
> somehow innovate their way out of watery deaths. We hope that they
> might be rescued. Sometimes this works, and sometimes it doesn't.
> Similarly, even crippled software can still be useful, if only to
> support interactive debugging. Just as a sick patient has a far greater
> chance of recovery than a dead one, an ailing application beats the heck
> out of a core dump. Debugging a core dump is the software equivalent of
> an autopsy. Once a program dies, the most you can hope for is to learn
> enough from the death to avoid similar problems in the future.
>
> When the Remote Agent software on Deep Space 1 detected an internal
> inconsistency, the engineers on the ground were able to debug the cause
> (a race condition), and salvage the mission. Had the original
> programmers chosen to terminate the application, rather than entering an
> interactive debugger, the $100M mission might have been a complete loss.
I see this discussion a lot here on this forum. The last big thread on
topic this I recall was debug and release asserts.
I will make the simple claim that sometimes release asserts make
sense, that is, error checking in release builds which kill the
process with great prejudice when the check fails. In other words, I
claim that it is not practical to have every single function be
"robust".
One prime example is mutexes. It is possible to program your system
such that acquiring a mutex will add an edge to a graph. If there ever
is a cycle in this mutex-acquire graph, then we have a possibility of
deadlock, and the mutex acquire can appropriately fail by error
return / exception / etc. However, such behavior would be entirely
unacceptable for performance critical applications. We are talking
about magnitudes of speed reduction if we make mutex acquires be
"robust" in this fashion. Mutexes are thus not a "robust" component
under your definition. This is the correct state of affairs for
performance critical applications IMO.
Another thing to note is that sometimes error checking may catch
program corruption and not simply "bad input". Program corruption is
unrecoverable, and generally the best course of action is to kill the
process asap after reporting the error as best you can.
Finally, there's the question of programmer sanity. You could design
every single component to be robust, but then your program will become
unmaintainable, a horrible mess to write, etc. A good example is the
following, a simple copy file program in pseudo-code.
acquire file handle 1
acquire file handle 2
copy from file 1 to file 2
release file handle 2
release file handle 1
Now, with a "robust" interface that doesn't crash which handles every
conceivable error, tell me how you propose how to handle if releasing
file handle 2 fails? Return an error code / throw an exception?
Wonderful. What about releasing file handle 1? What if that also
fails? All of your functions now need to return a collection of errors
if you cannot crash and must handle all conceivable errors.
Writing a program which successfully does this is incredibly taxing on
programmer time. It is far better to write programs which assert and
have a test suite to catch most of the problems. It will result in
faster coding time, aka better time to market and less cost to
produce, probably be more bug free, aka actually more robust, it will
have better runtime performance, it will be more easily maintained,
etc.
On Feb 2, 10:12 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
> The opinion some people express, when pressed, is that somehow
> they don't really mean the whole system should die, only the
> misbehaving portion. I'm guessing most of those people have never done
> defense work, nor embedded systems development, nor even Unix device
> drivers. Intentionally terminating the executable is rarely the best
> option.
Sure. If you can, use a process model instead of a thread model, then
more power to you. This is what Erik Wikstr�m <Erik-
wikst...@telia.com> wrote on Feb 1, 7:52 pm discussed in this thread
when he remarked that truly critical embedded systems to get
robustness do not do what you say, but instead have redundant backup
systems, analogous to a "process model" instead of a "threading
model". Validate all communication across the process boundary, but
crash on any bad communication inside a single process.
As I said above, the problem is that in the face of some unexpected
error condition, we don't know if the process has been compromised, so
we need to take it down. This is an ugly side of C++. Java, for
example, is different. It's rather hard to actually corrupt a Java
process using only Java code. (Perhaps impossible?) It's rather easy
to corrupt a C++ process, and you do not want a corrupted C++ process
running any longer than it has to to attempt logging and die.
In your example of the space probe, if the system was corrupted, the
corrupted device drivers could have fried the actual hardware through
misuse or activated the rockets wasting precious fuel. Your remote
debugger example I would surmise was enabled using a process model
where the broken module with the race condition was debuggable from
the still working "control" module.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
joshuamaurice
|
2/4/2009 12:40:50 AM
|
|
"Thant Tessman" <thant.tessman@gmail.com> wrote in message
news:gm7526$88p$1@news.xmission.com...
> Tony wrote:
>> "Thant Tessman" <thant.tessman@gmail.com> wrote in message
>> news:gm0oug$2d2$1@news.xmission.com...
>>> [...] I use assertions in C++ all the time, but as I
>>> said, not to assert that the caller of a function has passed in valid
>>> values.
>>
>> Why not?
>
> Because...
>
>>> The right thing to do in the case of invalid values is to report
>>> an error, not simply abort.
>>
>> Assuming you can and can take the risk of executing more code in a
>> program
>> you know is already in an undefined state.
>
> If the invalid value genuinely produces an undefined state,
The program is either incorrect or some other force acted upon the software
(or hardware!) when a precondition violation is detected: the bad thing
already occurred for whatever unknown reason. The function who's
precondition is violated hasn't the foggiest idea of why that happened, and
shouldn't make any assumptions. If you are handling the similar, but not
nearly identical case, where you are not asserting but rather using
exceptions, then it's not a precondition violation: it's an expected
(plausible) scenario that you (or some other handler) are handling.
You could use exceptions to do assertion/precondition checking, but then you
don't have the opportunity to turn off those checks in release code and
also, why use exceptions when you know that those violations are defined to
be not handle-able and shouldn't be handled? Some user of the function could
try to handle the violation and then you have code that is potentially and
likely less reliable from a paradigmic (coding style) level. Using
exceptions where assertions should be used could be a sign of cognitive
laziness in that instead of analyzing the situtation, someone used an
exception as a crutch (and put the poor user of the function at risk).
Use whatever mechanism is appropriate, but realize assertions and exceptions
solve different problems and code is more comprehensible by having distinct
syntax. By "asserting", you are saying to the user of the function that they
must ensure that certain things will never occur because the writer of the
function expects it to NOT occur and the function was coded with that in
mind (read: violate that contract and off to la la land your program could
go at worst or terminate at best). OTOH, when you raise and exception, you
are saying that while some certain thing is not likely to occur, it is
plausible that it could occur (disk full, for example), we have knowledge of
the error scenario and that something at a higher level with more knowledge
of the program context can be reasonably expected to handle the error and
potentially recover from the error.
[...]
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/4/2009 6:34:01 AM
|
|
Bart van Ingen Schenau wrote:
> Thant Tessman wrote:
>
>> Peter Dimov wrote:
>>> On Feb 2, 5:54 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>>>
>>>> Again I'm stunned that what I'm saying is controversial. Any program
>>>> or library you write that you expect to be used by other people can
>>>> and will be fed data that might be bad for reasons totally beyond
>>>> your control.
> <snip>
>>> Note that the intuitive reasoning that you should throw an exception
>>> to the party responsible for the bad data breaks down when the
>>> exception propagates upwards from F to its caller, who has no idea
>>> what this exception is about because he didn't pass bad data to F.
>> Who said the exception is being thrown to the party responsible for
>> the bad data?
>>
>> You're tasked with writing a library that will read in a file (or
>> stream or whatever) and do something useful with it. Say that it is
>> possible that the data could contain various kinds of errors such that
>> it would be a Bad Thing (TM) to continue on with whatever computation
>> the library happened to be performing. Say that such errors happen
>> deep within the library (...) the error is so bad,
>> you really don't want other programmers who use your library to get
>> away with ignoring the error.
>
> I don't think there is any disagreement about using exceptions is these
> situations, where the erroneous input is generated outside the
> development team's control.
>
> Now, in the exact same library, it is found that an internal calculation
> for an index has an off-by-one error. The calculation for the index is
> in no way affected by the data read in from the file (or whatever).
>
When you say "it is found" - what exactly do you mean?
Let's assume someone actually put a check there that makes it logically
possible to determine this off-by-one error ...
> Do you say that this off-by-one error must also be reported, with an
> exception, to the application using the library? Or would you rather
> have a bug report from the developers of that application that your
> library has a problem?
>
*If* the code was able to detect some internal consistency problem,
then, by all means it *should* throw an exception. What else should it
do? Call abort(). exit(1)?? We had some libraries that did that. I tell
you, IMHO that's really a Bad Thing.
The application developer can still tell you you have a bug, because he
got a logic_error exception that either a) crashed his application
because it's not caught or b) he "logs" an unexpected error from the
library and can continue with other important stuff.
Of course the question is: Will someone have bothered to put in checking
logic that catches such internal inconsistency errors?
br,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Martin
|
2/4/2009 6:35:59 AM
|
|
"Seungbeom Kim" <musiphil@bawi.org> wrote in message
news:gm5rlk$5nh$1@news.stanford.edu...
> Daniel T. wrote:
>> Seungbeom Kim <musiphil@bawi.org> wrote:
>>> Suppose you write a program that takes an IP address from the user.
>>> How do you ensure that the user input is always valid? Before
>>> calling the constructor? Then we're back to the double testing
>>> problem.
>>>
>>> I don't think assert is the right one to use here.
>>
>> Is the constructor's job to validate user input, construct an IP_Address
>> object or both? If your answer is the latter, then I think you need to
>> brush up on the single responsibility principle...
>
> The constructor's job is to construct an object, which may entail
> validating the input needed to construct the object.
The word 'may' in the above sentence is key: it's entirely
context/developer/design-specific. There is no general rule about
construction and data validation.
> Since the two
> may not be those that can be carried out very independently, this
> seems like a wrong situation for the single responsibility principle.
There's that word again ('may'). A generality is not applicable as it is a
context-specific issue.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/4/2009 6:36:15 AM
|
|
"Bart van Ingen Schenau" <bart@ingen.ddns.info> wrote in message
news:2637213.OZ2STcON7f@ingen.ddns.info...
> I don't think there is any disagreement about using exceptions is these
> situations, where the erroneous input is generated outside the
> development team's control.
Contraire. If the bad input is within a foreseen scenario, then use 'try'.
If not, use 'assert'. For example, if the bad input data to a function was
such that it was determined to be caused by use of a old version of a DLL
and the effects are known not to leave the program in a corrupt state, one
could perhaps put up a dialog to tell the user to reinstall the program and
then leave the program running for the user to save their work. OTOH, if a
null ptr was passed to a function and the precondition states that an arg
must not be null, well then what may likely occur is: an error dialog/stack
trace if in debug mode, or indeed a crash (not a terminate()) when the ptr
is dereferenced if the assertions were removed in a release build.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/4/2009 6:37:16 AM
|
|
On Feb 4, 2:35 pm, "Martin T." <0xCDCDC...@gmx.at> wrote:
> *If* the code was able to detect some internal consistency problem,
> then, by all means it *should* throw an exception. What else should it
> do? Call abort(). exit(1)?? We had some libraries that did that. I tell
> you, IMHO that's really a Bad Thing.
It is indeed a bad thing under some circumstances, which is why
exceptionism not only persists, but is winning.
It is a Bad thing when the library has so many bugs that it routinely
aborts, you have no access to its source or are not qualified enough
to fix it, and its developers are unwilling or unable to maintain it
properly.
Assertionism assumes a desire and ability to promptly fix the bugs
that are exposed by the asserts. The failure is made as visible as
possible in the hope that it will not be tolerated.
Exceptionism allows you to work around some of the failures without
modifying the library. You like the degree of control that you retain,
but the hidden cost is that the library is less likely to be fixed.
Yes, in theory, you could have the best of both worlds...
> The application developer can still tell you you have a bug, because he
> got a logic_error exception that either a) crashed his application
> because it's not caught or b) he "logs" an unexpected error from the
> library and can continue with other important stuff.
.... you could have a library that throws exceptions on unexpected
failures whose bugs are fixed at the same speed as they would be had
it asserted instead. But this doesn't happen in practice. The two
properties aren't orthogonal. Even when all of the information
required to identify and debug the failure is retained when the
exception is thrown, the library developer is still less likely to
receive a bug report for a failure that can be worked around, and
failures that have a workaround have lower priority in the issue
tracker.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/5/2009 2:30:34 AM
|
|
Peter Dimov wrote:
> On Feb 3, 8:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>
>> Sometimes (usually) it is important for a program to be robust in the
>> face of bad or unexpected input.
>
> A program should always be robust in the face of bad or unexpected
> input,
Sometimes a program only needs to work once, or only under controlled or
non-critical circumstances. In that situation, and given limited time
and resources, the extra effort put into making a program or library
robust may be inappropriate.
(Having said that, there do seem to be too many programmers that don't
bother with robustness even when it doesn't take much effort.)
[...]
>> You're tasked with writing a library that will read in a file (or stream
>> or whatever) and do something useful with it.
>
> This library has no preconditions (except being passed a valid stream
> object or whatever), so it's not allowed to crash (except when passed
> an invalid stream object), no matter what the contents of the stream.
No, it's not allowed to crash even when passed an invalid stream or
object. That's the whole point. If your library was built assuming valid
data, passing it invalid data may result in a crash, or it may result in
incorrect results which may or may not be obvious, or it may set your
disk on fire. What matters is the confidence the user of your library
can have that the library itself won't disguise or exacerbate bugs
elsewhere in the system. And why should someone using your library be
burdened with duplicating a lot of the work put into the library merely
to verify data is valid before passing it to your library?
And the chance that a component will disguise or exacerbate bugs
elsewhere in the system goes up not with the number of components in the
system, but with the number of connections between the components.
That's why asserts are the wrong tool for the job, and why "undefined
behavior" in particular is something worth going to great lengths to
preclude from your system.
[...]
>
>> Design by contract is nothing but a band-aid for the lack of a good type
>> system.
>
> Is it? One learns something new every day. :-)
Yes, it is. A good type system will tend to allow you to capture those
'preconditions' with the type system itself.
I once wrote a C++ library designed to facilitate time calculations. It
distinguished values specifying a given absolute moment in time from
values specifying an interval. You could subtract absolutes to get an
interval. You could add an interval to an absolute getting another
absolute, and so on. The type system itself helps make sure that the
programmer doesn't try to perform calculations that don't make any
logical sense.
This is just a simple example, but it makes the point. A good type
system is a tool for creating a 'language' for 'talking' about a given
problem domain in a way that allows the compiler to enforce logical
consistency.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/5/2009 2:33:03 AM
|
|
Tony wrote:
> "Thant Tessman" <thant.tessman@gmail.com> wrote in message
> news:gm7526$88p$1@news.xmission.com...
[...]
>> If the invalid value genuinely produces an undefined state,
>
> The program is either incorrect or some other force acted upon the software
> (or hardware!) when a precondition violation is detected: the bad thing
> already occurred for whatever unknown reason.
The "bad thing" might be something like: "The 3D geometric animation
file contains out-of-order key-frame data." This is a 'precondition'
that the user of your library has no business being responsible for
enforcing.
The rest of your point I addressed in another post.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/5/2009 2:34:31 AM
|
|
On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Design by contract is nothing but a band-aid for the lack of a good type
> system.
// Contract: Function returns true iff 'v' represents
// the SHA-1 hash of the specified string.
//
bool checkHash(const std::string & str, const vector<uint8_t> & v);
Oh man, I'd love to see that contract expressed in a type system.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/5/2009 2:34:59 AM
|
|
Marsh Ray wrote:
> On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>> Design by contract is nothing but a band-aid for the lack of a good type
>> system.
>
> // Contract: Function returns true iff 'v' represents
> // the SHA-1 hash of the specified string.
> //
> bool checkHash(const std::string & str, const vector<uint8_t> & v);
>
> Oh man, I'd love to see that contract expressed in a type system.
This is not a contract by my understanding. If it were, someone using
checkHash is expected to only pass in 'v' such that it represents the
SHA-1 hash of 'str'. And checkHash is only allowed to return true. If
the precondition isn't met, checkHash is allowed to return anything, or
not return at all, or set your disk on fire.
And you could express the notion of a valid SHA-1 hash of a string in
the type system by creating a separate class representing such.
struct Invalid {};
struct Validated;
boost::variant<Invalid,Validated> validate(std::string const & str);
struct Validated {
std::str const getValue() const {return value;}
private:
Validated(std::string const & s) : value(s) {}
friend boost::variant<Invalid,Validated>
validate(std::string const &);
std::string value;
};
/* implement validate here */
With the above, anyone manipulating a Validated knows that the compiler
has confirmed that the value it contains can only have been produced by
a call to the 'validate' function.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/5/2009 8:06:19 AM
|
|
Thant Tessman wrote:
> Marsh Ray wrote:
>> On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>>> Design by contract is nothing but a band-aid for the lack of a good type
>>> system.
>>
>> // Contract: Function returns true iff 'v' represents
>> // the SHA-1 hash of the specified string.
>> //
>> bool checkHash(const std::string & str, const vector<uint8_t> & v);
>>
>> Oh man, I'd love to see that contract expressed in a type system.
>
> This is not a contract by my understanding. If it were, someone using
> checkHash is expected to only pass in 'v' such that it represents the
> SHA-1 hash of 'str'.
My mistake. The "if and only if" implies that it indeed must return
false otherwise.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/6/2009 1:49:19 AM
|
|
On Feb 5, 10:33 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Peter Dimov wrote:
> >> You're tasked with writing a library that will read in a file (or stream
> >> or whatever) and do something useful with it.
>
> > This library has no preconditions (except being passed a valid stream
> > object or whatever), so it's not allowed to crash (except when passed
> > an invalid stream object), no matter what the contents of the stream.
>
> No, it's not allowed to crash even when passed an invalid stream or
> object.
In this context, an example of an invalid stream object is *
(std::istream*)0. The library will crash. This doesn't mean that it's
also allowed to crash when the stream object is valid but the library
doesn't like the data extracted from it.
> That's why asserts are the wrong tool for the job, [...]
You have simply cited a situation in which assert is the wrong tool
for the job. Such situations do exist. Dealing with external input is
one trivial example; handling run time errors coming from external
source such as the OS is another.
Return to the part you conveniently omitted and assume that the input
IS valid. Is the library allowed to throw an "input_invalid"
exception?
> >> Design by contract is nothing but a band-aid for the lack of a good type
> >> system.
>
> > Is it? One learns something new every day. :-)
>
> Yes, it is. A good type system will tend to allow you to capture those
> 'preconditions' with the type system itself.
>
> I once wrote a C++ library designed to facilitate time calculations. It
> distinguished values specifying a given absolute moment in time from
> values specifying an interval. You could subtract absolutes to get an
> interval.
This library does have a contract that is not checked by the type
system. One (small) part of it is that when subtracting two absolutes
you get an interval describing their difference, not merely any random
interval. Equating design by contract with the set of preconditions
that can be checked by the type system is absurd.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/6/2009 1:49:20 AM
|
|
On Feb 5, 4:06 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
> Marsh Ray wrote:
> > On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> >> Design by contract is nothing but a band-aid for the lack of a good type
> >> system.
>
> > // Contract: Function returns true iff 'v' represents
> > // the SHA-1 hash of the specified string.
> > //
> > bool checkHash(const std::string & str, const vector<uint8_t> & v);
>
> > Oh man, I'd love to see that contract expressed in a type system.
>
> This is not a contract by my understanding. If it were, someone using
> checkHash is expected to only pass in 'v' such that it represents the
> SHA-1 hash of 'str'. And checkHash is only allowed to return true.
Your understanding of design by contract is flawed. The function does
have a contract, despite having no preconditions. The existence of a
precondition as such is not necessary for a contract to be in place;
without a precondition, the caller has no obligations, only the callee
does. You can remove the assertable preconditions and replace them
with a guarantee that states that a specific exception will be thrown,
and this would still be a contract. You can also transform
preconditions into invariants, starting from:
// pre: v.size() == 20
// returns: true iff v is the SHA-1 hash of str
bool checkHash(const std::string & str, const vector<uint8_t> & v);
and replacing it with
// returns: true iff v is the SHA-1 hash of str
bool checkHash(const std::string & str, const SHA1 & v);
where SHA1 is
class SHA1
{
vector<uint8_t> data_;
};
and SHA1::data_.size() == 20 is an invariant of SHA1.
In the first case, checkHash is allowed to assert( v.size() == 20 );
in the second, it is allowed to assert( v.invariant() ). In both
cases, there asserts Should Never Fire but sometimes will, likely
because of a bug in the code that produces SHA-1 hashes that has not
been caught by the test suite.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/6/2009 2:01:48 AM
|
|
On Feb 5, 8:06 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Marsh Ray wrote:
> > On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> >> Design by contract is nothing but a band-aid for the lack of a good type
> >> system.
>
> > // Contract: Function returns true iff 'v' represents
> > // the SHA-1 hash of the specified string.
> > //
> > bool checkHash(const std::string & str, const vector<uint8_t> & v);
>
> > Oh man, I'd love to see that contract expressed in a type system.
>
> This is not a contract by my understanding. If it were, someone using
> checkHash is expected to only pass in 'v' such that it represents the
> SHA-1 hash of 'str'. And checkHash is only allowed to return true. If
> the precondition isn't met, checkHash is allowed to return anything, or
> not return at all, or set your disk on fire.
It was off-the-cuff and admittedly informal and under-specified. But
any reasonable programmer would get the meaning.
'iff' -> if and only if
Another reason I'd love to see it expressed in a general type system.
> And you could express the notion of a valid SHA-1 hash of a string in
> the type system by creating a separate class representing such.
>
> struct Invalid {};
> struct Validated;
>
> boost::variant<Invalid,Validated> validate(std::string const & str);
>
> struct Validated {
> std::str const getValue() const {return value;}
> private:
> Validated(std::string const & s) : value(s) {}
> friend boost::variant<Invalid,Validated>
> validate(std::string const &);
> std::string value;
>
> };
Just so I understand, is the std::string in your example meant to hold
some representation of a hash? I was using a vector<uint8_t> for that.
The contract wasn't simply that it was _a_ SHA-1 hash (any sequence of
20 bytes will do for that), it was that the function returned true iff
it was the correct hash for that string. Something might be used for
password checking.
> /* implement validate here */
>
> With the above, anyone manipulating a Validated knows that the compiler
> has confirmed that the value it contains can only have been produced by
> a call to the 'validate' function.
While I think that approach has great utility and I look forward to
using it sometime, in this case it seems like you're just pushing the
problem around.
My points were simply:
1. Software 'contracts' can add value even with a turing-complete type
system like C++.
2. If someone were to produce a way to validate that function's
semantics at compile time, I'd love to see it.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/6/2009 2:04:38 AM
|
|
"Thant Tessman" <thant.tessman@gmail.com> wrote in message
news:gmcqjc$vcq$1@news.xmission.com...
> Tony wrote:
>> "Thant Tessman" <thant.tessman@gmail.com> wrote in message
>> news:gm7526$88p$1@news.xmission.com...
>
> [...]
>
>>> If the invalid value genuinely produces an undefined state,
>>
>> The program is either incorrect or some other force acted upon the
>> software
>> (or hardware!) when a precondition violation is detected: the bad thing
>> already occurred for whatever unknown reason.
>
> The "bad thing" might be something like: "The 3D geometric animation
> file contains out-of-order key-frame data." This is a 'precondition'
> that the user of your library has no business being responsible for
> enforcing.
Your reply is non sequitor: the example given was ptr != null. The violated
function doesn't care why it was violated, it just knows that it was
(something bad happened) which means it will act as documented (in this
scenario, attempt to terminate()). I distinguish critical errors (should be
impossible for all practical purposes by the time the software goes to
release) from handle-able ones (errors still checked for and handled as
gracefully as possible in release code). If you don't, that's fine: YMMV.
But it seems like it may be one of those "if you only have a hammer..."
things.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/6/2009 7:39:13 AM
|
|
Peter Dimov wrote:
> On Feb 5, 10:33 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>> Peter Dimov wrote:
>
>>>> You're tasked with writing a library that will read in a file (or stream
>>>> or whatever) and do something useful with it.
>>> This library has no preconditions (except being passed a valid stream
>>> object or whatever), so it's not allowed to crash (except when passed
>>> an invalid stream object), no matter what the contents of the stream.
>> No, it's not allowed to crash even when passed an invalid stream or
>> object.
>
> In this context, an example of an invalid stream object is *
> (std::istream*)0. The library will crash. This doesn't mean that it's
> also allowed to crash when the stream object is valid but the library
> doesn't like the data extracted from it.
It's the very notion of preconditions that are checked with asserts that
I want to call into question. I've already had this conversation with
Andrei, but a well-designed programming language wouldn't provide any
way to create a null pointer in the first place. The way such a language
could get away with this is that it provides alternative ways of
expressing the structure values are allowed to have. See my Validated
example in response to Marsh Ray. In C++ this kind of thing is awkward,
but in SML it's very clean and natural because it was designed into the
type system from the beginning.
[...]
> This library does have a contract that is not checked by the type
> system. One (small) part of it is that when subtracting two absolutes
> you get an interval describing their difference, not merely any random
> interval. Equating design by contract with the set of preconditions
> that can be checked by the type system is absurd.
How does one enforce such a 'contract'? If this is design by contract,
what makes it different from mere 'design'? What makes the contract
anything other than documentation?
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/6/2009 12:35:42 PM
|
|
Peter Dimov wrote:
[...]
> Your understanding of design by contract is flawed. The function does
> have a contract, despite having no preconditions. The existence of a
> precondition as such is not necessary for a contract to be in place;
> without a precondition, the caller has no obligations, only the callee
> does. [...]
Here is the nub of the matter. I posit that placing obligations on the
caller is almost always something to be avoided if failing to meet those
obligations results in undefined behavior. And asserts don't eliminate
undefined behavior. I also posit that a well-designed language will help
allow the author of a function to avoid placing obligations on the caller.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/7/2009 11:42:53 AM
|
|
Marsh Ray wrote:
[...]
> Just so I understand, is the std::string in your example meant to hold
> some representation of a hash? I was using a vector<uint8_t> for that.
Sorry, I wrote that pre-coffee. I meant 'validate' to do basically the
same thing as your 'checkHash' but in a way that leveraged the type
system to make it difficult to misuse. That is, once you had an instance
of 'Validated' you knew that the string inside represented a validated
string. That way, a function that really only wanted a validated string
would take as an argument an instance of a 'Validated' which you can
only get from a successful call to 'validate.' And the variant forces
the programmer to deal with the case where 'validate' might fail.
[...]
> 2. If someone were to produce a way to validate that function's
> semantics at compile time, I'd love to see it.
Neither type systems, nor design-by-contract can validate a function's
semantics. They can only make it more likely that invalid semantics will
be detected. Here's the difference: Design-by-contract will tend to
expose invalid semantics at runtime as a violation of a pre- or
post-condition, wereas a type system will tend to expose invalid
semantics at compile time as a type violation. Maybe there is a role for
both, but in my experience the latter is far more effective given a good
type system.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/7/2009 11:43:52 AM
|
|
Thant Tessman wrote:
> Peter Dimov wrote:
>> On Feb 5, 10:33 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>>> Peter Dimov wrote:
>>
>>>>> You're tasked with writing a library that will read in a file (or
>>>>> stream
>>>>> or whatever) and do something useful with it.
>>>> This library has no preconditions (except being passed a valid stream
>>>> object or whatever), so it's not allowed to crash (except when passed
>>>> an invalid stream object), no matter what the contents of the stream.
>>> No, it's not allowed to crash even when passed an invalid stream or
>>> object.
>>
>> In this context, an example of an invalid stream object is *
>> (std::istream*)0. The library will crash. This doesn't mean that it's
>> also allowed to crash when the stream object is valid but the library
>> doesn't like the data extracted from it.
>
> It's the very notion of preconditions that are checked with asserts that
> I want to call into question. I've already had this conversation with
> Andrei, but a well-designed programming language wouldn't provide any
> way to create a null pointer in the first place. The way such a language
> could get away with this is that it provides alternative ways of
> expressing the structure values are allowed to have. See my Validated
> example in response to Marsh Ray. In C++ this kind of thing is awkward,
> but in SML it's very clean and natural because it was designed into the
> type system from the beginning.
I believe you are massively overselling said technique. Your example
suggests that you essentially need to encode many, most, or all valid
states of a value as distinct types. Please use the technique you used
in Validated to design a correct Stack type that never fails and does
not use contracts. Without having going through it, my prediction is
that you'll need to encode each size of the stack as a separate type. If
so, you're allowed to stop at 100 :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
|
2/7/2009 11:46:02 AM
|
|
On Feb 6, 8:35 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
> It's the very notion of preconditions that are checked with asserts that
> I want to call into question. I've already had this conversation with
> Andrei, but a well-designed programming language wouldn't provide any
> way to create a null pointer in the first place.
Yes, I know. Nevertheless, you can create an invalid non-trivial
object in any language. Consider the simplistic example of
int depth( Tree t );
'depth' has the implicit precondition that t is a tree. The type
system can guarantee that it's a Tree, and that it was created and
manipulated by dedicated Tree-specific functions. But it cannot guard
against a bug in one of these functions that causes its return value
to be a cyclic graph. 'depth' will likely loop forever or cause a
stack overflow, and it's "not allowed" to do that, if one diligently
follows the principle that any input is valid.
Precondition checking is a tool that catches bugs. If 'depth' had a
check that t is a tree, it would've detected the bug in the Tree-
specific function. But the caller of 'depth' is not allowed to rely on
this test being present, because t is never supposed to be a non-tree
in a correct program.
[...]
> How does one enforce such a 'contract'?
With tests and asserts (which are a form of testing, performed on a
live program).
> If this is design by contract, what makes it different from mere 'design'?
Expressing the contract in a formal way that can be checked by a
machine.
> What makes the contract anything other than documentation?
Enforcement.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/7/2009 11:46:34 AM
|
|
On Fri, 6 Feb 2009 at 12:35:42, in comp.lang.c++.moderated, Thant
Tessman wrote:
>Peter Dimov wrote:
<snip>
>> This library does have a contract that is not checked by the type
>> system. One (small) part of it is that when subtracting two absolutes
>> you get an interval describing their difference, not merely any random
>> interval. Equating design by contract with the set of preconditions
>> that can be checked by the type system is absurd.
>
>How does one enforce such a 'contract'? If this is design by contract,
>what makes it different from mere 'design'? What makes the contract
>anything other than documentation?
The contract is between the person who writes the calling code and the
person who writes the function's code. Whoever breaks the contract gets
the sack. It doesn't matter how or when the non-conformance is
discovered.
Note that some contract requirements can't be vetted by C++ code. For
instance, that an object has been deleted.
The difference between the 'contract' and 'documentation' is the
nit-picking insistence that the documentation shall tell the truth come
hell or high water.
John
--
John Harris
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
John
|
2/8/2009 2:05:43 PM
|
|
"Peter Dimov" <pdimov@gmail.com> wrote in message
news:4896cfca-50e8-45da-9810-e765744e41b1@r36g2000prf.googlegroups.com...
> On Feb 6, 8:35 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
>> It's the very notion of preconditions that are checked with asserts that
>> I want to call into question.
To me, precondition checking via asserts follows directly from "the concept"
of "precondition/precondition checking".
>> I've already had this conversation with
>> Andrei, but a well-designed programming language wouldn't provide any
>> way to create a null pointer in the first place.
>
> Yes, I know. Nevertheless, you can create an invalid non-trivial
> object in any language. Consider the simplistic example of
>
> int depth( Tree t );
>
> 'depth' has the implicit precondition that t is a tree. The type
> system can guarantee that it's a Tree, and that it was created and
> manipulated by dedicated Tree-specific functions. But it cannot guard
> against a bug in one of these functions that causes its return value
> to be a cyclic graph. 'depth' will likely loop forever or cause a
> stack overflow, and it's "not allowed" to do that, if one diligently
> follows the principle that any input is valid.
>
> Precondition checking is a tool that catches bugs.
That's how I (others apparently don't) grok/use the term/concept.
> If 'depth' had a
> check that t is a tree, it would've detected the bug in the Tree-
> specific function. But the caller of 'depth' is not allowed to rely on
> this test being present, because t is never supposed to be a non-tree
> in a correct program.
>
> [...]
>
>> How does one enforce such a 'contract'?
>
> With tests and asserts (which are a form of testing, performed on a
> live program).
I would say "testing and asserts". "tests" sounds like using exceptions and
to me that changes the nature of the situation (read, that reclassifies from
"precondition" to "EH" concepts).
>
>> If this is design by contract, what makes it different from mere
>> 'design'?
>
> Expressing the contract in a formal way that can be checked by a
> machine.
I so hate that "DbC" veil over common techniques as I do not ascribe to any
such formalism. (I had to say that).
>
>> What makes the contract anything other than documentation?
>
> Enforcement.
Actually, no, because the contract could be implicit as in passing the
correct type and value of args to functions. There will, of course,
eventually be "enforcement" even if some function doesn't assert on the args
or some such thing (as in, the dereferenced null ptr will crash the
program). I wouldn't call the so called "contract" "documentation" though,
though it largely could be just that (does one really have to explicitely
say: "arg ptr must not be null"?).
So, I think "contract" and "enforcement" are orthogonal. "Contract" and
"consequence(s)" though are hardly so!
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/8/2009 2:14:16 PM
|
|
"Peter Dimov" <pdimov@gmail.com> wrote in message
news:957a5830-d6fe-41d1-a7ed-e30a04087b84@g39g2000pri.googlegroups.com...
> On Feb 5, 4:06 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
>> Marsh Ray wrote:
>> > On Feb 3, 12:15 am, Thant Tessman <thant.tess...@gmail.com> wrote:
>> >> Design by contract is nothing but a band-aid for the lack of a good
>> >> type
>> >> system.
>>
>> > // Contract: Function returns true iff 'v' represents
>> > // the SHA-1 hash of the specified string.
>> > //
>> > bool checkHash(const std::string & str, const vector<uint8_t> & v);
>>
>> > Oh man, I'd love to see that contract expressed in a type system.
>>
>> This is not a contract by my understanding. If it were, someone using
>> checkHash is expected to only pass in 'v' such that it represents the
>> SHA-1 hash of 'str'. And checkHash is only allowed to return true.
>
> Your understanding of design by contract is flawed. The function does
> have a contract, despite having no preconditions.
You apparently meant "precondition checks", yes?
> The existence of a
> precondition as such is not necessary for a contract to be in place;
Same question as above (just for the record).
> without a precondition, the caller has no obligations, only the callee
> does.
(Same "question" (should be "precondition check/violation handling").
The caller has NO obligations as far as precondition checking goes. If the
caller violates the preconditions (whether implicit or explicit), it could
be "lethal". Niagra falls is not for going over a barrel in. If you choose
to do that, "say your prayers" (most likely: kiss your a.. buh bye!).
> it You can remove the assertable preconditions and replace them
> with a guarantee that states that a specific exception will be thrown,
> and this would still be a contract.
I would say that that act reclassifies "precondition" to "error". (Which may
immediately raise thoughts in some minds about the word 'exception' and how
it seems like it is "more severe" than "error". Isn't it ironic?).
[snipped invariant "tangent"]
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/8/2009 2:20:27 PM
|
|
Andrei Alexandrescu wrote:
> Thant Tessman wrote:
>> [...] I've already had this conversation with
>> Andrei, but a well-designed programming language wouldn't provide any
>> way to create a null pointer in the first place. The way such a language
>> could get away with this is that it provides alternative ways of
>> expressing the structure values are allowed to have. [...]
>
> I believe you are massively overselling said technique. Your example
> suggests that you essentially need to encode many, most, or all valid
> states of a value as distinct types. Please use the technique you used
> in Validated to design a correct Stack type that never fails and does
> not use contracts. [...]
I've appended a Standard ML implementation of a Stack. Note that the
built-in lists in SML provide the functionality of stacks already, so
what I provide below is something SML programmers probably wouldn't
bother with, but it is a good illustration of how to leverage the type
system to codify a data structure and its associated transformations.
Many things to note:
Structures in SML are more like namespaces in C++, only they can have
type parameters that apply to the whole namespace (structure).
The signature STACK describes what a stack implementation must look like
from the user's point of view. The 'push' function for example takes a
stack and a value of type 'a and returns a new stack. (Any variable
preceded by a single tick is a type parameter.)
ListStack is a structure (namespace) that satisfies the STACK signature.
It implements a stack using SML's built-in notion of lists. Like the Env
(dictionary) example I provided before, it happens to take the
'functional' approach to stacks. The 'push' function returns a new
stack. Any variable elsewhere in the program assigned to a previous
stack doesn't see the new stack. It sees the stack as it was assigned to
them.
It is possible to provide an 'imperative' implementation of a stack that
satisfies the STACK signature, but to do so in SML requires the use of
explicit references. So while SML supports imperative programming
techniques, unintentional aliasing basically never happens.
The type system forced me to deal with a couple quirks of stacks. The
first is: What do you do if you call pop on an empty stack? I chose to
simply return an empty stack. I could have chosen to throw an exception.
There are other things I could have done, but the type system would not
have allowed me to do nothing. It would have produced a compile-time
error. In SML, undefined behavior is not an option.
The second situation is: What do you do if you call 'top' on an empty
stack? I chose to make the 'top' function return not a value, but a
value wrapped in an 'option' datatype. It is semantically something like:
boost::variant<void,T>
In C++, 'decoherencing' a variant means building a class and writing
member functions for each of the possible types. In SML it's a mere case
statement (or even more conveniently, pattern matching). Again, I could
have thrown an exception. But whatever design approach is most
appropriate to a given situation, the compiler would not have allowed
the situation to go unaddressed.
There is a statement:
structure MyStack :> STACK = ListStack;
What this does is set the MyStack structure equal to the ListStack
structure. But it does two more things: First, it insures that MyStack
satisfies the STACK signature. Second, it makes MyStack's signature
opaque. No one can use MyStack in a way that goes beyond what's
described in the STACK signature. That is, no one can use MyStack in any
way that relies on its particular implementation. (If the :> was simply
a :, then it would have only checked that the STACK signature was
satisfied.)
(One truly amazing aspect of SML's type system I have no need for in
this example is its support of what it calls 'functors.' Functors are
structures parameterized by other structures.)
Summary:
Not a contract in sight, yet not only am I confident that my stack
implementation is bug-free, but I am also confident that anyone using my
stack implementation will be assisted by the type system in its bug-free
use.
I have even implemented things in SML just to help verify the design of
a C++ implementation of the same thign. An example is described here:
http://preview.tinyurl.com/csqd4y
-thant
***Basic Stack Implementation in Standard ML***
signature STACK =
sig
type 'a stack
val new : unit -> 'a stack
val top : 'a stack -> 'a option
val push : ('a stack * 'a) -> 'a stack
val pop : 'a stack -> 'a stack
val length : 'a stack -> int
end
structure ListStack =
struct
type 'a stack = 'a list
fun new () = []
fun top [] = NONE
| top (v :: _) = SOME v
fun push (s, v) = v :: s
fun pop [] = []
| pop (_ :: rest) = rest
fun length s = List.length s
end
structure MyStack :> STACK = ListStack;
val my_stack : int MyStack.stack = MyStack.new ();
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/8/2009 2:56:59 PM
|
|
On Feb 7, 7:42 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
> Here is the nub of the matter. I posit that placing obligations on the
> caller is almost always something to be avoided if failing to meet those
> obligations results in undefined behavior. And asserts don't eliminate
> undefined behavior.
That much is clear, but the "input validation" style arguments you
cited do not, in my opinion, provide any support for this claim. Bad
input simply is not allowed to trickle down to the basic building
blocks such as stack::pop and vector::operator[] in a well-designed
program, and a logic error that causes an attempt to pop an item from
an empty stack rarely has anything to do with bad input on the program
level.
Or, to illustrate the matter with an example, is this:
$ eval 1+2
Assertion failed: !data_.empty()
Segmentation fault, core dumped
$
worse than this:
$ eval 1+2
Exception: attempted pop from an empty stack
$
?
The exceptionist says that it obviously is, because the second program
is more robust. The assertionist says that both programs are broken,
and that instead of making a broken program more robust, we should
spend our resources toward this goal:
$ eval 1+2
3
$
because lack of brokenness dominates robustness.
> I also posit that a well-designed language will help
> allow the author of a function to avoid placing obligations on the caller.
This is true.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/8/2009 4:20:19 PM
|
|
On Feb 8, 10:56 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
[description of a stack implementation in SML with top returning x|
nil]
> Summary:
>
> Not a contract in sight, ...
The contract is precisely what makes a Stack a stack. It basically
consists of
(top (push s x)) == x
(pop (push s x)) == s
(length (push s x)) == (length s) + 1
(length (new)) == 0
and, in your case,
(top (new)) == nil
(pop (new)) == (new)
The first three are postconditions of push; the last three -
postconditions of new.
> yet not only am I confident that my stack implementation is bug-free, ...
You shouldn't be; there is nothing that guarantees it. (Assuming that
we're using the stack example as a proxy for something less trivial,
with an implementation that isn't as easy to verify against the
contract by mere visual inspection.)
> but I am also confident that anyone using my stack implementation will
> be assisted by the type system in its bug-free use.
As specified, there is no (formal) non-bug-free use of the stack as
every operation is always valid. There are buggy uses that the type
system cannot catch, of course, such as the program popping an item
when it shouldn't.
The goal of using preconditions by choice (efficiency and memory
safety aside) is to make it statistically more likely for such a buggy
program to expose itself as containing a logic error by violating a
precondition... and the goal of asserting preconditions, instead of
replacing them with exceptions, is to make it statistically less
likely for the bug to remain unfixed.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/8/2009 10:38:44 PM
|
|
Thant Tessman wrote:
> The type system forced me to deal with a couple quirks of stacks. The
> first is: What do you do if you call pop on an empty stack? I chose to
> simply return an empty stack. I could have chosen to throw an exception.
> There are other things I could have done, but the type system would not
> have allowed me to do nothing. It would have produced a compile-time
> error. In SML, undefined behavior is not an option.
But this was the entire charge. All you did was to fail successfully,
and you're trying to convince that you actually didn't by redefining the
charge. This will not go well.
You said:
"Design by contract is nothing but a band-aid for the lack of a good
type system."
This is as point-blank as it ever gets. Now I'm not an expert in DbC,
but I did read a few articles and a book about it, the book being
"Design by Contract, by Example" (I'd unrecommend it, but I digress.)
Now from what I've read, the consummate example of DbC, the "Hello
World" of DbC, the pick up line that gets you a date in the DbC world is
the Stack type. And without a doubt, the Stack example is chock-full of
various pre/post-conditions and invariants. A simple preconditions says
that Stack can't be empty before you pop(). A more complicated one is
that Stack after a push is just like the Stack before the push plus the
new thing at the top. (Now that's some expensive checking.)
Having been exposed to the Stack example, and having read your
statement, well... something wasn't ringing quite true. So I asked. And
surprise-surprise, nicely in the middle of you you explain how you
couldn't really do anything interesting when popping from an empty stack.
So what's needed now is some amount of qualifying of your initial
statement. You now claim that this:
fun pop [] = []
| pop (_ :: rest) = rest
obviates the need for design by contract because it forces you to write
what happens when the stack is empty. But the contract is _there_, and
it states that pop from an empty stack returns empty. People will still
attempt to pop from empty stacks, and that will still compile and run.
So the type system hasn't help you zilch in designing a Stack type that
doesn't need a contract. You can at most claim that using pattern
matching clarified that you need to make the contract explicit, instead
of relying on the system's enforcement of the contract (e.g. an "out of
bound array" or "null dereference" in Java). That's a fine claim, but
it's not the original claim.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/8/2009 10:39:47 PM
|
|
on Thu Feb 05 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Yes, it is. A good type system will tend to allow you to capture those
> 'preconditions' with the type system itself.
So you plan to design a numeric type that doesn't include zero? Or are
you planning to leave out division?
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/8/2009 10:40:19 PM
|
|
on Sat Feb 07 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> Precondition checking is a tool that catches bugs.
Even more than that, preconditions are a tool that help us to reason
about the correctness of programs. It's an _advantage_ to have
preconditions and to be able to classify some programs as illegal on
that basis.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:09:10 AM
|
|
on Sat Feb 07 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>> 2. If someone were to produce a way to validate that function's
>> semantics at compile time, I'd love to see it.
>
> Neither type systems, nor design-by-contract can validate a function's
> semantics.
Thank you. Preconditions (and postconditions and invariants) describe
semantics.
> They can only make it more likely that invalid semantics will
> be detected. Here's the difference: Design-by-contract will tend to
> expose invalid semantics at runtime as a violation of a pre- or
> post-condition, wereas a type system will tend to expose invalid
> semantics at compile time as a type violation. Maybe there is a role for
> both, but in my experience the latter is far more effective given a good
> type system.
Fortunately we can have both. And we do, in C++. The type system isn't
lacking, if you only bother to leverage it. Nobody is suggesting that
we give up the available guarantees of a strong type system, but as you
correctly note, the power of type systems to ensure correctness is
limited. You need semantic contracts in order to reason about program
behavior and correctness, and violations of those contracts are not
truly recoverable conditions.
I don't care if you decide that the shipping behavior of violated
asserts in your system must be to throw, though I would advise strongly
against it. However, if you actually try to write your programs to
attempt to somehow recover and continue from such an assertion
exception, you are truly lost. Then your entire system will be riddled
with code designed to account for conditions that can actually never
happen, and of course that code will never be tested. Oh, but then all
that extra untested code makes it so you can't reason about the code
that does get executed, and leads to bugs, and the conditions that could
never happen before start to happen, thereby justifying all the
defensive programming you did. It's a vicious cycle, and I know it from
personal experience. The only cure is to rigorously define the semantic
requirements and guarantees of your program: the preconditions,
postconditions, and invariants (and when things are that much of a mess,
you really need to check them religiously, too).
One of the courses I teach has a section where I discuss program
correctness. When I ask who in the room knows what preconditions,
postconditions, and invariants are, maybe 15% raise their hands. So
I've stopped wondering why so much software is buggy. "The system" (be
it education, professional culture, whatever) isn't preparing people to
think powerfully about correctness.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:11:20 AM
|
|
on Sun Feb 01 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
>
>> The idea that some local, deeply nested part of the code should presume
>> to crash the whole application seems irresponsible to me.
>
> Whether a function has the "right" to abort when its precondition is
> violated is an interesting question. I don't agree with the absolute
> characterization that giving it this right is irresponsible. A
> contract that doesn't have teeth decreases the incentive of the caller
> to get the preconditions right, so one might argue that it encourages
> irresponsibility on the part of the caller.
>
> Out of curiosity, does your style of programming also involve throwing
> exceptions on postcondition and invariant violations?
Hmm, I always throw when I find I will not be able to meet my
postcondition. For example, if I can't get the memory I need to grow
the container as requested: bad_alloc.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:11:23 AM
|
|
on Tue Feb 03 2009, Jeff Schwab <jeff-AT-schwabcenter.com> wrote:
> David Abrahams wrote:
>
>> If you give a person a lifejacket, he may be able to stay afloat long
>> enough to improvise a response to an unanticipated emergency situation,
>> in which case full recovery is a possibility. Since software can only
>> handle the situations the programmer can anticipate, the same reasoning
>> doesn't apply.
>
> Hardware (including lifejackets) and software (including exceptions) are
> no different in this sense. The presence of a life jacket proves that
> someone foresaw the potential need for it. The same is true of a
> throw-expression.
>
> We don't give people life jackets in the hope that the bearers will
> somehow innovate their way out of watery deaths. We hope that they
> might be rescued.
Umm, no. *I* hope that it will buy them enough time to avoid death, by
innovation or rescue or providence or whatever. Rescue isn't something
one can count on either.
> Sometimes this works, and sometimes it doesn't. Similarly, even
> crippled software can still be useful, if only to support interactive
> debugging.
Jah, if you want effective debugging, begin it at the first moment the
error is detected, not after some amount of unwinding and who knows
what-all else.
> Just as a sick patient has a far greater chance of
> recovery than a dead one, an ailing application beats the heck out of
> a core dump. Debugging a core dump is the software equivalent of an
> autopsy.
Think of it as cryogenics. If you think unwinding can *really*
resusscitate the program, you can always restart the core dumped state
under a debugger and allow it to continue. You can't seriously be
arguing that it'll be easier to cure the patient after the disease has
had a chance to make further progress?
> Once a program dies, the most you can hope for is to learn
> enough from the death to avoid similar problems in the future.
>
> When the Remote Agent software on Deep Space 1 detected an internal
> inconsistency, the engineers on the ground were able to debug the cause
> (a race condition), and salvage the mission. Had the original
> programmers chosen to terminate the application, rather than entering an
> interactive debugger, the $100M mission might have been a complete loss.
Exactly, that's my point. They didn't throw an exception, they went
straight to the debugger the moment the problem was detected.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:11:44 AM
|
|
on Sun Feb 08 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> In SML, undefined behavior is not an option.
Undefined behavior is not solely a property of a programming language.
It can also be a property of a library. It's sometimes more useful to
be able to say that some inputs to a function are illegal than it is to
define an arbitrary response to every possible input. It certainly can
be better for debugging, because it allows you to catch illegal
conditions earlier... err, I mean, it allows you to catch them at all
;-)
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:12:02 AM
|
|
on Sun Feb 01 2009, Damien Kick <dkixk-AT-earthlink.net> wrote:
> David Abrahams wrote:
>
>> I agree that this is an extremely important idea in general, and for
>> C/C++ programmers in particular (see Stepanov on Regular Types).
>> However, most other languages I've encountered either have no value
>> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
>> Isn't it strange that such a fundamental principle is inaccessible to
>> programmers in those languages?
>
> Perhaps I don't understand what you mean.
>
> UL-USER> (defparameter *x* (cons 'key 13))
>
> *X*
> UL-USER> *x*
> (KEY . 13)
> UL-USER> (defun f (cons) (incf (cdr cons)))
> F
> UL-USER> (f *x*)
> 14
> UL-USER> *x*
> (KEY . 14)
> UL-USER>
That looks like reference semantics. Value semantics is defined by
something like
(setq x <whatever>)
(setq x y)
(modify x)
and y remains unmodified. Now of course in lisp we explicitly call
binding "setq" (and not "=" like in Python) so it's more obviously not
the same as assignment, but if there's a default in lisp, it's binding,
not copy.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/9/2009 5:12:41 AM
|
|
Thant Tessman wrote:
>
> The type system forced me to deal with a couple quirks of stacks. The
> first is: What do you do if you call pop on an empty stack? I chose to
> simply return an empty stack. I could have chosen to throw an exception.
> There are other things I could have done, but the type system would not
> have allowed me to do nothing. It would have produced a compile-time
> error. In SML, undefined behavior is not an option.
Isn't it due to the particular way of pattern matching for lists,
not the type system in general? For example, if the input argument
being non-zero, or being an even number, were the precondition,
I don't think the type system would force you to check them.
--
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
|
2/9/2009 5:12:50 AM
|
|
On Feb 8, 8:40 pm, David Abrahams <d...@boostpro.com> wrote:
> on Thu Feb 05 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
>
> > Yes, it is. A good type system will tend to allow you to capture those
> > 'preconditions' with the type system itself.
>
> So you plan to design a numeric type that doesn't include zero? Or are
> you planning to leave out division?
In a very anal way, one can view the entire program in terms of "type
safety". It just isn't very economical to do so.
One example of a more comprehensive type system: Imagine your entire
program was written in templates, and all values in use are separate
instantiations of templates, aka each value is a separate type. A
"stack with one element" is a different type than a "stack with 0
elements". You could check the input to stack::pop() at compile time
to verify it was passed a non-empty stack. Basically, the entire
program would be run at compile time.
Put another way, there is a (somewhat) fine line between type safety
and program correctness, that there is a blur between types and values
when one talks formally about type systems.
Couldn't one argue that a "stack with 1 or more elements" is a
different 'type' than a "stack with 0 or more elements"? The operation
pop is only defined on the previous domain of "stacks of 1 or more
elements".
Further example: Division. "int" is a type. Division is defined as a
binary function int division(int , int );. However, division is
undefined on (X, 0). As David Abrahams said, we could express this by
having a separate type, ex: integers and nonzero integers, or nonzero
integers and zero integer.
We could thus define the entire program execution in terms of types
and type safety, just like any proof on program correctness.
It's just that because most / all of the major type systems are quite
similar, we all just refer to these common type systems as "type
systems" and runtime validation as "checking bad input". I would
imagine because any more restrictive type systems, like the one for
stacks or division, are not economical to implement due to lack of
generality and expressiveness, otherwise I would expect that we would
have done so already. I would imagine that type systems have already
evolved into the the natural equilibrium between catching errors and
program expressiveness.
Ex of a more "strict" / "robust" type system: We could have nonzero
integers and zero integer as separate types. Division could be nicely
defined then. However, multiplication would have an uglier definition.
We could define it piecewise, (nonnzero, nonzero), (nonzero, zero),
(zero, nonzero), (zero, zero). Alternatively we could define division
in terms of integers and nonzero integers, but we still have the same
problem defining a useful multiplication. In this trivial example it
is still relatively easy to define multiplication, but in any
nontrivial example, this quickly becomes unworkable.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
joshuamaurice
|
2/9/2009 5:15:47 AM
|
|
Seungbeom Kim wrote:
> Thant Tessman wrote:
>>
>> The type system forced me to deal with a couple quirks of stacks. The
>> first is: What do you do if you call pop on an empty stack? I chose to
>> simply return an empty stack. I could have chosen to throw an exception.
>> There are other things I could have done, but the type system would not
>> have allowed me to do nothing. It would have produced a compile-time
>> error. In SML, undefined behavior is not an option.
>
> Isn't it due to the particular way of pattern matching for lists,
> not the type system in general? For example, if the input argument
> being non-zero, or being an even number, were the precondition,
> I don't think the type system would force you to check them.
Correct. No type system will ever be perfect. But a type system can be
pretty good. And there are plenty of languages that serve as existence
proofs that there is no excuse for "undefined behavior" (except in
extreme, performance-critical situations).
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/9/2009 12:50:23 PM
|
|
On Feb 9, 11:15 am, joshuamaur...@gmail.com wrote:
>
> Ex of a more "strict" / "robust"typesystem: We could have nonzero
> integers and zero integer as separate types. Division could be nicely
> defined then. However, multiplication would have an uglier definition.
> We could define it piecewise, (nonnzero, nonzero), (nonzero, zero),
> (zero, nonzero), (zero, zero). Alternatively we could define division
> in terms of integers and nonzero integers, but we still have the same
> problem defining a useful multiplication. In this trivial example it
> is still relatively easy to define multiplication, but in any
> nontrivial example, this quickly becomes unworkable.
>
It doesn't work, even in principle. You'd need 1 + 1 to result in a
nonzero object but -1 + 1 to result in a zero object. Mathematically
speaking, this approach gets you as far as defining a ring, but a
field defeats it. Not very helpful.
James
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
James
|
2/9/2009 12:50:25 PM
|
|
David Abrahams wrote:
> on Sun Feb 01 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
>
>> On Feb 1, 9:49 am, Jeff Schwab <j...@schwabcenter.com> wrote:
>>
>>> The idea that some local, deeply nested part of the code should presume
>>> to crash the whole application seems irresponsible to me.
>> Whether a function has the "right" to abort when its precondition is
>> violated is an interesting question. I don't agree with the absolute
>> characterization that giving it this right is irresponsible. A
>> contract that doesn't have teeth decreases the incentive of the caller
>> to get the preconditions right, so one might argue that it encourages
>> irresponsibility on the part of the caller.
>>
>> Out of curiosity, does your style of programming also involve throwing
>> exceptions on postcondition and invariant violations?
>
> Hmm, I always throw when I find I will not be able to meet my
> postcondition. For example, if I can't get the memory I need to grow
> the container as requested: bad_alloc.
Yes, in that situation, I also throw. However, I find asserts useful as
documentation:
/* Postcondition: The result is evenly divisible by 2. */
int times_two(int i) {
int const j = i + i;
assert(j % 2 == 0);
return j;
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Jeff
|
2/9/2009 12:50:34 PM
|
|
Seungbeom Kim wrote:
> Thant Tessman wrote:
>>
>> The type system forced me to deal with a couple quirks of stacks. The
>> first is: What do you do if you call pop on an empty stack? I chose to
>> simply return an empty stack. I could have chosen to throw an exception.
>> There are other things I could have done, but the type system would not
>> have allowed me to do nothing. It would have produced a compile-time
>> error. In SML, undefined behavior is not an option.
>
> Isn't it due to the particular way of pattern matching for lists,
> not the type system in general? For example, if the input argument
> being non-zero, or being an even number, were the precondition,
> I don't think the type system would force you to check them.
>
Yah, exactly. Stack was an example that was *particularly* easy because
many (but not all!) of the constraints on a stack's primitives are
structural and can be easily enforced via pattern matching. That of
course still needs contracts as much as anything; pattern matching only
makes them marginally easier to write. Add a requirement for a primitive
pop(n) to pop a given number of elements from a stack and the illusion
of pattern-enforced contracts comes unglued.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/9/2009 12:50:47 PM
|
|
Jeff Schwab <jeff@schwabcenter.com> writes
(but possibly has used another indentation):
>/* Postcondition: The result is evenly divisible by 2. */
>int times_two(int i) {
> int const j = i + i;
> assert(j % 2 == 0);
> return j;
> }
I see that in your post, there is a TAB character in front of
>return<, but spaces in front of >assert<. This might disturb
the visual appearance on some implementations of Usenet
message display systems. Possibly, this is a result of the
moderation process.
In the quotation above, I have replaced this TAB it with
spaces,
I remember that recently a post of mine was modified. I sent in:
|{ int i( 12 );
| int & r( i );
| ...
The article <references-20090123194355@ram.dialup.fu-berlin.de>
however appeared with:
|{ int i( 12 );
| int & r( i );
| ...
What wanted to write, when I hit the >reply< button, is this:
Jeff Schwab <jeff@schwabcenter.com> writes
(but possibly has used another indentation):
>/* Postcondition: The result is evenly divisible by 2. */
>int times_two(int i) {
> int const j = i + i;
> assert(j % 2 == 0);
> return j;
> }
Whenever all return statements of a function have the form
>return <variable>;<, I will name that variable >result<.
/* Postcondition: The result is evenly divisible by 2. */
int times_two(int i) {
int const result = i + i;
assert(result % 2 == 0);
return result;
}
I like your use of >const< and even would extend it to >i<:
/* Postcondition: The result is evenly divisible by 2. */
int times_two(int const i) {
int const result = i + i;
assert(result % 2 == 0);
return result;
}
Which conditions or assertions might one add?
/* Precondition: Twice the argument value can still be
represented as an int value.
Postcondition: The result is evenly divisible by 2. */
int times_two(int const i) {
assert i <= ::std::numeric_limits<int>::max() / 2;
int const result = i + i;
assert(result % 2 == 0);
return result;
}
C++ might still give a result if the precondition is not
fulfilled, but then the name of the function might be
misleading, because the result is not twice the value.
In order to be more precise, I would need an actual
specification of the intended behavior (including the result)
of this function.
(If one finds any TAB characters in the body of this post or
if the first character of the lines within the function body
of >times_two< does not appear in the same position as the
final brace, then the body of my post was altered after I have
sent it.)
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
ram
|
2/9/2009 3:09:56 PM
|
|
On Feb 9, 10:50 am, James Hopkin <tasjae...@gmail.com> wrote:
> On Feb 9, 11:15 am, joshuamaur...@gmail.com wrote:
>
>
>
> > Ex of a more "strict" / "robust"typesystem: We could have nonzero
> > integers and zero integer as separate types. Division could be nicely
> > defined then. However, multiplication would have an uglier definition.
> > We could define it piecewise, (nonnzero, nonzero), (nonzero, zero),
> > (zero, nonzero), (zero, zero). Alternatively we could define division
> > in terms of integers and nonzero integers, but we still have the same
> > problem defining a useful multiplication. In this trivial example it
> > is still relatively easy to define multiplication, but in any
> > nontrivial example, this quickly becomes unworkable.
>
> It doesn't work, even in principle. You'd need 1 + 1 to result in a
> nonzero object but -1 + 1 to result in a zero object. Mathematically
> speaking, this approach gets you as far as defining a ring, but a
> field defeats it. Not very helpful.
Yes, sorry. It works only if you start having a separate "type" for
each integer (which quickly becomes unworkable if you want any
meaningful portion of your program to run after compilation [though
compilation and runtime start getting blurred with such type
systems]), or you start doing runtime casting, like dynamic_cast,
casting integer to nonzero integer, but this cast basically
demonstrates that the type system is unworkable.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
joshuamaurice
|
2/9/2009 3:10:20 PM
|
|
On Feb 9, 8:50 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
> Correct. No type system will ever be perfect. But a type system can be
> pretty good. And there are plenty of languages that serve as existence
> proofs that there is no excuse for "undefined behavior" (except in
> extreme, performance-critical situations).
There are plenty of languages that demonstrate that it is possible to
eliminate C-style undefined behavior on the language level. I agree
that this is good, but I don't think I agree with your reasoning on
why it's good.
This however does not necessarily imply that eliminating preconditions
(on the language level and otherwise) is good. You seem to be equating
the two on an axiomatic level.
Your logic, correct me if I'm wrong, appears to have undefined
behavior (or preconditions) as a cause, and bugs as a result.
Obviously, by this reasoning, programs without preconditions have no
bugs.
But this is backwards. Bugs are the cause, and undefined behavior (and
precondition violations) are the result. You can't eliminate bugs by
treating the symptom.
Now, you can argue that it's been shown empirically that programs
without preconditions have fewer bugs. Maybe. As far as I know, the
primary factors affecting defect rates are program complexity and
presence (or absence) of a rigorous formal design process. The
language doesn't matter that much.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/10/2009 4:54:12 AM
|
|
"David Abrahams" <dave@boostpro.com> wrote in message
news:878wogmc3w.fsf@mcbain.luannocracy.com...
> One of the courses I teach has a section where I discuss program
> correctness. When I ask who in the room knows what preconditions,
> postconditions, and invariants are, maybe 15% raise their hands.
Many people are shy to raise their hand or just may not feel compelled to
participate at a given time. You should try asking this way next time: "how
many DON'T know what ... are?" You may indeed get 15% response again.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/10/2009 4:55:47 AM
|
|
On Feb 6, 1:49 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> Thant Tessman wrote:
> > Marsh Ray wrote:
> >> // Contract: Function returns true iff 'v' represents
> >> // the SHA-1 hash of the specified string.
>
> My mistake. The "if and only if" implies that it indeed must return
> false otherwise.
Well, it was just as much my mistake if we're going to read it
strictly (and I probably should have had that in mind since we're
discussing contract design). It doesn't explicitly specify what should
happen if the hash doesn't match. I suppose a "conforming
implementation" could reformat the proverbial hard drive and throw an
exception.
This was perhaps inadvertently a good argument for more formal
specification systems?
> Neither type systems, nor design-by-contract can validate a function's semantics.
Hmm. Is that really a feature of contract design in general, or some
specific set of tools used to automate DBC?
Is there any limit to the complexity allowed in the specification of a
postcondition?
If not, why can't runtime postcondition checks be used to validate
just about any semantics (in theory, in the absence of lost
information, etc.) ?
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/10/2009 3:28:53 PM
|
|
On Feb 2, 1:30 pm, Thant Tessman <thant.tess...@gmail.com> wrote:
> Tony wrote:
> >> The right thing to do in the case of invalid values is to report
> >> an error, not simply abort.
>
> > Assuming you can and can take the risk of executing more code in a program
> > you know is already in an undefined state.
>
> If the invalid value genuinely produces an undefined state, then throw
> an exception.
I used to believe that too, but was talked out of it.
If you have code that operates at a different security level than the
data it handles (e.g., just about anything using IP sockets or could
conceivably run in a web browser), you simply can't execute any code
(even exception handlers) from an "undefined state". You really must
terminate immediately at the first indication of a stray pointer, out-
of-bounds array access, etc.
There's an endless supply of example for this:
http://www.google.com/search?q=pointer+exploit
In other words, what you call "undefined state" is quite possibly very
well understood and easily manipulated by a knowledgeable attacker.
Just another aspect to fuel the ongoing discussion of the role of
asserts, exceptions, and error handling in general. It's a perpetually
difficult and unglamorous problem.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/10/2009 3:29:16 PM
|
|
on Tue Feb 10 2009, "Tony" <tony-AT-my.net> wrote:
> "David Abrahams" <dave@boostpro.com> wrote in message
> news:878wogmc3w.fsf@mcbain.luannocracy.com...
>
>> One of the courses I teach has a section where I discuss program
>> correctness. When I ask who in the room knows what preconditions,
>> postconditions, and invariants are, maybe 15% raise their hands.
>
> Many people are shy to raise their hand or just may not feel compelled
> to participate at a given time. You should try asking this way next
> time: "how many DON'T know what ... are?" You may indeed get 15%
> response again.
Thanks, but:
* Really, I know that some people don't like to speak up in class.
* I have a pretty good read on how responsive my classes are overall,
and this happens even in classes that are on average very vocal.
* I learned long ago that asking "who doesn't know X" questions doesn't
work well. People don't like to be forced to single themselves out as
less knowledgeable.
* It is an unscientific measurement, but I believe it to be reasonably
accurate.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/10/2009 3:38:36 PM
|
|
on Tue Feb 10 2009, Peter Dimov <pdimov-AT-gmail.com> wrote:
> But this is backwards. Bugs are the cause, and undefined behavior (and
> precondition violations) are the result. You can't eliminate bugs by
> treating the symptom.
>
> Now, you can argue that it's been shown empirically that programs
> without preconditions have fewer bugs. Maybe. As far as I know, the
> primary factors affecting defect rates are program complexity and
> presence (or absence) of a rigorous formal design process. The
> language doesn't matter that much.
I'm going even further: I believe that taking preconditions out of
programs increases their complexity.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/10/2009 3:39:03 PM
|
|
On Feb 9, 3:10 pm, joshuamaur...@gmail.com wrote:
> Yes, sorry. It works only if you start having a separate "type" for
> each integer (which quickly becomes unworkable if you want any
> meaningful portion of your program to run after compilation [though
> compilation and runtime start getting blurred with such type
> systems]), or you start doing runtime casting, like dynamic_cast,
> casting integer to nonzero integer, but this cast basically
> demonstrates that the type system is unworkable.
That may be going to far to say it's totally unworkable as a type
system. After all, good old original C defined separate int and (non-
negative) unsigned int ring types for reasons that may have some deep
similarity.
Looks like incompleteness in action:
http://en.wikipedia.org/wiki/G�del's_incompleteness_theorems
So the general case is undecidable at compile time, big deal!
1. Compiler authors are free to get as sophisticated as they like with
value range analysis.
2. Users/library authors can define types to represent subranges of
integers if they so desire.
3. Users can explicitly define division for types.
4. Expressions that are theoretically checkable at compile time can,
in practice, be checked at compile time (adding zero runtime
overhead).
5. For things that cannot be checked at compile time, there is a
relatively consistent and intuitive syntax "like dynamic_cast".
It's the C++ way!
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/10/2009 3:39:28 PM
|
|
On Feb 9, 12:50 pm, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> Add a requirement for a primitive
> pop(n) to pop a given number of elements from a stack and the illusion
> of pattern-enforced contracts comes unglued.
Perhaps even less than that. How about just the ability to synthesize
a pop(n) from iteration/recursion of a primitive pop1()?
Interestingly, Haskell allows arbitrary boolean expressions 'gurards'
in its patterns (not sure about SML).
http://www.haskell.org/tutorial/patterns.html
>From http://www.syntaxpolice.org/lectures/haskellTalk/slides/x100.html
:
pop :: Stack a -> (a, Stack a)
pop (MakeStack (h:t)) = (h, MakeStack t)
pop (MakeStack []) = error "Attempt to pop an empty stack."
So I presume one could write something like:
popN :: Stack a -> Integer n -> (a, Stack a)
popN s n | n == 1 = pop as
| n <= size s = popN (head (pop s)) (n - 1)
| n > size s = error "Too much pop."
The compiler would even warn you if the guards are not comprehensive.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/10/2009 3:41:02 PM
|
|
On Feb 4, 12:35 am, Bart van Ingen Schenau <b...@ingen.ddns.info>
wrote:
> Now, in the exact same library, it is found that an internal calculation
> for an index has an off-by-one error. The calculation for the index is
> in no way affected by the data read in from the file (or whatever).
>
> Do you say that this off-by-one error must also be reported, with an
> exception, to the application using the library? Or would you rather
> have a bug report from the developers of that application that your
> library has a problem?
In practice this seems to be a small minority of errors. If it doesn't
depend on runtime input, what does it depend on? Surely the library
developer tests it at least a little before shipping it!
Also, in practice, it seems like most development occurs on code that
is not a general library with a vendor/user boundary like that.
Where there is a real vendor/user boundary, paranoid parameter
validation and exception reporting are the preferred method.
Parameter validation might be occasionally skipped for real
performance reasons (e.g., std::vector<>::operator [] under some
specific compiler environments), but they should be clearly
documented.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/10/2009 3:49:31 PM
|
|
Marsh Ray wrote:
> On Feb 9, 12:50 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> Add a requirement for a primitive
>> pop(n) to pop a given number of elements from a stack and the illusion
>> of pattern-enforced contracts comes unglued.
>
> Perhaps even less than that. How about just the ability to synthesize
> a pop(n) from iteration/recursion of a primitive pop1()?
>
> Interestingly, Haskell allows arbitrary boolean expressions 'gurards'
> in its patterns (not sure about SML).
> http://www.haskell.org/tutorial/patterns.html
>
>>From http://www.syntaxpolice.org/lectures/haskellTalk/slides/x100.html
> :
> pop :: Stack a -> (a, Stack a)
> pop (MakeStack (h:t)) = (h, MakeStack t)
> pop (MakeStack []) = error "Attempt to pop an empty stack."
>
> So I presume one could write something like:
>
> popN :: Stack a -> Integer n -> (a, Stack a)
> popN s n | n == 1 = pop as
> | n <= size s = popN (head (pop s)) (n - 1)
> | n > size s = error "Too much pop."
>
> The compiler would even warn you if the guards are not comprehensive.
Sure (damn! I remembered after posting), but that code drives the point
home: YOU are the one writing the contracts. The type system does not
naturally eliminate their need, so the contracts are not the alleged
duct tape.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/11/2009 2:53:19 AM
|
|
"David Abrahams" <dave@boostpro.com> wrote in message
news:874oz26wdz.fsf@mcbain.luannocracy.com...
> on Tue Feb 10 2009, "Tony" <tony-AT-my.net> wrote:
>
>> "David Abrahams" <dave@boostpro.com> wrote in message
>> news:878wogmc3w.fsf@mcbain.luannocracy.com...
>>
>>> One of the courses I teach has a section where I discuss program
>>> correctness. When I ask who in the room knows what preconditions,
>>> postconditions, and invariants are, maybe 15% raise their hands.
>>
>> Many people are shy to raise their hand or just may not feel compelled
>> to participate at a given time. You should try asking this way next
>> time: "how many DON'T know what ... are?" You may indeed get 15%
>> response again.
>
> Thanks, but:
>
> * Really, I know that some people don't like to speak up in class.
>
> * I have a pretty good read on how responsive my classes are overall,
> and this happens even in classes that are on average very vocal.
>
> * I learned long ago that asking "who doesn't know X" questions doesn't
> work well. People don't like to be forced to single themselves out as
> less knowledgeable.
>
> * It is an unscientific measurement, but I believe it to be reasonably
> accurate.
If indeed true, it sounds like a Programming 101 class then by definition.
"Precondition checking" as in argument checking, at least that, surely is
understood by every programmer (else by definition they are just early
students of programming). Invariants and postconditions may come later
unless the now common concepts have been introduced into curriculum (where
the programmer is not self-taught, which so many are).
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/11/2009 2:56:01 AM
|
|
On Feb 10, 3:54 am, Peter Dimov <pdi...@gmail.com> wrote:
[...]
> This however does not necessarily imply that eliminating preconditions
> (on the language level and otherwise) is good. You seem to be equating
> the two on an axiomatic level.
>
> Your logic, correct me if I'm wrong, appears to have undefined
> behavior (or preconditions) as a cause, and bugs as a result. [...]
No, I said what I meant: Design-by-contract is a band-aid. It's what
you do when you have a language that allows for undefined behavior and
doesn't have a good type system. In this case, design-by-contract is
useful. It helps the programmer insure that their programs aren't
broken.
However, if you have a language with a good type system, and one which
doesn't allow for undefined behavior, design-by-contract doesn't buy
you anything. Yes, maybe there's a way to think about a type system as
merely another way to enforce contracts, but I guarantee you that the
people who program in such languages don't think about them that way.
> [...] As far as I know, the
> primary factors affecting defect rates are program complexity and
> presence (or absence) of a rigorous formal design process. The
> language doesn't matter that much.
And this is exactly what I claim is false. Of course a good
programming language won't turn a bad programmer into a good one, and
a good programmer can likely get the job done with a variety of
different tools, but a good programmer will definitely be made more
productive with a good programming language.
The idea that language doesn't matter much is demonstrably false
without going into all this type stuff. I don't know whether it's
still true or not, and I don't remember how they gathered their data
(I think it was Linux OS bugs), but I remember a paper a few years ago
at PLDI that claimed that over half of all bugs were memory
violations. There are languages that don't allow for memory violations
at all. So any language that doesn't allow for memory violations
already precludes half the bugs--assuming such a language can
successfully be substituted for the task.
And I claim that for general application development, there are
several superior languages that most definitely could be substituted
for C++ modulo cultural and infrastructure issues. Of course, cultural
and infrastructure issues are nothing to sneeze at. It's just that
cultural and infrastructure issues tend to have a huge influence in
the way people perceive good and bad design.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
thant
|
2/11/2009 2:29:54 PM
|
|
on Wed Feb 11 2009, "Tony" <tony-AT-my.net> wrote:
>> Thanks, but:
>>
>> * Really, I know that some people don't like to speak up in class.
>>
>> * I have a pretty good read on how responsive my classes are overall,
>> and this happens even in classes that are on average very vocal.
>>
>> * I learned long ago that asking "who doesn't know X" questions doesn't
>> work well. People don't like to be forced to single themselves out as
>> less knowledgeable.
>>
>> * It is an unscientific measurement, but I believe it to be reasonably
>> accurate.
>
> If indeed true, it sounds like a Programming 101 class then by
> definition.
It's not; that's my point.
> "Precondition checking" as in argument checking, at least that, surely is
> understood by every programmer (else by definition they are just early
> students of programming).
You'd think, wouldn't you?
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/11/2009 2:31:25 PM
|
|
Marsh Ray wrote:
> Where there is a real vendor/user boundary, paranoid parameter
> validation and exception reporting are the preferred method.
> Parameter validation might be occasionally skipped for real
> performance reasons (e.g., std::vector<>::operator [] under some
> specific compiler environments), but they should be clearly
> documented.
At the boundary itself, I agree that rigorous validation with exceptions
for error reporting should be the norm.
Behind the boundary, thus in code that is never directly called by the
client and does not have to deal with non-validated data, I regard
asserts a better tool for the precondition tests. Every failure here is
a direct result of a bug in the vendor's code. The client should not
have to compensate for that.
>
> - Marsh
>
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Bart
|
2/11/2009 2:31:39 PM
|
|
on Wed Feb 11 2009, Bart van Ingen Schenau <bart-AT-ingen.ddns.info> wrote:
> Marsh Ray wrote:
>
>> Where there is a real vendor/user boundary, paranoid parameter
>> validation and exception reporting are the preferred method.
>> Parameter validation might be occasionally skipped for real
>> performance reasons (e.g., std::vector<>::operator [] under some
>> specific compiler environments), but they should be clearly
>> documented.
>
> At the boundary itself, I agree that rigorous validation with exceptions
> for error reporting should be the norm.
> Behind the boundary, thus in code that is never directly called by the
> client and does not have to deal with non-validated data, I regard
> asserts a better tool for the precondition tests. Every failure here is
> a direct result of a bug in the vendor's code. The client should not
> have to compensate for that.
The consistent way to think about that is that "valid" parameters are
not a precondition of functions that live on the boundary. Their
specification says they have to be resilient in the face of "invalid
input," and says explicitly /in what ways/ they must be resilient.
Expression of that resiliency, whatever it may be, is thus defined
behavior.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/11/2009 8:19:14 PM
|
|
on Wed Feb 11 2009, thant.tessman-AT-gmail.com wrote:
>> [...] As far as I know, the
>> primary factors affecting defect rates are program complexity and
>> presence (or absence) of a rigorous formal design process. The
>> language doesn't matter that much.
>
> And this is exactly what I claim is false. Of course a good
> programming language won't turn a bad programmer into a good one, and
> a good programmer can likely get the job done with a variety of
> different tools, but a good programmer will definitely be made more
> productive with a good programming language.
No argument
> The idea that language doesn't matter much is demonstrably false
> without going into all this type stuff. I don't know whether it's
> still true or not, and I don't remember how they gathered their data
> (I think it was Linux OS bugs), but I remember a paper a few years ago
> at PLDI that claimed that over half of all bugs were memory
> violations. There are languages that don't allow for memory violations
> at all. So any language that doesn't allow for memory violations
> already precludes half the bugs--assuming such a language can
> successfully be substituted for the task.
That line of reasoning is bogus. Let's take Q++, a version of C++ that
defines the semantics of all accesses that would ordinarily be memory
violations. The semantics are, let's say, to throw an exception. Or
maybe better yet, to abort. Take your pick. If what you said were
true, a horribly buggy C++ program in which all the bugs "were memory
violations" would be a totally bug-free Q++ program. But we all know
that the program would still be buggy in Q++; it still wouldn't operate
according to its spec.
I think the problem starts with the phrase "half of all bugs were
memory violations." Memory violations are merely the expression of some
bugs; the bugs exist independently of their specific effects.
> And I claim that for general application development, there are
> several superior languages that most definitely could be substituted
> for C++ modulo cultural and infrastructure issues.
I don't think anyone here is arguing either side of that point other
than you ;-)
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/11/2009 8:19:46 PM
|
|
On Feb 11, 10:29 pm, thant.tess...@gmail.com wrote:
> No, I said what I meant: Design-by-contract is a band-aid. It's what
> you do when you have a language that allows for undefined behavior and
> doesn't have a good type system. In this case, design-by-contract is
> useful. It helps the programmer insure that their programs aren't
> broken.
>
> However, if you have a language with a good type system, and one which
> doesn't allow for undefined behavior, design-by-contract doesn't buy
> you anything. Yes, maybe there's a way to think about a type system as
> merely another way to enforce contracts, but I guarantee you that the
> people who program in such languages don't think about them that way.
Tell me which part of the contract of your stack implementation:
(top (push s x)) == x
(pop (push s x)) == s
(length (push s x)) == (length s) + 1
(length (new)) == 0
(top (new)) == nil
(pop (new)) == (new)
is made redundant by the type system or the absence of undefined
behavior in the language.
> The idea that language doesn't matter much is demonstrably false
> without going into all this type stuff. I don't know whether it's
> still true or not, and I don't remember how they gathered their data
> (I think it was Linux OS bugs), but I remember a paper a few years ago
> at PLDI that claimed that over half of all bugs were memory
> violations. There are languages that don't allow for memory violations
> at all. So any language that doesn't allow for memory violations
> already precludes half the bugs--assuming such a language can
> successfully be substituted for the task.
No. The memory violation is caused by the bug, it is not the bug. In
languages where memory violations are possible, half of the bugs
manifest themselves as memory violations. In a safer language, these
bugs would not necessarily disappear; they'll manifest themselves in
some other way (a function failing with an out_of_range exception
where success is expected, for example). It _is_ possible for a
language to have an intrinsically lower defect rate (although I'd
claim that this is a second order effect due to reduced complexity or
forced formal design style) but the above is not a proof of that. And
of course it says nothing about programs with preconditions compared
with programs without preconditions.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Peter
|
2/11/2009 8:21:05 PM
|
|
"David Abrahams" <dave@boostpro.com> wrote in message
news:87ab8szxe2.fsf@mcbain.luannocracy.com...
>
> on Wed Feb 11 2009, "Tony" <tony-AT-my.net> wrote:
>
>>> Thanks, but:
>>>
>>> * Really, I know that some people don't like to speak up in class.
>>>
>>> * I have a pretty good read on how responsive my classes are overall,
>>> and this happens even in classes that are on average very vocal.
>>>
>>> * I learned long ago that asking "who doesn't know X" questions doesn't
>>> work well. People don't like to be forced to single themselves out as
>>> less knowledgeable.
>>>
>>> * It is an unscientific measurement, but I believe it to be reasonably
>>> accurate.
>>
>> If indeed true, it sounds like a Programming 101 class then by
>> definition.
>
> It's not; that's my point.
>
>> "Precondition checking" as in argument checking, at least that, surely is
>> understood by every programmer (else by definition they are just early
>> students of programming).
>
> You'd think, wouldn't you?
>
Maybe they know the concept(s) but not the terminology. Certainly if you
indeed have a bunch of students that don't have the faintest idea of
programming defensively, then it's an elementary group no matter what the
class name indicates. Perhaps you need a "prerequisites" requirement/test
(hehe, a precondition!) for entry into your advanced class.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/12/2009 2:02:09 AM
|
|
On Feb 11, 7:19 pm, David Abrahams <d...@boostpro.com> wrote:
[...]
> I think the problem starts with the phrase "half of all bugs were
> memory violations." Memory violations are merely the expression of some
> bugs; the bugs exist independently of their specific effects.
Valid point. But I would counter with two other points: First, when a
bug leads to a memory violation instead of an abort or exception, it
may go undetected when it otherwise wouldn't have, or it may be
'detected' by way of consequences far more catastrophic than they
otherwise would have been. So a programming language that did turn all
memory violations into aborts or exceptions is still precluding some
bugs as well as precluding the more catastrophic effects of other
bugs. It's still a big win.
Second, a well-designed programming language won't avoid memory
violations merely by turning all memory violations into aborts or
exceptions. Like I keep saying, it would provide a way for programmers
to express their ideas in a way that the compiler itself can check for
logical consistency. This is far more powerful than merely checking
for pre- and post-conditions and invariants.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
thant
|
2/12/2009 7:45:28 AM
|
|
In article
news:<3b8e41cb-e2f7-4076-b3a1-0253032685b6@j35g2000yqh.googlegroups.com
>, Thant.tessman@gmail.com wrote:
> Yes, maybe there's a way to think about a type system as
> merely another way to enforce contracts, but I guarantee you that the
> people who program in such languages don't think about them that way.
IME you are wrong about that. In a system/language that has strong
typing that is seen as one of the tools that can be used to enforce
contracts -- and to enforce them at /compile/ time (which is so much
better than enforcing them at runtime).
What do you think concepts are for in C+++0x?
--
Daniel James | djng
Sonadata Limited | at sonadata
UK | dot co dot uk
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Daniel
|
2/12/2009 7:45:50 AM
|
|
thant.tessman@gmail.com wrote:
> On Feb 11, 7:19 pm, David Abrahams <d...@boostpro.com> wrote:
>
> [...]
>
>> I think the problem starts with the phrase "half of all bugs were
>> memory violations." Memory violations are merely the expression of some
>> bugs; the bugs exist independently of their specific effects.
>
> Valid point. But I would counter with two other points: First, when a
> bug leads to a memory violation instead of an abort or exception, it
> may go undetected when it otherwise wouldn't have, or it may be
> 'detected' by way of consequences far more catastrophic than they
> otherwise would have been. So a programming language that did turn all
> memory violations into aborts or exceptions is still precluding some
> bugs as well as precluding the more catastrophic effects of other
> bugs. It's still a big win.
But the discussion on soft vs. hard errors is rather separate from the
discussion on DbC. The relation is that the language has more default
contracts entering in action should you forget to state yours. But they
are only of the kind akin to memory safety; they aren't going to help
any higher-level consistency requirements. DbC is as useful as ever in
safe languages.
> Second, a well-designed programming language won't avoid memory
> violations merely by turning all memory violations into aborts or
> exceptions. Like I keep saying, it would provide a way for programmers
> to express their ideas in a way that the compiler itself can check for
> logical consistency. This is far more powerful than merely checking
> for pre- and post-conditions and invariants.
This is a substantially diluted claim than your initial one (albeit it
preserves most of its weakening vagueness), and my takeaway from the
conversation (more diluted still) is that one nice thing about e.g.
pattern matching is that you can put some (but not all) preconditions in
the function's signature. This has a sensible effect. The compiler has
better means to modularly check that the preconditions are respected.
It's unclear to me at this point what features of "good" type systems
obviate postconditions and invariants, so I'd appreciate pointers in
that direction.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/13/2009 1:47:01 AM
|
|
on Thu Feb 12 2009, "Tony" <tony-AT-my.net> wrote:
>>> "Precondition checking" as in argument checking, at least that, surely is
>>> understood by every programmer (else by definition they are just early
>>> students of programming).
>>
>> You'd think, wouldn't you?
>>
>
> Maybe they know the concept(s) but not the terminology.
Understanding the concepts isn't enough. You need to have a way to talk
about them and they need to be a part of your everyday vocabulary for
thinking about programming. That's what's missing.
> Certainly if you indeed have a bunch of students that don't have the
> faintest idea of programming defensively, then it's an elementary
> group no matter what the class name indicates.
Defensive programming is not at all what I'm talking about, and in fact
I am opposed to defensive programming.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/13/2009 1:47:09 AM
|
|
"David Abrahams" <dave@boostpro.com> wrote in message
news:87ljsbsjya.fsf@mcbain.luannocracy.com...
>
> on Thu Feb 12 2009, "Tony" <tony-AT-my.net> wrote:
>
>>>> "Precondition checking" as in argument checking, at least that, surely
>>>> is
>>>> understood by every programmer (else by definition they are just early
>>>> students of programming).
>>>
>>> You'd think, wouldn't you?
>>>
>>
>> Maybe they know the concept(s) but not the terminology.
>
> Understanding the concepts isn't enough. You need to have a way to talk
> about them and they need to be a part of your everyday vocabulary for
> thinking about programming. That's what's missing.
But you implied that the students didn't know about "DbC"-like stuff. I was
suggesting that surely most of them do, to various degrees. But convo such
as this belongs probably in learning/teaching/education groups rather than
one about a programming language.
Yes, a "standard" vocabulary facilitates communication of the topic. It is
good to know "precondition", "postcondition" and "invariant". But the
meanings of those terms differ amongst the programming community. Indeed,
this thread shows that.
>
>> Certainly if you indeed have a bunch of students that don't have the
>> faintest idea of programming defensively, then it's an elementary
>> group no matter what the class name indicates.
>
> Defensive programming is not at all what I'm talking about, and in fact
> I am opposed to defensive programming.
See, you have a different "definition" of that than I do also (no need to
tangent off of this though as this sub-thread needs to come to an end).
Mostly, I rely on context to determine what a given writer/speaker is trying
to convey.
Tony
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Tony
|
2/14/2009 5:11:55 AM
|
|
David Abrahams wrote:
> on Sun Feb 01 2009, Damien Kick <dkixk-AT-earthlink.net> wrote:
>
>> David Abrahams wrote:
>>
>>> I agree that this is an extremely important idea in general, and for
>>> C/C++ programmers in particular (see Stepanov on Regular Types).
>>> However, most other languages I've encountered either have no value
>>> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
>>> Isn't it strange that such a fundamental principle is inaccessible to
>>> programmers in those languages?
Of course, one might wonder if value semantics really are such a
fundamental principal if there are so many successful languages which
completely lack value semantics.
>> Perhaps I don't understand what you mean. [... example ellided ...]
>
> That looks like reference semantics. Value semantics is defined by
> something like
>
> (setq x <whatever>)
> (setq x y)
> (modify x)
>
> and y remains unmodified. Now of course in lisp we explicitly call
> binding "setq" (and not "=" like in Python) so it's more obviously not
> the same as assignment, but if there's a default in lisp, it's binding,
> not copy.
Ah, I see what you mean. Something like the following:
(defmacro <<= (x y) `(setf ,x (copy-object ,y)))
Of course, Common Lisp doesn't have a generic copy-object. But it's
interesting to me to note that Common Lisp doesn't have a copy-object on
purpose <http://www.nhplace.com/kent/PS/EQUAL.html> and that Kent
Pitman, the project editor of the Common Lisp standard, considers the
main point of departure to be related to issues of typing.
<blockquote>
In a language with strong static typing, the intentional type of the
object would be evident at compile time, and the same representational
type could be used for multiple intentional types. The main problem with
this approach is that it gives up dynamic typing, which Lisp users have
come to expect and enjoy.
If static type information is optional, it is difficult for language
designers to reliably express how operators behave in the hybrid
environment that results.
</blockquote>
Of course, C++ does define a default copy construtor for every type, but
C++ programmers have to worry about deep copy vs shallow copy, whether
or not to introduce some kind of "virtual T* clone() const" operation,
and the problems introduced by the interaction of pass-by-value and
public inheritance, i.e. slicing. This all makes me wonder if perhaps
the more fundamental concept is object identity and that the decisions
to make regarding copying, assignment, and tests for equality depend on
the interactions of decisions made regarding other more fundamental
concepts in the design space of the language, e.g. static vs dynamic typing.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Damien
|
2/17/2009 9:58:34 AM
|
|
on Sat Feb 14 2009, "Tony" <tony-AT-my.net> wrote:
> "David Abrahams" <dave@boostpro.com> wrote in message
> news:87ljsbsjya.fsf@mcbain.luannocracy.com...
>>
>> on Thu Feb 12 2009, "Tony" <tony-AT-my.net> wrote:
>>
>
> But you implied that the students didn't know about "DbC"-like stuff.
I think my original statement was much clearer and more precise than
that. The question I ask is, "who in the room knows what
'preconditions,' 'postconditions,' and 'invariants' are?"
> I was suggesting that surely most of them do, to various degrees. But
> convo such as this belongs probably in learning/teaching/education
> groups rather than one about a programming language.
>
> Yes, a "standard" vocabulary facilitates communication of the
> topic. It is good to know "precondition", "postcondition" and
> "invariant".
Yes, clear and precise.
> But the meanings of those terms differ amongst the programming
> community. Indeed, this thread shows that.
Having precise, common, definitions for things is important.
>>> Certainly if you indeed have a bunch of students that don't have the
>>> faintest idea of programming defensively, then it's an elementary
>>> group no matter what the class name indicates.
>>
>> Defensive programming is not at all what I'm talking about, and in fact
>> I am opposed to defensive programming.
>
> See, you have a different "definition" of that than I do also (no need
> to tangent off of this though as this sub-thread needs to come to an
> end). Mostly, I rely on context to determine what a given
> writer/speaker is trying to convey.
Mostly I try to be precise. The sub-thread should end, but maybe not
with the snarky implication that I'm just being too literal. ;-)
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/19/2009 1:05:58 AM
|
|
on Tue Feb 17 2009, Damien Kick <dkixk-AT-earthlink.net> wrote:
> David Abrahams wrote:
>> on Sun Feb 01 2009, Damien Kick <dkixk-AT-earthlink.net> wrote:
>>
>>> David Abrahams wrote:
>>>
>>>> I agree that this is an extremely important idea in general, and for
>>>> C/C++ programmers in particular (see Stepanov on Regular Types).
>>>> However, most other languages I've encountered either have no value
>>>> semantics (Python, Java, Lisp, **) or inconsistent value semantics (D).
>>>> Isn't it strange that such a fundamental principle is inaccessible to
>>>> programmers in those languages?
>
> Of course, one might wonder if value semantics really are such a
> fundamental principal if there are so many successful languages which
> completely lack value semantics.
That question sure made me think.
I think value semantics important because of its relationship to the
underlying machine model and to equational reasoning. In other words,
it's a combination that has to do with understandability *and*
efficiency, and not everyone cares about efficiency at this level. You
can provide an experience of pure value semantics if everything is
immutable, but then some important operations become too expensive. If
you want to allow mutability, your choices are to either expose the use
of references in the implementation (now mutating X changes the value of
Y) or to introduce real copy semantics.
Hmm, I'm not satisfied yet. Trying to put my finger on a good way to
say it (not enough sleep last night). Let me try again. Programming is
about mathematical structures. IMO it's important to be able to write
b = a
assert(a == b)
a += c
assert(a != b)
where a, b, and c are numeric types, whether built-in or user-defined,
and no matter how complicated their internal data structure.
Well, closer, but still not satisfied. I'll have to start thinking
about this more deeply. Thanks for the impetus.
> But it's interesting to me to note that Common Lisp doesn't have a
> copy-object on purpose <http://www.nhplace.com/kent/PS/EQUAL.html> and
> that Kent Pitman, the project editor of the Common Lisp standard,
> considers the main point of departure to be related to issues of
> typing.
>
> <blockquote>
> In a language with strong static typing, the intentional type of the
> object would be evident at compile time, and the same representational
> type could be used for multiple intentional types. The main problem with
> this approach is that it gives up dynamic typing, which Lisp users have
> come to expect and enjoy.
>
> If static type information is optional, it is difficult for language
> designers to reliably express how operators behave in the hybrid
> environment that results.
> </blockquote>
To me that sounds like a failure to recognize the schematic type
requirements (i.e. concepts) present in the dynamically-typed code.
It's very reminiscent of this thread:
http://markmail.org/message/mvcq2wegwb2bzkpj
> Of course, C++ does define a default copy construtor for every type,
> but C++ programmers have to worry about deep copy vs shallow copy,
> whether or not to introduce some kind of "virtual T* clone() const"
> operation, and the problems introduced by the interaction of
> pass-by-value and public inheritance, i.e. slicing. This all makes me
> wonder if perhaps the more fundamental concept is object identity and
> that the decisions to make regarding copying, assignment, and tests
> for equality depend on the interactions of decisions made regarding
> other more fundamental concepts in the design space of the language,
> e.g. static vs dynamic typing.
Perhaps, but I don't think so. You can (and should) define the
semantics of "operators" on types, even when the type identity can only
be known at runtime.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/19/2009 1:07:22 AM
|
|
Andrei Alexandrescu wrote:
> [...] and my takeaway from the
> conversation (more diluted still) is that one nice thing about e.g.
> pattern matching is that you can put some (but not all) preconditions in
> the function's signature. This has a sensible effect. The compiler has
> better means to modularly check that the preconditions are respected.
> It's unclear to me at this point what features of "good" type systems
> obviate postconditions and invariants, so I'd appreciate pointers in
> that direction.
I'll try to restate my claims more succinctly: I have two. First:
A programmer should not in general use the fact that a precondition
hasn't been met as an excuse for undefined behavior or unreported error
conditions, especially when designing a public API. To the degree that
asserts are used as checks for 'preconditions,' they are--in my
book--being used incorrectly. Checks for 'preconditions' should not be
something you can turn off for release builds, nor should they as an
alternative always result in an abort. This is why I do indeed think
that the discussion on "soft vs. hard errors" is not at all separate
from the discussion on DbC as practiced in C++.
Second: The design of a programming language can and does have a huge
effect on how likely it is that a program will have bugs, and how severe
the consequences of those bugs are. Programmers tend not to be as aware
of this aspect of their tools because they subconsciously steer away
from tasks and techniques beyond their reach. Design-by-contract is a
way of making a program robust by enforcing logical consistency where
the language itself fails to do so. Some languages are designed so well
that design-by-contract becomes superfluous. One of the things that can
help make design-by-contract superfluous is a really good type system.
Another is the preclusion of "undefined behavior."
C++'s templates are astoundingly powerful, but they are awkward and
unwieldy in all but expert hands. C++'s type system is okay, but it is
incomplete, and severely handicapped by its C heritage. These are not
things one can ever really rise above, even with a decade of experience.
But the only way to know for yourself is to actually try to get real
work done in something other than C++.
In short, the utility of design-by-contract (like the utility of design
patters) should more properly be thought of as evidence of weakness in
one's language.
-thant
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/19/2009 1:08:23 AM
|
|
Andrei Alexandrescu wrote:
[...]
> So what's needed now is some amount of qualifying of your initial
> statement. You now claim that this:
>
> fun pop [] = []
> | pop (_ :: rest) = rest
>
> obviates the need for design by contract because it forces you to write
> what happens when the stack is empty. But the contract is _there_, and
> it states that pop from an empty stack returns empty. People will still
> attempt to pop from empty stacks, and that will still compile and run.
> So the type system hasn't help you zilch in designing a Stack type that
> doesn't need a contract. [...]
So a 'contract' is what a programmer has decided to do in the case that
a stack is empty? Again, I'll try to ask the dumb question: What makes a
'contract' different than "the decided-upon behavior"?
Let's try another tack. Instead of using a Stack as a stand-in for 'some
data structure,' let's consider an actual stack boiled down to its essence:
datatype 'a Stack = EmptyStack
| NotEmptyStack of 'a * 'a Stack
To explain: This is a type signature of a type 'Stack' with a free type
parameter "'a". A value of the type 'Stack' is allowed to be one of two
structures: either a structure that doesn't contain anything called
EmptyStack, or a structure called NotEmptyStack containing a value of
type 'a and another Stack (which in turn can be either an EmptyStack or
NotEmptyStack containg...).
But the above is more than merely a type signature. It's a complete
implementation. Here's how you would create an empty stack:
val s = EmptyStack;
Here's how you would push a 3 onto that empty stack:
val s = NotEmptyStack (3, s);
Here's how one might fetch the top value of the stack:
case s of
NotEmptyStack (v, _) => doSomethingWithTopOfStack v
| EmptyStack => doSomethingElse ();
The programmer must explicitly take into account the fact that a stack
is perfectly welcome to be an empty stack. [1]
Now I would hope the amazing elegance of this two-line 'Stack'
implementation would be self-evident, never mind compared to the
behaviorally-equivalent C++ code. I would also hope that folks might be
impressed enough with its elegance to try to identify its source. I've
come to the conclusion that elegance in this case is a reflection of the
type system (specifically the notion of discriminated unions combined
with genericity) along with automatic memory management. [2]
> You can at most claim that using pattern
> matching clarified that you need to make the contract explicit, instead
> of relying on the system's enforcement of the contract (e.g. an "out of
> bound array" or "null dereference" in Java). That's a fine claim, but
> it's not the original claim.
Talk about the need to decide what to do in the empty-stack case all you
want, but nowhere does the explicit notion of preconditions,
postconditions, or invariants as described in the OO world bring
anything to this particular party. In this version I never even wrote a
'top' function. There was no need. It (and any contract it embodied)
would be superfluous. More than that, the function embodying the
contract would--if we were being strict--itself require a contract.
As I said in another post, the need for contracts, like the need for
'design patterns' should more properly be interpreted as evidence of
weakness in one's language.
-thant
[1] Note that pattern-matching is, strictly speaking, nothing but
syntactic sugar for the 'case' statement.
[2] Of course discriminated unions and automatic memory management can
be and have been supported in C++ in one form or another. Yet the true
ugliness of C++ is evidenced in the fact that even with these features,
C++ fails to come even remotely close to the elegance of the above
manifestation of stacks.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Thant
|
2/19/2009 1:13:35 AM
|
|
Thant Tessman wrote:
> Andrei Alexandrescu wrote:
>
>> [...] and my takeaway from the conversation (more diluted still) is
>> that one nice thing about e.g. pattern matching is that you can put
>> some (but not all) preconditions in the function's signature. This has
>> a sensible effect. The compiler has better means to modularly check
>> that the preconditions are respected. It's unclear to me at this point
>> what features of "good" type systems obviate postconditions and
>> invariants, so I'd appreciate pointers in that direction.
>
> I'll try to restate my claims more succinctly: I have two. First:
>
> A programmer should not in general use the fact that a precondition
> hasn't been met as an excuse for undefined behavior or unreported error
> conditions, especially when designing a public API. To the degree that
> asserts are used as checks for 'preconditions,' they are--in my
> book--being used incorrectly. Checks for 'preconditions' should not be
> something you can turn off for release builds, nor should they as an
> alternative always result in an abort.
I'd like us to use the definitions prevalent in literature. Without
being an expert in DbC, my understanding is that invariants and
postconditions are "always" true, subject to bugs in implementations.
But preconditions, at least according to some, refer to not only
implementation bugs, but also runtime conditions. In other words, for
some people, a string being convertible to an int is a precondition of
atoi(), so you can read a string from console and pass it to atoi
directly because you know its precondition will catch it.
Other people put preconditions straight in the same bin as invariants
and postconditions: always true, modulo implementation bugs. Parameter
checking is *not* part of the precondition, except in internal private
functions.
The DbC examples I've seen ALWAYS check parameters in preconditions. So
I'm not sure what to make of all this.
> This is why I do indeed think
> that the discussion on "soft vs. hard errors" is not at all separate
> from the discussion on DbC as practiced in C++.
That I don't much agree with, but no matter.
> Second: The design of a programming language can and does have a huge
> effect on how likely it is that a program will have bugs, and how severe
> the consequences of those bugs are.
I've seen a study that shows bug count increasing with line count in
various languages. So I guess it's more about how concise the language
is than other factors.
> Programmers tend not to be as aware
> of this aspect of their tools because they subconsciously steer away
> from tasks and techniques beyond their reach. Design-by-contract is a
> way of making a program robust by enforcing logical consistency where
> the language itself fails to do so. Some languages are designed so well
> that design-by-contract becomes superfluous. One of the things that can
> help make design-by-contract superfluous is a really good type system.
> Another is the preclusion of "undefined behavior."
Here's where your attempt at being pedantic falls flat. My pedantic
dictionary does not list "really good" as an accurate description of a
type system.
The discussion on UB is easily separable: without UB, what this all
means that the abstract machine has some more restrictive contracts when
yours fail to enforce consistency properly. So yes, there is a link, but
not an inextricable one.
> C++'s templates are astoundingly powerful, but they are awkward and
> unwieldy in all but expert hands. C++'s type system is okay, but it is
> incomplete, and severely handicapped by its C heritage. These are not
> things one can ever really rise above, even with a decade of experience.
> But the only way to know for yourself is to actually try to get real
> work done in something other than C++.
Absolutely.
> In short, the utility of design-by-contract (like the utility of design
> patters) should more properly be thought of as evidence of weakness in
> one's language.
Still unable to draw the same exact conclusion from your arguments.
Andrei
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Andrei
|
2/19/2009 12:54:33 PM
|
|
on Thu Feb 19 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote
something I disagree with:
> Design-by-contract is a way of making a program robust by enforcing
> logical consistency where the language itself fails to do so.
Design-by-contract is a way of thinking about program documentation and
behavior that allows us to understand what client software can expect
from the libraries they use (and vice-versa).
> Some languages are designed so well that design-by-contract becomes
> superfluous.
The contracts are there intrinsically, whether you think they're
superfluous or not.
> One of the things that can help make design-by-contract superfluous is
> a really good type system. Another is the preclusion of "undefined
> behavior."
I don't see how. Even if the behavior isn't undefined, it might not be
the behavior you want. You need to have a contract with the code you're
calling that describes what it expects of your inputs and what it
guarantees about its outputs and effects.
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/19/2009 12:54:56 PM
|
|
on Thu Feb 19 2009, Thant Tessman <thant.tessman-AT-gmail.com> wrote:
> Andrei Alexandrescu wrote:
>
> [...]
>
>> So what's needed now is some amount of qualifying of your initial
>> statement. You now claim that this:
>>
>> fun pop [] = []
>> | pop (_ :: rest) = rest
>>
>> obviates the need for design by contract because it forces you to write
>> what happens when the stack is empty. But the contract is _there_, and
>> it states that pop from an empty stack returns empty. People will still
>> attempt to pop from empty stacks, and that will still compile and run.
>> So the type system hasn't help you zilch in designing a Stack type that
>> doesn't need a contract. [...]
>
> So a 'contract' is what a programmer has decided to do in the case that
> a stack is empty?
If you want to know what 'contract' means in this context, why don't you
start here: http://en.wikipedia.org/wiki/Design_by_contract
Design by Contract (DbC) or Programming by Contract is an approach to
designing computer software. It prescribes that software designers
should define formal, precise and verifiable interface specifications
for software components based upon the theory of abstract data types
and the conceptual metaphor of a business contract. Thus, it can be
considered as an evolution of the abstract data type theory.
...
Regards,
--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
David
|
2/19/2009 12:55:31 PM
|
|
On Feb 19, 1:08 am, Thant Tessman <thant.tess...@gmail.com> wrote:
> A programmer should not in general use the fact that a precondition
> hasn't been met as an excuse for undefined behavior or unreported error
> conditions, especially when designing a public API
I agree that's a nice goal where it's possible, but often impractical
to achieve.
Plenty of stuff that (to me anyway) falls under the general heading of
"preconditions" would require unbounded time or space to verify.
At some point, some code, somewhere, has to trust that something is in
a proper state that couldn't be proven at compile time.
> To the degree that
> asserts are used as checks for 'preconditions,' they are--in my
> book--being used incorrectly. Checks for 'preconditions' should not be
> something you can turn off for release builds, nor should they as an
> alternative always result in an abort. This is why I do indeed think
> that the discussion on "soft vs. hard errors" is not at all separate
> from the discussion on DbC as practiced in C++.
C++ is a practical language for writing real programs that run in many
many different scenarios. This is why there will always be a complete
spectrum of points-of-view on (safety, correctness) vs. (language
complexity, source code complexity, compile-time-costs, run-time
costs).
Your views are certainly valid from your perspective, but don't expect
everyone to jump on board (ever).
> Second: The design of a programming language can and does have a huge
> effect on how likely it is that a program will have bugs,
....and how fast the program will compile, and how fast it will run,
and how long it takes to develop, and so on
> and how severe the consequences of those bugs are.
In my experience, trying to reason ahead of time about the severity of
potential bugs doesn't turn out to be useful nearly as often as it
seems like it should at the time.
1. Faulty logic is hard to reason about in general. Maybe even
impossible.
2. If you know about the potential for a bug somewhere, you probably
need to address that specifically anyway.
3. Once something, escapes the realm of 'defined behaviour' in C++,
usually all bets are off. E.g., pointer bugs may behave differently
when the memory layout changes with your next compile.
4. In a security-conscious setting (e.g., a web browser) simple logic
bugs can be leveraged into privilege escalation or outright arbitrary
code execution surprisingly often enough that it should be your
default assumption.
> Programmers tend not to be as aware
> of this aspect of their tools because they subconsciously steer away
> from tasks and techniques beyond their reach.
That's not my sense of things, and it sounds pretty hard to prove one
way or another. My sense is they consciously steer away from those
mostly due to time pressure and an intentional attempt to put some
hard limits on complexity.
> Design-by-contract is a
> way of making a program robust by enforcing logical consistency where
> the language itself fails to do so. Some languages are designed so well
> that design-by-contract becomes superfluous.
Without disagreeing with the statement, this sounds like something out
of a book about some other language that's used waay less than C++.
The factors behind language popularity are pretty difficult to
quantify, but IMHO it's not all (or even mostly) coincidences and
marketing.
Also, keep in min that C++ and the idea of "interfaces as contracts"
have both been around longer than "Design by Contract (TM)".
In other words, there are real reasons that I use C++ and those
reasons are very related to it making undefined behavior possible and
not making DbC "superfluous" in your view.
> One of the things that can help make design-by-contract superfluous is a really good type system.
If you just swear off the address-of operator and C-style casts, it's
pretty much the same type system as ML. But if you want to write C
with array-to-pointer decay and so on, you're free to own the
consequences.
> Another is the preclusion of "undefined behavior."
Good luck writing an operating system or language runtime in a
language where everything you are allowed to do was been defined ahead
of time by some committee or vendor. (Especially if the vendor also
sells operating systems and language runtimes!)
> C++'s templates are astoundingly powerful, but they are awkward and
> unwieldy in all but expert hands. C++'s type system is okay, but it is
> incomplete, and severely handicapped by its C heritage.
I've got hundreds of thousands of lines of C++ in continuous operation
without pointer bugs or memory leaks that disagrees with you on that.
> These are not
> things one can ever really rise above, even with a decade of experience.
> But the only way to know for yourself is to actually try to get real
> work done in something other than C++.
>
> In short, the utility of design-by-contract (like the utility of design
> patters) should more properly be thought of as evidence of weakness in
> one's language.
You know, that is a real interesting point, and there's probably some
truth to it.
But that point seems to be obscured by a general "C++ is un-beautiful"
sense to your arguments. There are many ways a language can express
beauty:
http://en.wikipedia.org/wiki/C%2B%2B#Philosophy
Your metric of language quality ("[narrow definition of] design-by-
contract becomes superfluous") was simply not the overriding design
goal for C++. And again, that wasn't just an oversight or a mistake,
it was an intentional decision to favor other competing design goals.
To me what's great about C++ is that we can now move the discussion to
how we can incorporate the best elements of contract design into a
portable, reusable library and be able to reason about with some
confidence what run-time costs it will impose.
- Marsh
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
0
|
|
|
|
Reply
|
Marsh
|
2/19/2009 7:43:38 PM
|
|
Thant Tessman wrote:
> Andrei Alexandrescu wrote:
[snip]
> So a 'contract' is what a programmer has decided to do in the case that
> a stack is empty? Again, I'll try to ask the dumb question: What makes a
> 'contract' different than "the decided-upon behavior"?
I think the difference is the place where the decision is being made.
Much of the motivation behind contracts has been that interfaces wanted
to enforce that their implementers are correct.
> Let's try another tack. Instead of using a Stack as a stand-in for 'some
> data structure,' let's consider an actual stack boiled down to its essence:
>
> datatype 'a Stack = EmptyStack
> | NotEmptyStack of 'a * 'a Stack
Yeah, I was positive that at some point or another discriminated options
will come into the discussion sooner or later. In fact I confess that it
was my plan all along since I suggested you define a stack (not an
array, which I'll bring now to make my point).
There are two nice things about the stack as a discriminated union:
a) The empty stack is a distinct from any other stack. This is of course
doable in other languages but not so concisely and elegantly.
b) The stack does not need to define a contract because there's no
chance any usage would fail it.
On the flipside:
a) Note how all this forces you into value semantics and functional
style all the way. Now you are free to argue that functional style is
the one true way to do anything around computers, to which I'll just
note that some bad people still want their mutative semantics.
b) Instead of defining a contract, the discriminated union forces the
user to pick their contract at each use. This may turn out to be very
cumbersome. I mean, *every* time I plan to extract an element I need to
handle the empty stack case, even when I know a priory the stack can't
be empty, or if I'd be ok with just an exception being thrown.
So you didn't do away with contracts; you put them in a different place,
which may often be a worse place. (Btw, if configurable contracts were a
must, I, being a policy guy, would make the behavior of the empty stack
a policy and call it a day.)
But now comes the killer argument: design an array that way. In that
case, the array would be a convolution of types:
EmptyArray
ArrayOfSizeAtLeast1
ArrayOfSizeAtLeast2
ArrayOfSizeAtLeast3
....
Now, when a user wants to access element at index N, they need to do so
for an ArrayOfSizeAtLeastN, and then specify what happens for all other
types. I'm a nice guy, so I suggest you make ArrayOfSizeAtLeast2 would
be a subtype of ArrayOfSizeAtLeast1 and so on. That way you only need to
specify what happens for arrays of size up to N.
I guess it's easy to see how this all becomes untenable.
> Now I would hope the amazing elegance of this two-line 'Stack'
> implementation would be self-evident, never mind compared to the
> behaviorally-equivalent C++ code.
Well where you see amazing elegance I see yet another way to look at
some problems known to be tough, with its own advantages, disadvantages
and other consequences.
> I would also hope that folks might be
> impressed enough with its elegance to try to identify its source. I've
> come to the conclusion that elegance in this case is a reflection of the
> type system (specifically the notion of discriminated unions combined
> with genericity) along with automatic memory management. [2]
I agree that algebraic types have some rather nifty uses.
>> You can at most claim that using pattern
>> | | | |