Topic: C++ wishlist
Author: Dave Steffen <dgsteffen@numerica.us>
Date: Mon, 18 Sep 2006 11:28:34 CST Raw View
tommi.hoynalanmaa@iki.fi (Tommi H yn l nmaa) writes:
> Paul Elliott kirjoitti:
> > On 2004-09-03, Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
> >> some time ago it has been asked in this group whether anyone had
> >> large-scale wishes. Here are mine
> Here are mine:
> 1. Add a module system supporting separate compilation of modules.
> 2. Remove the implicit conversion from floating point types to integer.
> This conversion is actually a mathematical operation and it should be
> explicitly written.
> 3. Add keyword "null" for the null pointer and remove the implicit
> conversion from 0 to the null pointer.
Folks - have you checked out what the committee is actually working
on? I know that at least 1) and 3) above are being actively
considered.
The full list is at
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2011.htm>
(unless there's a more up-to-date summary?)
----------------------------------------------------------------------
Dave Steffen, Ph.D.
Software Engineer IV Disobey this command!
Numerica Corporation - Douglas Hofstadter
dgsteffen at numerica dot us
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: pelliott@hrnowl.io.com (Paul Elliott)
Date: Fri, 15 Sep 2006 19:01:37 GMT Raw View
On 2004-09-03, Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
> Hi,
>
> some time ago it has been asked in this group whether anyone had large-scale
> wishes. Here are mine
I think the following needs to be added:
Some kind of way to "cut off" the virtualness of a base class with
respect to further derived classes.
For example:
class A : virtual base {...};
class B : virtual base {...};
class myclass : public A, public B {...};
class derived: public myclass {...};
There needs to be someway of saying that although the virtualness of
base was necessary to construct myclass, the writers of classes like
derived do not need to know how myclass was constructed (with respect
to base). There needs to be someway of saying that base is NOT a
virtual base class of derived. And that writers of "derived" like
classes do not need to provide a ctor initializer for base, but that
the ctor initializer of myclass (for base) should be used instead.
I don't know possibly a notvirtual keyword?
class myclass : public A, public B, notvirtual base {...};
I don't insist on any particular syntax, but someway
of providing this functionality.
--
Paul Elliott 1(512)837-1096
pelliott@io.com PMB 181, 11900 Metric Blvd Suite J
http://www.io.com/~pelliott/pme/ Austin TX 78758-3117
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Sun, 17 Sep 2006 18:00:34 GMT Raw View
Paul Elliott ha scritto:
> On 2004-09-03, Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
>
> Some kind of way to "cut off" the virtualness of a base class with
> respect to further derived classes.
>
> For example:
>
>
> class A : virtual base {...};
> class B : virtual base {...};
>
>
> class myclass : public A, public B {...};
>
> class derived: public myclass {...};
>
> There needs to be someway of saying that although the virtualness of
> base was necessary to construct myclass, the writers of classes like
> derived do not need to know how myclass was constructed (with respect
> to base). There needs to be someway of saying that base is NOT a
> virtual base class of derived. And that writers of "derived" like
> classes do not need to provide a ctor initializer for base, but that
> the ctor initializer of myclass (for base) should be used instead.
>
> I don't know possibly a notvirtual keyword?
>
> class myclass : public A, public B, notvirtual base {...};
>
> I don't insist on any particular syntax, but someway
> of providing this functionality.
>
There's no need for a special syntax, because the virtual-ness of a base
is not "inherited". If you want to specify that a base is virtual you
must re-specify the virtual keyword. In particular:
class myclass : public A, public B, public base {...};
means that "base" is a new non-virtual base-class distinct from any
other base-classes (virtual or not) already defined in A and B.
Ganesh
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: tommi.hoynalanmaa@iki.fi (=?ISO-8859-15?Q?Tommi_H=F6yn=E4l=E4nmaa?=)
Date: Sun, 17 Sep 2006 18:08:47 GMT Raw View
Paul Elliott kirjoitti:
> On 2004-09-03, Simon Richter <richtesi@informatik.tu-muenchen.de> wrote=
:
>> Hi,
>>
>> some time ago it has been asked in this group whether anyone had large=
-scale
>> wishes. Here are mine=20
Here are mine:
1. Add a module system supporting separate compilation of modules.
2. Remove the implicit conversion from floating point types to integer.
This conversion is actually a mathematical operation and it should be=20
explicitly written.
3. Add keyword "null" for the null pointer and remove the implicit=20
conversion from 0 to the null pointer.
--=20
Tommi H=F6yn=E4l=E4nmaa
s=E4hk=F6posti / e-mail: tommi.hoynalanmaa@iki.fi
kotisivu / homepage: http://www.iki.fi/tohoyn/
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Zorro" <zorabi@comcast.net>
Date: Thu, 24 Mar 2005 22:00:47 CST Raw View
===================================== MODERATOR'S COMMENT:
Please avoid top-posting in the comp. hierarchy of newsgroups.
===================================== END OF MODERATOR'S COMMENT
Hi. I seem to run into this somewhat late. The topic is interesting,
there are answers, and in fact more than the list discussed here. Also,
since this is a wish list, as far as standard goes nothing discussed
within this topic is part of standard C++ yet. They will be someday.
Invariant. This has been nicely done with following syntax:
invariant (boolean-expression) action;
where action is either raising an exception or calling another method
(called trigger). The points made in various sections of this topic are
treated properly.
There is also the notion of "constraint" associated with methods, with
similar syntax, but different semantics.
Enumeration. The extension has been done, same syntax/semantics. In
fact there are also successor/predecessor operators and bracket
functions associated with enumerations.
Furthermore, an extension of enumeration, called collection, allows
associating class-type objects with enumeration literals as opposed to
int only.
The wish-list actually implemented is considerably larger than the
items discussed in this topic. You can get the compiler from
www.zhmicro.com where you can also see actual examples and white papers
explaining the items in the wish list.
Sincerely,
Dr. Honargohar.
Simon Richter wrote:
> Hi,
>
> some time ago it has been asked in this group whether anyone had
large-scale
> wishes. Here are mine (in a short form, for some of these I've
started writing
> longer texts about them). I'd like to have a bit of feedback whether
anything
> like this would be worth pursuing or whether that makes no sense at
all.
>
> - Explicit invariants
>
> For example, a string class that allocates more memory than it
actually uses
> could do the following:
>
> class mystring {
> /* ... */
> private:
> size_t allocated;
> size_t length;
> (length < allocated);
> };
>
> This would mean that any attempt to store a value to allocated or
length that
> would violate the invariant (i.e. make the expression within the
parentheses
> evaluate to false) would throw an exception before the offending
value has been
> written (that is, the assignment operator would throw).
>
> Invariants would be part of the API if defined in public or protected
sections,
> they may only reference members of the same visibility.
>
> Optionally, there could be a special treatment for the case where the
expression
> is a boolean variable only: It is allowed to store "false" into this
variable,
> thus invalidating the object, but any code path exiting from the
current
> method or calling a method on this object will lead to an exception
being
> thrown (this still needs a lot of thinking to get consistent, but has
a lot of
> potential IMO).
>
> - dynamic_cast<char>(int) and co.
>
> This would be a range checked cast. If the variable would overflow
when stored
> to the smaller type, a std::bad_cast will be thrown. If the
destination type is
> larger or equally sized, this behaves like static_cast.
>
> - "Inheritance" of enum values
>
> This kind of inheritance would actually work the other way round:
>
> enum Base {
> Value1,
> Value2,
> Value3
> };
>
> enum Derived : Base {
> Value4,
> Value5,
> Value6
> };
>
> Now, a variable of type Base can store Value1-3, and a variable of
type Derived
> can store Value1-6. It is always possible to cast a variable of type
Base to
> Derived, for the other direction there needs to be a
dynamic_cast<Base &> that
> checks if the current value is in range. Multiple "inheritance" can
be done if
> the values used are distinct (but this is a can of worms).
>
> - using object names from different namespaces with a different name
>
> namespace foo {
> const char *bar = "bar";
> }
>
> namespace baz {
> using foo::bar = frobboz;
> }
>
> int main(int, char **) {
> std::cout << baz::frobboz << std::endl;
> }
>
> This should actually compile, I think. :-)
>
> - "compatible" yet distinct types
>
> This would allow to specify that a given class has the same memory
layout as
> another class it is derived from and thus it is basically safe to
convert a
> reference/pointer to the base class to a reference/pointer to the
derived
> class. This could be used for example where input checking is to be
performed:
>
> class string { /* ... */ };
>
> class trusted_string : compat string;
>
> Lvalue casts from the base to the derived class have to be explicit.
Since the
> memory layout is the same, such types only have a single dynamic type
(the base
> type). The idea is to have "markers" that you can attach to specific
types that
> are evaluated at compile time and can be used to distinguish purposes
for
> things that are really the same type (like, user names and file
names).
>
> Optionally, one could try to make the conversion operator check for
specific
> constraints on the object before allowing the cast; this is difficult
however
> since there may be references to the base type floating around which
could be
> used to modify the object after the checks have been performed.
>
> - nonblocking streams and stream transactions
>
> iostreams should support a nonblocking mode (separate for read and
write) that
> makes it possible to extract data only if available or insert data
only if that
> would not block the program, and, additionally, transaction objects
that can be
> constructed on top of a stream, allow the same operations as the
stream itself
> but make the effects on the stream permanent only when commit() is
called on
> them before their destruction. These would be required for proper I/O
> multiplexing. (Write transactions are easy, however read transactions
would
> require streambufs to be able to save all characters in the buffer
while
> reading on, which would be nontrivial).
>
> Comments?
>
> Simon
>
> ---
> [ comp.std.c++ is moderated. To submit articles, try just posting
with ]
> [ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu
]
> [ --- Please see the FAQ before posting. ---
]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html
]
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: cppljevans@cox-internet.com (Larry Evans)
Date: Tue, 29 Mar 2005 21:45:43 GMT Raw View
On 03/24/2005 10:00 PM, Zorro wrote:
> Hi. I seem to run into this somewhat late. The topic is interesting,
> there are answers, and in fact more than the list discussed here. Also,
[snip]
> Enumeration. The extension has been done, same syntax/semantics. In
> fact there are also successor/predecessor operators and bracket
> functions associated with enumerations.
I've also needed something like that, but had to create a work-around
as shown here:
http://boost-sandbox.sourceforge.net/vault/index.php?&direction=0&order=&directory=cppljevans/mpl
in range_all.zip. The analog to z++'s successor and predecessor
operators, I guess, boost::mpl's next and prior templates as defined
in range_all.zip.
>
> Furthermore, an extension of enumeration, called collection, allows
> associating class-type objects with enumeration literals as opposed to
> int only.
Again, I'm glad someone else has seen the need for associating a type
with an enumeration value. This issue has occurred before:
http://www.mail-archive.com/boost@lists.boost.org/msg03806.html
and one work-around is the indexed_types subdirectories of:
http://cvs.sourceforge.net/viewcvs.py/boost-sandbox/boost-sandbox
In particular, the libs/indexed_types/test/composite_product_test.cpp
shows the work-around uses a metafunction
( http://www.boost.org/libs/mpl/doc/refmanual/metafunction.html )
to associate a type with a type by specialization
( e.g. see:
template<>
struct field_types<c>
in the above .cpp file
)
on the enumerators in the enumeration. Obviously, z++'s method is more
direct and intuitive.
[snip]
BTW, I saw no mention of garbage collection in the white pages. Does
z++ use anything besides what c++ use's to do memory management?
TIA.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Wed, 22 Sep 2004 17:15:35 GMT Raw View
nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message news:<414ff1ad$0$39258$14726298@news.sunsite.dk>...
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:belvis-501FEA.01482020092004@news.la.sbcglobal.net...
> | Less conservative answer: if a programmer decides to throw an exception
> | when invariants are checked, it's assumed that that invariants
> | succeeded, and the programmer knows what he's doing. In this model,
> | invariants are considered valid unless we take a code path that leads to
> | std::broken_invariant(). If a programmer avoids getting to
> | std::broken_invariant(), even by throwing an exception, then the
> | invariants are considered valid.
> |
> | I find the less conservative answer appealing, but I think it might have
> | problems. For example, it seems clear that public member functions must
> | check their invariants if they are exited via an exception, but if an
> | invariant function can also throw an exception, we're back to two
> | exceptions and std::terminate().
>
> yeah, that's why you and John basically convinced me it was a bad idea :-)
>
> | Maybe this is OK; there is a analogous situation with destructors; they
> | are allowed to throw exceptions, and there are cases where a destructor
> | throwing is completely harmless, but in general it's such a bad idea
> | that no one does it. Perhaps we could use the less conservative approach
> | and allow programmers to write invariants that call functions that throw
> | for the sake of some limited contexts where it's useful, but it could be
> | understood that in general it's a bad idea.
>
> It could at least be beneficial to get the exception's context/type/what()
> even though std::broken_invariant()
> is called. I'm not sure how that could be incorporated.
In the "less conservative" approach above, std::broken_invariant()
would never be called. I imagine it being implemented as follows. The
user writes:
class Foo {
public:
Foo() : mThrow(false) { }
void f();
private:
bool check(void)
{
if (mThrow)
throw "hello";
return true;
}
invariant
{
check();
}
// This variable controls whether the invariant
// check will throw or not. We set it during a
// function to cause the invariant check to
// throw at function exit.
bool mThrow;
};
void Foo::f()
{
mThrow = true;
throw "world";
}
The compiler generates something like:
// Generated class:
class Foo_invariant {
public:
Foo_invarant(Foo& iFoo) : mFoo(iFoo)
{
mFoo.invariant();
}
~Foo_invarant(void)
{
mFoo.invariant();
}
private:
Foo& mFoo
};
void Foo::f()
{
// compiler inserts this sentry object:
Foo_invariant(*this);
mThrow = true;
throw "world";
}
// generated function:
void Foo::invariant()
{
if (!check())
std::broken_invariant();
}
The class Foo_invariant is generated to check invariants whether the
function exits via an exception or a normal return. When Foo::f()
throws, the destructor of the Foo_invariant executes, which calls
Foo::invariant(), which in turn calls the Foo::check() function, which
throws a second exception. We never reach the std::broken_invariant()
call.
Note that it Foo::f() didn't throw an exception of its own, we'd have
a situation where there is only one exception active, and everything
would be OK (except for the ambiguous fate of the Foo_invariant
object; did it get destroyed or not?)
This example shows how throwing from invariants is analogous to
throwing from destructors, and implies that it's a bad idea for the
same kinds of reasons that throwing destructors are a bad idea.
However, it could be implemented by having the compiler wrap
everything in a generate try-catch:
void Foo::f()
{
invariant();
try {
// compiler inserts everything except these two lines:
mThrow = true;
throw "world";
}
catch (...) {
invariant();
throw;
}
invariant();
}
In this version, the first call to invariant doesn't throw (because
mThrow is false). The try block sets mThrow to true and throws, which
immediately takes us to the catch handler. The catch handler checks
the invariant again, but this time it throws, because mThrow is true.
So in this version we don't have two simultaneous exceptions; rather,
the exception thrown directly by Foo::f() is discarded in favor of the
one from Foo::check().
This gets around the two exception problem, but feels wrong to me;
presumably Foo::f() threw the exception because it wanted a caller to
deal with something Foo::f() couldn't deal with, and that information
is lost. Admittedly, that information is lost due to the decision of
Foo's author, so maybe that's what the intention is. However, it
strikes me as error-prone. It seems like it would be easy to forget
the throwing behavior of the invariants when writing Foo::f(). In
cases where losing the first exception is unintended, having it
silently disappear would make it a hard problem to debug. For this
reason I actually prefer the first approach above where we end up with
two simultaneous exceptions and a call to std::terminate() -- that way
the programmer is alerted to the fact that something bogus is going
on.
All this of course begs the question of why someone would throw an
exception from inside an invariant check anyway. I can't think of a
good reason for doing so, which is another reason why I'm willing to
prefer the first implementation above and the std::terminate()
behavior. The more I think about it, the more it seems that throwing
from an invariant check is similar to throwing from a destructor.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 23 Sep 2004 16:33:20 GMT Raw View
belvis@pacbell.net (Bob Bell) wrote in message news:<c87c1cfb.0409211319.4ddbecfb@posting.google.com>...
> void Foo::f()
> {
> // compiler inserts this sentry object:
> Foo_invariant(*this);
>
> mThrow = true;
>
> throw "world";
> }
D'oh! I hope it was understood that the compiler-inserted sentry was
meant to exist until the end of the function:
void Foo::f()
{
// compiler inserts this sentry object:
Foo_invariant __sentry(*this);
..
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Bob Bell <belvis@pacbell.net>
Date: Mon, 20 Sep 2004 23:23:20 CST Raw View
In article
<belvis-E6BB6D.22110812092004@newssvr21-ext.news.prodigy.com>,
belvis@pacbell.net (Bob Bell) wrote:
> In article <4142cb3e$0$209$14726298@news.sunsite.dk>,
> nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
> > How would you forbid that a function called from within an invariant did
> > not
> > throw?
>
> If we abort when invariants fail, it shouldn't be a problem. But if a
> programmer finds a way to do it, I wouldn't consider it a big deal.
After finally reading your Contract Programming proposal, I realize that
I misunderstood this question.
Conservative answer: invariants are checked as if they are called from a
function that looks like:
void check_invariants()
{
try {
invariant();
}
catch (...) {
std::broken_invariant();
}
}
That is, any exception that escapes from the invariants goes straight to
std::broken_invariant().
Another variation is to make a rule that all invariants are evaluated as
if they have an empty throw specification, which means an invariant
function that throws anything would go straight to std::unexpected().
Less conservative answer: if a programmer decides to throw an exception
when invariants are checked, it's assumed that that invariants
succeeded, and the programmer knows what he's doing. In this model,
invariants are considered valid unless we take a code path that leads to
std::broken_invariant(). If a programmer avoids getting to
std::broken_invariant(), even by throwing an exception, then the
invariants are considered valid.
I find the less conservative answer appealing, but I think it might have
problems. For example, it seems clear that public member functions must
check their invariants if they are exited via an exception, but if an
invariant function can also throw an exception, we're back to two
exceptions and std::terminate().
Maybe this is OK; there is a analogous situation with destructors; they
are allowed to throw exceptions, and there are cases where a destructor
throwing is completely harmless, but in general it's such a bad idea
that no one does it. Perhaps we could use the less conservative approach
and allow programmers to write invariants that call functions that throw
for the sake of some limited contexts where it's useful, but it could be
understood that in general it's a bad idea.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Tue, 21 Sep 2004 13:05:10 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message
news:belvis-501FEA.01482020092004@news.la.sbcglobal.net...
| In article
| <belvis-E6BB6D.22110812092004@newssvr21-ext.news.prodigy.com>,
| belvis@pacbell.net (Bob Bell) wrote:
|
| > In article <4142cb3e$0$209$14726298@news.sunsite.dk>,
| > nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
| > > How would you forbid that a function called from within an invariant did
| > > not
| > > throw?
| >
| > If we abort when invariants fail, it shouldn't be a problem. But if a
| > programmer finds a way to do it, I wouldn't consider it a big deal.
|
| After finally reading your Contract Programming proposal, I realize that
| I misunderstood this question.
|
| Conservative answer: invariants are checked as if they are called from a
| function that looks like:
|
| void check_invariants()
| {
| try {
| invariant();
| }
| catch (...) {
| std::broken_invariant();
| }
| }
[snip]
| Less conservative answer: if a programmer decides to throw an exception
| when invariants are checked, it's assumed that that invariants
| succeeded, and the programmer knows what he's doing. In this model,
| invariants are considered valid unless we take a code path that leads to
| std::broken_invariant(). If a programmer avoids getting to
| std::broken_invariant(), even by throwing an exception, then the
| invariants are considered valid.
|
| I find the less conservative answer appealing, but I think it might have
| problems. For example, it seems clear that public member functions must
| check their invariants if they are exited via an exception, but if an
| invariant function can also throw an exception, we're back to two
| exceptions and std::terminate().
yeah, that's why you and John basically convinced me it was a bad idea :-)
| Maybe this is OK; there is a analogous situation with destructors; they
| are allowed to throw exceptions, and there are cases where a destructor
| throwing is completely harmless, but in general it's such a bad idea
| that no one does it. Perhaps we could use the less conservative approach
| and allow programmers to write invariants that call functions that throw
| for the sake of some limited contexts where it's useful, but it could be
| understood that in general it's a bad idea.
It could at least be beneficial to get the exception's context/type/what()
even though std::broken_invariant()
is called. I'm not sure how that could be incorporated.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Wed, 15 Sep 2004 03:01:39 GMT Raw View
richtesi@informatik.tu-muenchen.de (Simon Richter) wrote in message news:<ci5bjj$53t4j$1@sunsystem5.informatik.tu-muenchen.de>...
> Hi,
>
> belvis@pacbell.net (Bob Bell) writes:
> >void B::G()
> >{
> > // break invariants
> > mA.F();
>
> .. this call is illegal. B's invariants are broken, hence this is marked
> "invalid", and all members are marked too, thus the static type at the
> point of the call is "invalid A", and only calls that accept an "invalid"
> A object are allowed.
Now I'm really confused. First, B::G() breaks B's invariants, not A's,
so why should A be marked as invalid? Why should the call to A::F() be
disallowed?
Second, how can invariants be analyzed statically? Let me rewrite a
bit:
void B::G()
{
if (/* condition based on user input */) {
// break invariants
}
mA.F();
// restore invariants
}
Is the static type of B valid or invalid? It sounds like you're saying
that the static type of the B changes throughout the function
depending on whether the invariants are broken, but I can't imagine
the compiler being able to track that most of the time.
Third, what does it mean to have statically typed valid/invalid
functions? Can I overload on valid/invalid? Can I override a valid
function without affecting an invalid function of the same name? Can a
valid function override an invalid one? Does a valid function in a
derived class hide an invalid one in a base class?
> >It isn't enough to provide a mechanism that allows a programmer to avoid
> >the situation. To solve the problem, it must be impossible for a
> >programmer to write code that exhibits the problem at all.
>
> In my opinion, it is enough to give the programmer the necessary tools. It
> is no problem at all to shoot yourself in the foot, but you should be able
> to specify that your gun should make funny noises when you point it at your
> foot so you can decide not to pull the trigger.
Agreed. My example was designed to probe the limits of your solution.
> >No one has yet demonstrated how a program can decide at runtime whether
> >an invariant failure is important enough to stop the program, or that
> >it's OK to continue running.
>
> This is something I'm not overly sure that it's the right thing to do,
> however it appears to me as the most flexible solution.
Flexibility without a demonstrated benefit is just extra complexity.
Why have it?
> If it is not possible
> to do this consistently, I'm happy with dropping exceptions and moving to
> implementation defined behaviour.
I think it's significant that no one has yet described how to do this
*inconsistently*, let alone consistently.
> >How will the catching code know that the object has a boolean invariant,
> >and that it's OK to destroy the object? Isn't an object's invariants and
> >their interactions with each other an implementation detail?
>
> That is not the difficult thing. The difficult thing is for the runtime to
> know *which* object was damaged.
There are several difficult things:
-- determining which object(s) is damaged
-- determining how extensive the damage is
-- determining what to do with (or to) the damaged object(s)
-- determining whether to keep running
More than one person in this thread has suggested that these things
are doable in code, but no one has demonstrated how. I've given it a
lot of thought myself over the years, and I haven't yet figured out
how to write code to do any of these things reliably. I think it's
possible only in limited contexts, and even then it's arguable that
you're really writing code that deals with broken invariants. It seems
to me that if you write code that can recover from a broken invariant,
all you're really doing is expanding the scope of the invariants to
allow states that you previously disallowed.
> Slowly I'm getting more and more convinced that exceptions are not the right
> way to do this and we should rather abort at the first violation and dump
> core (or whatever). This will also solve your case (we get a trace from the
> first evil function that we encounter. That the other object is damaged as
> well does not count since it may be restored before it is checked.
That's the point I've been trying to make: don't throw when invariants
are violated, stop the program and get the programmer's attention.
Yes, it solves the problems I've raised, and yes, the fact that
multiple objects are damaged is not an issue.
> Few programs will ever catch invariant failures. Most of them will let the
> exception propagate in order to get the cleanest possible shutdown.
So you anticipate the feature "programs can continue running when
invariants fail" will be seldom taken advantage of? Given that it has
significant negatives attached to it, and that it will be seldom used,
I propose that the invariant checker not allow a program to continue.
If you grant me that, then it's obvious that exceptions shouldn't be
used when invariants fail, since exceptions allow a program to
continue.
There's no reason why the invariant checker can't allow some kind of
cleanup code to be executed when invariants fail; it just shouldn't be
run in a catch handler or RAII destructor.
> >AFAIK, under Unix that's exactly what assert does.
>
> Indeed. The point in assert() is that it can be left out of production code;
> this is not what I have in mind for invariants (otherwise I could just have
> a class that verifies some things in another object when constructed or
> destructed and place an instantiation of it in every function).
Yes, but I think a programmer would want to have the option of
removing invariant checking in optimized builds. I know I'd like that
option.
> >I'd like invariant failure behavior to be defined in a similar way to
> >assert, where the implementation is allowed to write debug messages,
> >dump core, etc., but is not allowed to let the program continue.
>
> So basically we can agree on "implementation-defined".
No; just saying "implementation defined" means the implementation
could allow the program to continue. It has to be stronger, where the
implementation is allowed to do certain things, but not others.
> >> And allow speedups by moving validity tests from readers to writers.
>
> >I don't get this; invariant checking cannot allow speedups compared to
> >no invariant checking.
>
> Yes, but assuming the invariants to be met at the start of a function will
> lead to speedups and smaller code, as the conditions don't need to be
> rechecked. This is something assertions don't give us, since the checks
> will be in the functions, either as an assertion or later on.
These speedups you mention are not a benefit of invariant checking,
but they may be a benefit of one form of invariant checking vs.
another form. Invariant checking lets me find bugs, and that's about
it. If I take a program which doesn't check invariants, and add
invariant checking, the only thing I gain is the ability to more
easily (automatically?) find bugs, but the program will almost
certainly slow down. Programmers need to know that so that they can
decide on what "checks vs. performance" tradeoffs to make.
Certainly there is an optimization issue here in terms of trying to
design a system that impacts performance as little as possible, but in
my mind that's of secondary importance. The primary issue is that the
system work the way a programmer expects. Once that's in place,
optimizations can be performed using the "as if" rule: a function
executes "as if" the invariants are checked, even if they aren't.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: pasa@lib.hu ("Balog Pal")
Date: Mon, 13 Sep 2004 14:35:45 GMT Raw View
===================================== MODERATOR'S COMMENT:
Please note that if the idea is disassociated from the C++ language, it's likely to become offtopic in this newsgroup as well. :-)
===================================== END OF MODERATOR'S COMMENT
"Bob Bell" <belvis@pacbell.net> wrote in message
news:belvis-D82FCF.22235912092004@newssvr21-ext.news.prodigy.com...
[lots of repeated and still unanswered questions regarding implicit
invariant check]
I have an idea -- what about disassociate this ivariant checking stuff from
the language?
The discussion showed it is not really a thing to have in the final build,
just instrumentation that helps testing. If so, why not put it in the
debugger? :)
Debuggers had hardware breakpoints and expression breakpoints even long ago
(I didn;t say they worked reliably though :). Some #pragma could be used to
emit metadata to debugger defining the invariant, and another one would
control generation of call points.
Then the debugger can tie them together, and break on failed check.
Just like it happens on ASSERT, the additional stuff is automagical
placement of certain asserts, that makes the source more readable and less
easier to mess up.
Having the whole idea formally defined some vendor could implement it, and
then further experiments and thinking could find some more sophisticated
use.
Paul
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Mon, 13 Sep 2004 16:52:58 GMT Raw View
Bob Bell wrote:
> In article <frv0d.14060$QJ3.12504@newssvr21.news.prodigy.com>,
> nagle@animats.com (John Nagle) wrote:
>> I just can't see design by contract going into C++.
>>I like design by contract. But it implies a level of rigor
>>foreign to the C++ community.
>
>
> We don't have to throw the baby out with the bathwater. Just because
> exceptions are a poor way to signal invariant failures in C++ doesn't
> mean that we can't include _some_ kind of support for DBC in C++.
It's not just a problem with exceptions.
There's synchronization and locking. (Can one thread
invalidate the invariant while another thread is assuming
it holds?) There's the blocking issue. (If a thread
blocks inside an object, and other threads can then call
the object's function members, what is the state of the
invariant?) There's the inside/outside issue (Exactly when
does control "enter" and "leave" the object? What about
the "A calls B which calls A" issue?) There's
the public data member issue (Should invariants be
allowed to mention public data members? If so, what
does that mean?) There's optimization. (When do we have
to check invariants, and how much checking can be safely
optimized out for each function member?)
Threads, locking, exceptions, cancellation, and invariants
all need to be designed to work together. All these little
proposals in those areas aren't addressing the hard cases
for interoperability.
A sound approach would probably require "strict objects",
for which tighter rules apply and for which design by
contract was supported. Such objects would need rules
similar to those of Eiffel.
It's worth noting that invariant checking can be optimized
extensively if the language is tight enough. If you have
confidence that an object can't be changed when control is
outside it, you only need to validate invariants at object
exit. And you only need to validate the terms of the invariant
that involve variables a function member can modify. So
access functions don't need to check invariants at all.
This gets the overhead down to the point you may be able to
leave invariant checking on in production code. So there's
a major performance win if this is integrated into the
language, rather than added on with some macro or template
add-on.
Is anyone working seriously on this?
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Mon, 13 Sep 2004 18:20:55 GMT Raw View
In article <4142cb3e$0$209$14726298@news.sunsite.dk>,
nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:belvis-AC4E21.23302709092004@news.la.sbcglobal.net...
> | In article <4140e863$0$211$14726298@news.sunsite.dk>,
> | nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
> | > "Bob Bell" <belvis@pacbell.net> wrote in message
>
> | > | Bar::G() breaks Bar's invariants and then calls Foo::F(). Foo::F()
> | > | erroneously breaks Foo's invariants and fails to restore them. At
> | > | Foo::F() exit time, the invariant checker runs, detects the violation
> | > | and throws InvariantViolation. When the exception passes up through
> | > | Bar::G(), the invariant checker runs again,
> | >
> | > actually no...we agreed that invariants are not checked if the function
> exits
> | > due to an exception.
> |
> | Then you have a situation where one or more objects has not had its
> | invariants checked. That sounds like a deal-breaker to me.
> |
> | I can't tell if you're misunderstanding the problem I'm describing, or
> | if you do understand, but just don't think it's a big deal.
>
> I think it must have been a bit of both.
I'm sure you're right; I know that I sometimes write crystal clear prose
that when someone else thinks it works I'm sure.
Like that. ;-)
> I don't think its possible to
> detect more than one of the two in your example.
>
> But that should be ok for most purposes if we choose to abort the
> program as soon as a violation is detected (as you suggest).
Of course; if you abort right away there is no problem. The problem only
occurs if you want the program to continue via an exception.
> | I thought the whole point was to have the system check invariants at all
> | the right times and places.
> |
> | > .. what the system can do is to check that an invariant have been broken.
> |
> | No, it can't in this case. That's the point.
> |
> | What good is an automatic system for checking invariants if it doesn't
> | automatically check invariants?
>
> yeah. FWIW, I changed the semantics in the soon-to-be-released
> paper to follow your suggestion. The means that
>
> 1. invariant violations never let exceptions escape
> 2. invariant violations leads to a call to terminate()
> 3 invariants are checked here:
> 1. end of constructor
> 2. around public functions
> 3. start of destructor
> 4. when a function exits via an exception
>
> | > | If not, then we have two ways of dealing with broken
> | > | invariants: one by throwing, and another via std::broken_invariant().
> | > | Why not just use std::broken_invariant() all the time? Wouldn't that
> | > | be simpler?
> | >
> | > yeah, maybe...it is definitely worth considering. I guess it would amount
> to
> | > saying
> | > that the invariant is always evaluated in a try-catch block as above.
> |
> | Or perhaps that checking invariants doesn't interact with exceptions at
> | all.
>
> How would you forbid that a function called from within an invariant did not
> throw?
If we abort when invariants fail, it shouldn't be a problem. But if a
programmer finds a way to do it, I wouldn't consider it a big deal.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Mon, 13 Sep 2004 19:34:31 GMT Raw View
"John Nagle" <nagle@animats.com> wrote in message
news:RKj1d.15041$QJ3.11211@newssvr21.news.prodigy.com...
| Bob Bell wrote:
| > In article <frv0d.14060$QJ3.12504@newssvr21.news.prodigy.com>,
| > nagle@animats.com (John Nagle) wrote:
|
| >> I just can't see design by contract going into C++.
| >>I like design by contract. But it implies a level of rigor
| >>foreign to the C++ community.
| >
| >
| > We don't have to throw the baby out with the bathwater. Just because
| > exceptions are a poor way to signal invariant failures in C++ doesn't
| > mean that we can't include _some_ kind of support for DBC in C++.
|
| It's not just a problem with exceptions.
|
| There's synchronization and locking. (Can one thread
| invalidate the invariant while another thread is assuming
| it holds?)
How does this interfere with invariants? I mean, this would only be for
objects shared
between two (or more) threads, right?
| There's the blocking issue. (If a thread
| blocks inside an object, and other threads can then call
| the object's function members, what is the state of the
| invariant?)
Hm...does an invariant have state? Or do you simply mean the state space
{ok,broken}?
Let's assume blocking occurs. When another thread call a function on the same
object,
should that result in a call to the invariant? Seems to me it should to make
sure the object is valid
for the pending function call. Would there be any problem with this?
| There's the inside/outside issue (Exactly when
| does control "enter" and "leave" the object?
even though this cannot be detected, you still get some benefit...which is
better than none.
| What about
| the "A calls B which calls A" issue?)
you can add extra checks manually or the language could mandate
always to check the invariant.
| There's
| the public data member issue (Should invariants be
| allowed to mention public data members?
sure, why not? and private too.
| If so, what
| does that mean?)
too philosophical for me...could you explain?
| There's optimization. (When do we have
| to check invariants, and how much checking can be safely
| optimized out for each function member?)
I think most benefits would come from
1. better design
2. better documentation
3. easier debugging
but that in the end, very few programs would keep invariants in release
builds.
| Threads, locking, exceptions, cancellation, and invariants
| all need to be designed to work together. All these little
| proposals in those areas aren't addressing the hard cases
| for interoperability.
Which little proposals are you referring to?
| It's worth noting that invariant checking can be optimized
| extensively if the language is tight enough. If you have
| confidence that an object can't be changed when control is
| outside it, you only need to validate invariants at object
| exit. And you only need to validate the terms of the invariant
| that involve variables a function member can modify. So
| access functions don't need to check invariants at all.
compiler vendors can provide a multiple of different flags to
generate so and so much code from the contracts.
| This gets the overhead down to the point you may be able to
| leave invariant checking on in production code. So there's
| a major performance win if this is integrated into the
| language, rather than added on with some macro or template
| add-on.
|
| Is anyone working seriously on this?
depends on what "this" means.
best regards
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Mon, 13 Sep 2004 20:09:46 GMT Raw View
Thorsten Ottosen wrote:
> | It's not just a problem with exceptions.
> |
> | There's synchronization and locking. (Can one thread
> | invalidate the invariant while another thread is assuming
> | it holds?)
>
> How does this interfere with invariants? I mean, this would only be for
> objects shared
> between two (or more) threads, right?
Well, yes.
>
> | There's the blocking issue. (If a thread
> | blocks inside an object, and other threads can then call
> | the object's function members, what is the state of the
> | invariant?)
>
> Hm...does an invariant have state? Or do you simply mean the state space
> {ok,broken}?
The question is whether the invariant must be made true
before the object is unlocked and the thread blocks.
If not, the invariant will be
false when some other thread calls a public function member,
which is an error. If so, there must be support for
insuring the invariant is made true before the object is
unlocked.
If you're going to have object invariants, you need to
be very clear about when they are supposed to be true.
In particular, you need to make sure that they're required
to be true at all the places you exit an object, because
you're expecting them to be true when you enter it.
That's why there's such need for clarity about what
"enter", "exit", "inside", and "outside" means.
>
> Let's assume blocking occurs. When another thread call a function on the same
> object,
> should that result in a call to the invariant? Seems to me it should to make
> sure the object is valid
> for the pending function call. Would there be any problem with this?
>
> | There's the inside/outside issue (Exactly when
> | does control "enter" and "leave" the object?
>
> even though this cannot be detected, you still get some benefit...which is
> better than none.
"Well, it works some of the time" is not an adequate response.
>
> | What about
> | the "A calls B which calls A" issue?)
>
> you can add extra checks manually or the language could mandate
> always to check the invariant.
>
> | There's
> | the public data member issue (Should invariants be
> | allowed to mention public data members?
>
> sure, why not? and private too.
>
> | If so, what
> | does that mean?)
>
> too philosophical for me...could you explain?
Read up on Eiffel. Or read some of the DEC WRL papers
on their Java verifier.
>
> | There's optimization. (When do we have
> | to check invariants, and how much checking can be safely
> | optimized out for each function member?)
>
> I think most benefits would come from
>
> 1. better design
> 2. better documentation
> 3. easier debugging
No, that's not the point. The idea is to eliminate whole classes
of bugs. If it can't do that, it's not worth the work.
>
> but that in the end, very few programs would keep invariants in release
> builds.
Both Java and C# provide subscript checking, and it's usually on.
C++ has to do some catching in the safety department.
> | Threads, locking, exceptions, cancellation, and invariants
> | all need to be designed to work together. All these little
> | proposals in those areas aren't addressing the hard cases
> | for interoperability.
>
> Which little proposals are you referring to?
Read the recent postings in this newsgroup on thread
support for C++.
>
>
> | It's worth noting that invariant checking can be optimized
> | extensively if the language is tight enough. If you have
> | confidence that an object can't be changed when control is
> | outside it, you only need to validate invariants at object
> | exit. And you only need to validate the terms of the invariant
> | that involve variables a function member can modify. So
> | access functions don't need to check invariants at all.
>
> compiler vendors can provide a multiple of different flags to
> generate so and so much code from the contracts.
That's irrelevant. My point is that invariant checking
need not be expensive. It tends to be expensive in current
bolt-on implementations (using macros, templates, or
preprocessors) because they don't have enough information
to tell when an invariant check can be safely avoided.
But any serious compiler has internal data structures that can
answer the question "can variable A possibly be modified
between code points P and Q". Once you have that,
invariant check optimization is straightforward.
We may need "strict objects", with tighter rules, for
design by contract. The rules have
to be tighter for such objects, and that impacts backwards
compatibility.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Mon, 13 Sep 2004 22:06:00 GMT Raw View
"John Nagle" <nagle@animats.com> wrote in message
news:n0n1d.15116$QJ3.3697@newssvr21.news.prodigy.com...
| Thorsten Ottosen wrote:
| > | There's the blocking issue. (If a thread
| > | blocks inside an object, and other threads can then call
| > | the object's function members, what is the state of the
| > | invariant?)
| >
| > Hm...does an invariant have state? Or do you simply mean the state space
| > {ok,broken}?
|
| The question is whether the invariant must be made true
| before the object is unlocked and the thread blocks.
| If not, the invariant will be
| false when some other thread calls a public function member,
| which is an error. If so, there must be support for
| insuring the invariant is made true before the object is
| unlocked.
well, that could be left a burden of the programmer. It won't affect 99% of
all code.
If C++ were to provide certain lock mechanisms, then the lock could call
this->invariant() in the destructor, like
template< class Locked >
struct lock
{
Locked this_;
lock( Locked* r ) : this_(r) { /*lock*/ }
~lock(){ this_->invariant(); /*unlock*/ }
};
| That's why there's such need for clarity about what
| "enter", "exit", "inside", and "outside" means.
its always nice with clarity.
| > Let's assume blocking occurs. When another thread call a function on the
same
| > object,
| > should that result in a call to the invariant? Seems to me it should to
make
| > sure the object is valid
| > for the pending function call. Would there be any problem with this?
| >
| > | There's the inside/outside issue (Exactly when
| > | does control "enter" and "leave" the object?
| >
| > even though this cannot be detected, you still get some benefit...which is
| > better than none.
|
| "Well, it works some of the time" is not an adequate response.
whatever you imagine contract programming to be, then it is not a panacea
that automatically will solve all your problems. that said, I believe contract
gives *substantial* benefits even though it does not solve all you
multithread/shared object
problems magically.
| > | What about
| > | the "A calls B which calls A" issue?)
| >
| > you can add extra checks manually or the language could mandate
| > always to check the invariant.
| >
| > | There's
| > | the public data member issue (Should invariants be
| > | allowed to mention public data members?
| >
| > sure, why not? and private too.
| >
| > | If so, what
| > | does that mean?)
| >
| > too philosophical for me...could you explain?
|
| Read up on Eiffel.
| Or read some of the DEC WRL papers
| on their Java verifier.
I know both of those languages, but its not obbvious what it has to do with
contract programming.
(In eiffel you might access a public variable without knowing it because it
doesn't have the () syntax)
Whether a programmer would like to mention public member variables in a
contract should be up to him.
Personally, I would never do it, but why prohibit it?
| >
| > | There's optimization. (When do we have
| > | to check invariants, and how much checking can be safely
| > | optimized out for each function member?)
| >
| > I think most benefits would come from
| >
| > 1. better design
| > 2. better documentation
| > 3. easier debugging
|
| No, that's not the point. The idea is to eliminate whole classes
| of bugs. If it can't do that, it's not worth the work.
I think we have to agree to disagree.
| > but that in the end, very few programs would keep invariants in release
| > builds.
|
| Both Java and C# provide subscript checking, and it's usually on.
| C++ has to do some catching in the safety department.
which only makes contract programming even more needed in C++
| > | It's worth noting that invariant checking can be optimized
| > | extensively if the language is tight enough. If you have
| > | confidence that an object can't be changed when control is
| > | outside it, you only need to validate invariants at object
| > | exit. And you only need to validate the terms of the invariant
| > | that involve variables a function member can modify. So
| > | access functions don't need to check invariants at all.
| >
| > compiler vendors can provide a multiple of different flags to
| > generate so and so much code from the contracts.
|
| That's irrelevant. My point is that invariant checking
| need not be expensive. It tends to be expensive in current
| bolt-on implementations (using macros, templates, or
| preprocessors) because they don't have enough information
| to tell when an invariant check can be safely avoided.
Even though you minimize the amount of calls to the invariant,
some queries will still be too expensive. Consider
size() == std::distance(begin(),end());
for a list.
| But any serious compiler has internal data structures that can
| answer the question "can variable A possibly be modified
| between code points P and Q". Once you have that,
| invariant check optimization is straightforward.
ok.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: richtesi@informatik.tu-muenchen.de (Simon Richter)
Date: Tue, 14 Sep 2004 00:55:33 GMT Raw View
Hi,
belvis@pacbell.net (Bob Bell) writes:
> Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
[long text]
>I have to confess that I find this all excessively complicated, and I'm
>not sure if I'm really following it, so forgive me if I'm
>misunderstanding. The best I can make of it is that this is a
>complicated way of saying that sometimes we let a programmer say that
>it's OK for some invariants to fail, by saying that any invariants tied
>to the boolean invariants (like "valid" above) don't have to hold if the
>boolean invariant is false.
Indeed.
If the boolean is not an invariant, there is no problem, since the invariant
says that either the boolean is false or the invariant is fulfilled (thus
we can test the value of the boolean and take the associated invariants for
granted if it tested true).
If the boolean is an invariant, this is the hard case everyone has been
talking about and that makes us all jump through hoops.
>Not only does this seem really complex (sometimes the invariant holds,
>sometimes it doesn't), it doesn't solve the problem I posed:
It solves this problem, because...
>class A {
> public:
> void F();
>};
>void A::F()
>{
> // break invariants
>}
>class B {
> public:
> void G();
> private:
> A mA;
>};
>void B::G()
>{
> // break invariants
> mA.F();
.. this call is illegal. B's invariants are broken, hence this is marked
"invalid", and all members are marked too, thus the static type at the
point of the call is "invalid A", and only calls that accept an "invalid"
A object are allowed.
Granted, you could also damage the A object from "A::F(void) invalid", or
invoke the member function on a global object (or one that is not a member).
Damaging the object from "A::F(void) invalid" will not lead to an exception,
as a function marked "invalid" is not expected to return the object in a
valid state. A deferred check could/should take place when the last boolean
invariant is restored and the object should be back in a valid state.
Using a global or other non-member object would definitely lead to an
exception, and in this case, into std::terminate(). I believe a warning
could be issued if the current object is invalid and the function may/will
cause an invariant failure if an exception is thrown at it and someone calls
a function with a non-empty throw specifier (Obviously, if an exception is
thrown after an invariant failure, functions that have invariants checked
will not be able to have an empty throw specifier unless they catch all
their invariant failures and are able to deal with them so that the
invariant will be met at function exit).
>It isn't enough to provide a mechanism that allows a programmer to avoid
>the situation. To solve the problem, it must be impossible for a
>programmer to write code that exhibits the problem at all.
In my opinion, it is enough to give the programmer the necessary tools. It
is no problem at all to shoot yourself in the foot, but you should be able
to specify that your gun should make funny noises when you point it at your
foot so you can decide not to pull the trigger.
>> We could also terminate() here, it makes no real difference. I prefer the
>> "exception" variant because it allows us to decide at runtime whether an
>> operation that failed because of a programming error was "important" enough
>> to warrant stopping the program.
>No one has yet demonstrated how a program can decide at runtime whether
>an invariant failure is important enough to stop the program, or that
>it's OK to continue running.
This is something I'm not overly sure that it's the right thing to do,
however it appears to me as the most flexible solution. If it is not possible
to do this consistently, I'm happy with dropping exceptions and moving to
implementation defined behaviour.
>> If the class has a "boolean" invariant, it needs to have a destructor that
>> can operate on an "invalid" this pointer.
>How will the catching code know that the object has a boolean invariant,
>and that it's OK to destroy the object? Isn't an object's invariants and
>their interactions with each other an implementation detail?
That is not the difficult thing. The difficult thing is for the runtime to
know *which* object was damaged.
Slowly I'm getting more and more convinced that exceptions are not the right
way to do this and we should rather abort at the first violation and dump
core (or whatever). This will also solve your case (we get a trace from the
first evil function that we encounter. That the other object is damaged as
well does not count since it may be restored before it is checked.
I fear we will still need the "invalid" modifier and the other things I
described in my last article, though, as the value of core dumps is
probably limited compared to the value of warnings for unsafe programming.
>> I also believe that invariant failures will
>> only seldom be caught.
>Do you mean "most programs will have a few functions which catch
>invariant failures" or "few programs will have any functions which catch
>invariant failures"? Or something else?
Few programs will ever catch invariant failures. Most of them will let the
exception propagate in order to get the cleanest possible shutdown.
>AFAIK, under Unix that's exactly what assert does.
Indeed. The point in assert() is that it can be left out of production code;
this is not what I have in mind for invariants (otherwise I could just have
a class that verifies some things in another object when constructed or
destructed and place an instantiation of it in every function).
>I'd like invariant failure behavior to be defined in a similar way to
>assert, where the implementation is allowed to write debug messages,
>dump core, etc., but is not allowed to let the program continue.
So basically we can agree on "implementation-defined".
>> And allow speedups by moving validity tests from readers to writers.
>I don't get this; invariant checking cannot allow speedups compared to
>no invariant checking.
Yes, but assuming the invariants to be met at the start of a function will
lead to speedups and smaller code, as the conditions don't need to be
rechecked. This is something assertions don't give us, since the checks
will be in the functions, either as an assertion or later on.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Sat, 11 Sep 2004 00:12:02 GMT Raw View
In article <4140e863$0$211$14726298@news.sunsite.dk>,
nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:c87c1cfb.0409091243.3cdd134a@posting.google.com...
> | nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message
> news:<414020e8$0$214$14726298@news.sunsite.dk>...
>
> | OK. How do you get around the problem you snipped? Where we either
> | have two exceptions, or one object with broken invariants without a
> | corresponding InvariantFailure exception?
>
> [example:]
>
> | here's another example:
> |
> | class Foo {
> | public:
> | void F();
> | };
> |
> | void Foo::F()
> | {
> | // break invariants
> | }
> |
> | class Bar {
> | public:
> | void G();
> | private:
> | Foo mF;
> | };
> |
> | void Bar::G()
> | {
> | // break invariants
> | mF.F();
> | // restore invariants
> | }
> |
> | Bar::G() breaks Bar's invariants and then calls Foo::F(). Foo::F()
> | erroneously breaks Foo's invariants and fails to restore them. At
> | Foo::F() exit time, the invariant checker runs, detects the violation
> | and throws InvariantViolation. When the exception passes up through
> | Bar::G(), the invariant checker runs again,
>
> actually no...we agreed that invariants are not checked if the function exits
> due to an exception.
Then you have a situation where one or more objects has not had its
invariants checked. That sounds like a deal-breaker to me.
I can't tell if you're misunderstanding the problem I'm describing, or
if you do understand, but just don't think it's a big deal.
> | void Foo::F()
> | {
> | // break invariants
> |
> | // the checker doesn't run because we are
> | // exiting via an exception
> |
> | throw "hello, world.";
> |
> | // restore invariants
> | }
> |
> | If I can break invariants and they aren't checked, it seems to me the
> | system isn't guaranteeing my invariants.
>
> correct...it makes no sense to say the system should guarantee your
> invariants...its your job
> to provide the basic guarantee of exception-safety
I thought the whole point was to have the system check invariants at all
the right times and places.
> .. what the system can do is to check that an invariant have been broken.
No, it can't in this case. That's the point.
What good is an automatic system for checking invariants if it doesn't
automatically check invariants?
> | > and lets say it is checked in the
> | > destructor like this
> | >
> | > Foo::~Foo()
> | > {
> | > try
> | > { invariant(); } catch( ... ) { std::broken_invariant(); }
> | > }
> |
> | What is std::broken_invariant()? I'm guessing it does not throw an
> | exception.
>
> yes, it doesn't.
What _does_ it do?
> | If not, then we have two ways of dealing with broken
> | invariants: one by throwing, and another via std::broken_invariant().
> | Why not just use std::broken_invariant() all the time? Wouldn't that
> | be simpler?
>
> yeah, maybe...it is definitely worth considering. I guess it would amount to
> saying
> that the invariant is always evaluated in a try-catch block as above.
Or perhaps that checking invariants doesn't interact with exceptions at
all.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Sat, 11 Sep 2004 01:45:49 GMT Raw View
nagle@animats.com (John Nagle) wrote in message news:<hac0d.18385$kf5.8729@newssvr29.news.prodigy.com>...
> Balog Pal wrote:
> > "Bob Bell" <belvis@pacbell.net> wrote in message
> > news:c87c1cfb.0409081404.52e63d28@posting.google.com...
> >
> > [mass cut]
> >
> >>There's only one reason to throw: something happened in a function
> >>that the function can't (or doesn't want to) deal with, but maybe some
> >>function higher up the call stack can deal with it. And the reason a
> >>caller might be able to handle the problem is that the caller will
> >>have more context for the failure.
> >>
> >>When an invariant fails, it's an entirely local problem. There is no
> >>higher-level context available for a caller, so a caller is no more
> >>likely to know how to deal with the problem. In fact, because of
> >>encapsulation, callers that are not members of the broken class will
> >>have _less_ context in which to deal with the problem. ...
>
> If you're going to try to recover from invariant failures,
> you need some notion of an object being in an "invalid" state.
There is no such thing as "recovering from invariant failures"; at
least, not if you mean letting the program continue to run after
you've discovered it's broken. That's not recovering, that's hoping.
By definition, when an invariant fails, your program has entered a
state that you thought it couldn't get into, and that your code
doesn't deal with.
> An invariant failure makes an object invalid. It's not clear
> what you do then. Arguably, it's then invalid to call any
> public method of the object. It's not even clear that you
> can call the destructor.
It's not even clear that the problems are limited to the object you
identified as being invalid. It could be another object in the system
that overwrote this object's memory to make it invalid. If that's the
case, the invalid object is not the problem at all, and isolating it
won't fix anything.
> Maybe the object has to stay around,
> broken, until the program exits.
That's one possibility. Another is to stop the program before the
damage can spread.
> The relationship between exception handlers and invariants
> is tough.
So tough, in fact, that perhaps we should pick a different mechanism
for dealing with broken invariants than exceptions.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Sat, 11 Sep 2004 16:58:59 GMT Raw View
Bob Bell wrote:
> nagle@animats.com (John Nagle) wrote in message news:<hac0d.18385$kf5.8729@newssvr29.news.prodigy.com>...
>
>>Balog Pal wrote:
>>
>>>"Bob Bell" <belvis@pacbell.net> wrote in message
>>>news:c87c1cfb.0409081404.52e63d28@posting.google.com...
>>An invariant failure makes an object invalid. It's not clear
>>what you do then. Arguably, it's then invalid to call any
>>public method of the object. It's not even clear that you
>>can call the destructor.
>
>
> It's not even clear that the problems are limited to the object you
> identified as being invalid. It could be another object in the system
> that overwrote this object's memory to make it invalid. If that's the
> case, the invalid object is not the problem at all, and isolating it
> won't fix anything.
That's why this sort of thing works better in safe languages.
Ada has a much stronger notion of exceptions than C++ does, but
Ada does far more to contain the effects of defects. That's
why it's useable for flight control systems.
>>Maybe the object has to stay around,
>>broken, until the program exits.
>
>
> That's one possibility. Another is to stop the program before the
> damage can spread.
Sometimes, stopping the program isn't an option. But you don't
usually write such programs in C++.
>
>
>> The relationship between exception handlers and invariants
>>is tough.
>
>
> So tough, in fact, that perhaps we should pick a different mechanism
> for dealing with broken invariants than exceptions.
I just can't see design by contract going into C++.
I like design by contract. But it implies a level of rigor
foreign to the C++ community.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Sat, 11 Sep 2004 16:59:10 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message
news:belvis-AC4E21.23302709092004@news.la.sbcglobal.net...
| In article <4140e863$0$211$14726298@news.sunsite.dk>,
| nesotto@cs.auc.dk ("Thorsten Ottosen") wrote:
| > "Bob Bell" <belvis@pacbell.net> wrote in message
| > | Bar::G() breaks Bar's invariants and then calls Foo::F(). Foo::F()
| > | erroneously breaks Foo's invariants and fails to restore them. At
| > | Foo::F() exit time, the invariant checker runs, detects the violation
| > | and throws InvariantViolation. When the exception passes up through
| > | Bar::G(), the invariant checker runs again,
| >
| > actually no...we agreed that invariants are not checked if the function
exits
| > due to an exception.
|
| Then you have a situation where one or more objects has not had its
| invariants checked. That sounds like a deal-breaker to me.
|
| I can't tell if you're misunderstanding the problem I'm describing, or
| if you do understand, but just don't think it's a big deal.
I think it must have been a bit of both. I don't think its possible to
detect more than one of the two in your example.
But that should be ok for most purposes if we choose to abort the
program as soon as a violation is detected (as you suggest).
| I thought the whole point was to have the system check invariants at all
| the right times and places.
|
| > .. what the system can do is to check that an invariant have been broken.
|
| No, it can't in this case. That's the point.
|
| What good is an automatic system for checking invariants if it doesn't
| automatically check invariants?
yeah. FWIW, I changed the semantics in the soon-to-be-released
paper to follow your suggestion. The means that
1. invariant violations never let exceptions escape
2. invariant violations leads to a call to terminate()
3 invariants are checked here:
1. end of constructor
2. around public functions
3. start of destructor
4. when a function exits via an exception
| > | If not, then we have two ways of dealing with broken
| > | invariants: one by throwing, and another via std::broken_invariant().
| > | Why not just use std::broken_invariant() all the time? Wouldn't that
| > | be simpler?
| >
| > yeah, maybe...it is definitely worth considering. I guess it would amount
to
| > saying
| > that the invariant is always evaluated in a try-catch block as above.
|
| Or perhaps that checking invariants doesn't interact with exceptions at
| all.
How would you forbid that a function called from within an invariant did not
throw?
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: hyrosen@mail.com (Hyman Rosen)
Date: Sun, 12 Sep 2004 16:57:30 GMT Raw View
John Nagle wrote:
> Ada has a much stronger notion of exceptions than C++ does, but
> Ada does far more to contain the effects of defects.
What do you mean by this?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Sun, 12 Sep 2004 17:58:55 CST Raw View
Hi,
belvis@pacbell.net (Bob Bell) writes:
>nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message news:<414020e8$0$214$14726298@news.sunsite.dk>...
>OK. How do you get around the problem you snipped? Where we either
>have two exceptions, or one object with broken invariants without a
>corresponding InvariantFailure exception?
With my proposal (and some of the good ideas presented here), a possible case
could look like this:
class A {
public:
void foo(void);
private:
int a;
(a == 5);
};
void A::foo(void) {
a = 4;
}
class B {
public:
void bar(void);
private:
A a;
int b;
bool valid;
(valid <= (b == 5));
(valid);
};
void B::bar(void) {
valid = false; // Okay, (valid) is special.
b = 4; // Okay, (valid <= (b == 5)) is still true.
a.foo(); // Throws
b = 5; // Invariant restored
valid = true; // Checking reenabled
}
Indeed, this is a problem, as the part after a.foo() is not reached and there
is no way to tell that from the headers (a compiler may refuse to compile
A::foo because it cannot finish, however a reliable detection would be a
solution to the halting problem. My proposal also includes a way to "fix"i
that, although it is ugly and heavily relies on the optimizer (and also bends
overload resolution really hard).
The "boolean" invariants that enforce the valid flag to be reasserted at the
end of the bar function (note that there is no problem is this is not forced,
however functions would need to test for validity at the beginning) are
relatively easy to verify at compile time: Either they are "true" at a given
point, or they aren't. At the beginning of the function, the invariant forces
them to be true, also any unconditional "valid = true;" will. Whereever that
is, the type of this is the object's static type, otherwise the object's
static type qualified with "invalid". Within an area where the type is marked
as invalid, every method called will have to be able to handle invalid
objects, i.e. there needs to be a method signature that allows the type to
be "invalid"-qualified. If no such method exists, it is an error. As the
"invalid" modifier as I have proposed it will also be applied to sub-objects
and members, the type of B::a in the example above will be "invalid A", and
since the function A::foo(void) is not marked as allowing an "invalid" this
pointer, the function can not be called at this point.
For efficiency, it may be feasible to call the "regular" version of a
function if the object is actually valid at the point of the call, i.e. the
compiler may want to generate two different calls, depending on the current
value of the "valid" member (of which the optimizer may remove one if it
determines that it is not reachable), however the only required semantics is
that the compiler is able to determine statically whether an object's
validity is guaranteed or uncertain.
The only case not covered by this is A calls B calls A, where someone gets
a "valid" reference to the other object via a different channel than its
member variables. I have no real suggestion on how to address this, but I'd
consider it UB.
>What is std::broken_invariant()? I'm guessing it does not throw an
>exception. If not, then we have two ways of dealing with broken
>invariants: one by throwing, and another via std::broken_invariant().
>Why not just use std::broken_invariant() all the time? Wouldn't that
>be simpler?
We could also terminate() here, it makes no real difference. I prefer the
"exception" variant because it allows us to decide at runtime whether an
operation that failed because of a programming error was "important" enough
to warrant stopping the program.
>> | Another problem with throwing an exception to report an invariant
>> | violation is: what would you do with such an exception?
>> There is probably cases where destruction is ok.
>Which ones are they? When you catch an InvariantFailure, how will you
>know if this is one of those OK cases?
If the class has a "boolean" invariant, it needs to have a destructor that
can operate on an "invalid" this pointer.
> try {
> a.F(b, x);
> y.G();
> z.H(a, b.F(c, y));
> }
> catch (const InvariantFailure&) {
> // which object broke?
The one in the try block. If you make your try blocks too large, you will not
know who threw the exception. I also believe that invariant failures will
only seldom be caught.
>Invariant
>failures should be signalled by a different mechanism, like assert.
If throwing an exception is not the desired behaviour, I would suggest
implementation-defined behaviour. Under Unix, we'd probably kill ourselves
with SIGABRT, i.e. dump core.
>All of the issues I raised come down to this point: checking
>invariants is about finding bugs.
And allow speedups by moving validity tests from readers to writers.
>Using exceptions to find bugs
>interacts very badly with the other uses of exceptions.
Agreed, but I believe the benefit of being able to deal with the error at
runtime and possibly also get a bit of sane cleanup is worth it.
>So you end up
>having to choose between multiple exceptions or silently allowing
>invariants to fail. Finally, recovery, which is normally possible with
>exceptions, is impossible with invariant failure.
Like I said, there may be cases where recovery is possible, by destructing
a bunch of objects and continuing without them. I'd leave that decision to
the programmer.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Mon, 13 Sep 2004 08:28:02 GMT Raw View
In article <frv0d.14060$QJ3.12504@newssvr21.news.prodigy.com>,
nagle@animats.com (John Nagle) wrote:
> Bob Bell wrote:
>
> > nagle@animats.com (John Nagle) wrote in message
> > news:<hac0d.18385$kf5.8729@newssvr29.news.prodigy.com>...
> >> The relationship between exception handlers and invariants
> >>is tough.
> >
> > So tough, in fact, that perhaps we should pick a different mechanism
> > for dealing with broken invariants than exceptions.
>
> I just can't see design by contract going into C++.
> I like design by contract. But it implies a level of rigor
> foreign to the C++ community.
We don't have to throw the baby out with the bathwater. Just because
exceptions are a poor way to signal invariant failures in C++ doesn't
mean that we can't include _some_ kind of support for DBC in C++.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Mon, 13 Sep 2004 09:53:46 GMT Raw View
In article <ci2f05$52rg0$1@sunsystem5.informatik.tu-muenchen.de>,
Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
> Hi,
>
> belvis@pacbell.net (Bob Bell) writes:
> >nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message
> >news:<414020e8$0$214$14726298@news.sunsite.dk>...
>
> >OK. How do you get around the problem you snipped? Where we either
> >have two exceptions, or one object with broken invariants without a
> >corresponding InvariantFailure exception?
>
> With my proposal (and some of the good ideas presented here), a possible case
> could look like this:
>
> class A {
> public:
> void foo(void);
> private:
> int a;
> (a == 5);
> };
>
> void A::foo(void) {
> a = 4;
> }
>
> class B {
> public:
> void bar(void);
> private:
> A a;
> int b;
> bool valid;
> (valid <= (b == 5));
> (valid);
> };
>
> void B::bar(void) {
> valid = false; // Okay, (valid) is special.
> b = 4; // Okay, (valid <= (b == 5)) is still true.
> a.foo(); // Throws
> b = 5; // Invariant restored
> valid = true; // Checking reenabled
> }
>
> Indeed, this is a problem, as the part after a.foo() is not reached and there
> is no way to tell that from the headers (a compiler may refuse to compile
> A::foo because it cannot finish, however a reliable detection would be a
> solution to the halting problem. My proposal also includes a way to "fix"i
> that, although it is ugly and heavily relies on the optimizer (and also bends
> overload resolution really hard).
>
> The "boolean" invariants that enforce the valid flag to be reasserted at the
> end of the bar function (note that there is no problem is this is not forced,
> however functions would need to test for validity at the beginning) are
> relatively easy to verify at compile time: Either they are "true" at a given
> point, or they aren't. At the beginning of the function, the invariant forces
> them to be true, also any unconditional "valid = true;" will. Whereever that
> is, the type of this is the object's static type, otherwise the object's
> static type qualified with "invalid". Within an area where the type is marked
> as invalid, every method called will have to be able to handle invalid
> objects, i.e. there needs to be a method signature that allows the type to
> be "invalid"-qualified. If no such method exists, it is an error. As the
> "invalid" modifier as I have proposed it will also be applied to sub-objects
> and members, the type of B::a in the example above will be "invalid A", and
> since the function A::foo(void) is not marked as allowing an "invalid" this
> pointer, the function can not be called at this point.
>
> For efficiency, it may be feasible to call the "regular" version of a
> function if the object is actually valid at the point of the call, i.e. the
> compiler may want to generate two different calls, depending on the current
> value of the "valid" member (of which the optimizer may remove one if it
> determines that it is not reachable), however the only required semantics is
> that the compiler is able to determine statically whether an object's
> validity is guaranteed or uncertain.
>
> The only case not covered by this is A calls B calls A, where someone gets
> a "valid" reference to the other object via a different channel than its
> member variables. I have no real suggestion on how to address this, but I'd
> consider it UB.
I have to confess that I find this all excessively complicated, and I'm
not sure if I'm really following it, so forgive me if I'm
misunderstanding. The best I can make of it is that this is a
complicated way of saying that sometimes we let a programmer say that
it's OK for some invariants to fail, by saying that any invariants tied
to the boolean invariants (like "valid" above) don't have to hold if the
boolean invariant is false.
Not only does this seem really complex (sometimes the invariant holds,
sometimes it doesn't), it doesn't solve the problem I posed:
class A {
public:
void F();
};
void A::F()
{
// break invariants
}
class B {
public:
void G();
private:
A mA;
};
void B::G()
{
// break invariants
mA.F();
// restore invariants
}
As near as I can tell, this code is still possible with the proposal
you're making. So we still have a situation where we either throw two
exceptions, which is clearly unacceptable, or we silently allow one of
the objects' invariants to fail.
It isn't enough to provide a mechanism that allows a programmer to avoid
the situation. To solve the problem, it must be impossible for a
programmer to write code that exhibits the problem at all.
> >What is std::broken_invariant()? I'm guessing it does not throw an
> >exception. If not, then we have two ways of dealing with broken
> >invariants: one by throwing, and another via std::broken_invariant().
> >Why not just use std::broken_invariant() all the time? Wouldn't that
> >be simpler?
>
> We could also terminate() here, it makes no real difference. I prefer the
> "exception" variant because it allows us to decide at runtime whether an
> operation that failed because of a programming error was "important" enough
> to warrant stopping the program.
No one has yet demonstrated how a program can decide at runtime whether
an invariant failure is important enough to stop the program, or that
it's OK to continue running.
> >> | Another problem with throwing an exception to report an invariant
> >> | violation is: what would you do with such an exception?
> >> There is probably cases where destruction is ok.
>
> >Which ones are they? When you catch an InvariantFailure, how will you
> >know if this is one of those OK cases?
>
> If the class has a "boolean" invariant, it needs to have a destructor that
> can operate on an "invalid" this pointer.
How will the catching code know that the object has a boolean invariant,
and that it's OK to destroy the object? Isn't an object's invariants and
their interactions with each other an implementation detail?
I'd really like to see a comprehensive description of how an invariant
failure can be dealt with at runtime.
> > try {
> > a.F(b, x);
> > y.G();
> > z.H(a, b.F(c, y));
> > }
> > catch (const InvariantFailure&) {
> > // which object broke?
>
> The one in the try block.
None of these objects are defined in the try block. If they were, they
would already be destroyed before the catch handler was entered --
assuming they were still destructible and the program didn't crash.
> If you make your try blocks too large, you will not
> know who threw the exception.
Not sure if I'm understanding this; are you advocating a new try block
for every variable in a function?
> I also believe that invariant failures will
> only seldom be caught.
Do you mean "most programs will have a few functions which catch
invariant failures" or "few programs will have any functions which catch
invariant failures"? Or something else?
> >Invariant
> >failures should be signalled by a different mechanism, like assert.
>
> If throwing an exception is not the desired behaviour, I would suggest
> implementation-defined behaviour. Under Unix, we'd probably kill ourselves
> with SIGABRT, i.e. dump core.
AFAIK, under Unix that's exactly what assert does.
I'd like invariant failure behavior to be defined in a similar way to
assert, where the implementation is allowed to write debug messages,
dump core, etc., but is not allowed to let the program continue.
> >All of the issues I raised come down to this point: checking
> >invariants is about finding bugs.
>
> And allow speedups by moving validity tests from readers to writers.
I don't get this; invariant checking cannot allow speedups compared to
no invariant checking.
> >Using exceptions to find bugs
> >interacts very badly with the other uses of exceptions.
>
> Agreed, but I believe the benefit of being able to deal with the error at
> runtime and possibly also get a bit of sane cleanup is worth it.
It's because of the poor interaction between bug detection and
exceptions that the cleanup is not sane.
> >So you end up
> >having to choose between multiple exceptions or silently allowing
> >invariants to fail. Finally, recovery, which is normally possible with
> >exceptions, is impossible with invariant failure.
>
> Like I said, there may be cases where recovery is possible, by destructing
> a bunch of objects and continuing without them. I'd leave that decision to
> the programmer.
No one has yet demonstrated how this can be done, despite my repeatedly
asking. How exactly is recovery done? Please give a concrete example,
because IMHO this is a pretty big hole in the whole proposal; if
techniques for recovery from invariant failure can't be described, or if
they only apply in a very narrow domain, then there doesn't seem to be
any reason to let a program continue when invariants fail.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Thu, 9 Sep 2004 00:21:20 GMT Raw View
Simon Richter wrote:
> Hi,
>
> "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
>
>
>>"Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message news:chdhvo$4t25u$1@sunsystem5.informatik.tu-muenchen.de...
>>| "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
> It has been pointed out that the lifetime of an object starts at the end of
> the constructor body and that it would make more sense to let the constructor
> body establish invariants before they are first checked.
Right.
Should constructors be barred from calling public methods of
an object for that reason?
[I'd like to see design by contract in C++, but I don't see
it happening. Too many holes have to be plugged, and each
hole has its lobby. This is just one of many.
If the language were tight enough, you could prove
invariants, not just test them at run time. A high percentage
of assertions and invariants can be be machine-proven without
human intervention. But that requires a more rigorous language than
C++ will ever be.]
John Nagle
Team Overbot
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 9 Sep 2004 08:01:19 GMT Raw View
"Thorsten Ottosen" <nesotto@cs.auc.dk> wrote in message news:<413d5a94$0$205$14726298@news.sunsite.dk>...
> "Alf P. Steinbach" <alfps@start.no> wrote in message news:413d2afa.341179000@news.individual.net...
> | Ooops, now I see there's a problem with letting invariant checking throw
> | an exception... :-)
>
> what problem?
Not sure if this is what Alf had in mind, but here's _a_ problem:
class Foo {
public:
void A();
void B();
};
Let's say that the compiler will insert code automatically to check
invariants and throw an InvariantViolation exception if they are
violated. Here are A and B:
void Foo::A()
{
B();
}
void Foo::B()
{
// do something to mess up the invariants
}
When B() exits, the invariant checker runs, detects the invariants are
violated, and throws an InvariantViolation. As the exception passes up
through A(), the invariant checker runs because A is exiting. It
detects that the invariants are violated, and throws another
InvariantViolation. Two exceptions == std::terminate, goodnight, and
God bless.
In case you're thinking that the invariant checker can be smart enough
to detect that it doesn't have to throw a second InvariantViolation,
here's another example:
class Foo {
public:
void F();
};
void Foo::F()
{
// break invariants
}
class Bar {
public:
void G();
private:
Foo mF;
};
void Bar::G()
{
// break invariants
mF.F();
// restore invariants
}
Bar::G() breaks Bar's invariants and then calls Foo::F(). Foo::F()
erroneously breaks Foo's invariants and fails to restore them. At
Foo::F() exit time, the invariant checker runs, detects the violation
and throws InvariantViolation. When the exception passes up through
Bar::G(), the invariant checker runs again, detects the broken
invariants in Bar() and throws another InvariantViolation. Back to
std::terminate.
Now we're stuck with two unappealing choices. Either the invariant
checker must be able to throw two simultaneous exceptions, _or_ we
throw only one and ignore the other. If we ignore one, then one of
these objects will continue with its invariants violated, and we've
received no notification of that fact. Thus the invariant checking
system fails.
Here's another variation on this theme:
class Foo {
public:
class X { };
void F();
};
void Foo::F()
{
// break invariants
if (someCondition)
throw Foo::X(); // whoops! forgot about the invariants.
// restore invariants
}
The author of Foo::F() forgot to restore the invariants before
throwing the Foo::X(). As the exception leaves Foo::F(), the invariant
checker kicks in and, you guessed it, we go to std::terminate.
Another problem with throwing an exception to report an invariant
violation is: what would you do with such an exception?
void F()
{
Foo foo;
try {
foo.G();
}
catch (InvariantViolation&) {
// what now?
}
}
What can we do if we catch InvariantViolation? Can we fix foo? Can we
even destroy it? How about this:
void F()
{
try {
Foo foo;
foo.G();
}
catch (InvariantViolation&) {
// what now?
}
}
Here, the object that threw the invariant violation doesn't even
exist. Did we even get to the catch handler, or are we going to crash
because attempting to destroy foo dereferenced a null pointer or
something?
There's only one reason to throw: something happened in a function
that the function can't (or doesn't want to) deal with, but maybe some
function higher up the call stack can deal with it. And the reason a
caller might be able to handle the problem is that the caller will
have more context for the failure.
When an invariant fails, it's an entirely local problem. There is no
higher-level context available for a caller, so a caller is no more
likely to know how to deal with the problem. In fact, because of
encapsulation, callers that are not members of the broken class will
have _less_ context in which to deal with the problem. So unlike the
normal situation with exceptions, with invariant failures the farther
up the call stack you go, the less likely the problem can be handled.
So why throw?
Throwing exceptions when invariants are violated simply allows the
system to continue executing with broken objects (which is what
happens in the two examples above where we catch InvariantViolation)
without providing any benefit in terms of making the program safer,
more robust or easier to debug.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 9 Sep 2004 08:01:43 GMT Raw View
kuyper@wizard.net (James Kuyper) wrote in message news:<8b42afac.0409072143.2481c06d@posting.google.com>...
> Simon Richter <richtesi@informatik.tu-muenchen.de> wrote in message news:<chis7h$4uk8d$1@sunsystem5.informatik.tu-muenchen.de>...
> > Hi,
> >
> > "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
> ..
> > >| Allowing a function to violate the invariant until the end of the
> > >| function would leave us with an obviously incorrect object,
>
> > >this is not obvious.
> >
> > If the function were allowed to leave the object in an invalid state, we would
>
> Who suggested leaving it in an invalid state? The case under
> discussion was when the invariant may be violated "until the end", at
> which point it can no longer be violated. In other word, the invariant
> must be restore; it might not be restored until the very last
> statement in the function executes, but it must be restored. This fits
> the quite common case where the first statement in a member function
> breaks a class invariant, and the class invariants are not fully
> restored until after every statement in the function has been
> executed.
This makes me think of another reason why exceptions should not be
used to report invariant violations. Suppose a member function of a
class wants to throw an exception of its own, and does so
(erroneously) without first re-establishing the invariants. As we exit
the function (via throw), the invariant checker executes, detects the
invariant violation, and throws an exception itself. We now have two
simultaneous exceptions, and a trip straight to std::terminate().
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Thu, 9 Sep 2004 09:53:03 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message
news:c87c1cfb.0409081404.52e63d28@posting.google.com...
| "Thorsten Ottosen" <nesotto@cs.auc.dk> wrote in message
news:<413d5a94$0$205$14726298@news.sunsite.dk>...
| > "Alf P. Steinbach" <alfps@start.no> wrote in message
news:413d2afa.341179000@news.individual.net...
| > | Ooops, now I see there's a problem with letting invariant checking throw
| > | an exception... :-)
| >
| > what problem?
|
| Not sure if this is what Alf had in mind, but here's _a_ problem:
|
| class Foo {
| public:
| void A();
| void B();
| };
|
| Let's say that the compiler will insert code automatically to check
| invariants and throw an InvariantViolation exception if they are
| violated. Here are A and B:
|
| void Foo::A()
| {
| B();
| }
|
| void Foo::B()
| {
| // do something to mess up the invariants
| }
|
| When B() exits, the invariant checker runs,
no, invariants would be disabled until A() returns.
| detects the invariants are
| violated, and throws an InvariantViolation. As the exception passes up
| through A(), the invariant checker runs because A is exiting.
we have to agree on when the invariant is cheked. let's say the invariant is
not checked if A() exists by exception and lets say it is checked in the
destructor like this
Foo::~Foo()
{
try
{ invariant(); } catch( ... ) { std::broken_invariant(); }
}
| It
| detects that the invariants are violated, and throws another
| InvariantViolation. Two exceptions == std::terminate, goodnight, and
| God bless.
I think this won't happen given the rules above.
| Another problem with throwing an exception to report an invariant
| violation is: what would you do with such an exception?
|
| void F()
| {
| Foo foo;
|
| try {
| foo.G();
| }
| catch (InvariantViolation&) {
| // what now?
| }
| }
|
| What can we do if we catch InvariantViolation? Can we fix foo? Can we
| even destroy it?
There is probably cases where destruction is ok.
| When an invariant fails, it's an entirely local problem. There is no
| higher-level context available for a caller, so a caller is no more
| likely to know how to deal with the problem. In fact, because of
| encapsulation, callers that are not members of the broken class will
| have _less_ context in which to deal with the problem. So unlike the
| normal situation with exceptions, with invariant failures the farther
| up the call stack you go, the less likely the problem can be handled.
| So why throw?
To continue the program instead of crashing.
I agree, that it is a bit hard to come up with good examples; but the question
is if it
is worth to forbid an invariant from throwing; for example, even the
functions called in might throw.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Thu, 9 Sep 2004 15:26:35 GMT Raw View
"John Nagle" <nagle@animats.com> wrote in message
news:4sG%c.17515$rL4.5411@newssvr29.news.prodigy.com...
| Simon Richter wrote:
| > Hi,
| >
| > "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
| >
| >
| >>"Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message
news:chdhvo$4t25u$1@sunsystem5.informatik.tu-muenchen.de...
| >>| "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
|
| > It has been pointed out that the lifetime of an object starts at the end
of
| > the constructor body and that it would make more sense to let the
constructor
| > body establish invariants before they are first checked.
|
| Right.
|
| Should constructors be barred from calling public methods of
| an object for that reason?
no, the invariant should be disabled for the object in question, just like
with any public function.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: richtesi@informatik.tu-muenchen.de (Simon Richter)
Date: Thu, 9 Sep 2004 17:58:48 GMT Raw View
Hi,
alfps@start.no (Alf P. Steinbach) writes:
>* Simon Richter:
[Which functions are affected]
>> So functions that do not assume invariants
>> on entry should be seldom, and functions that do not guarantee the invariants
>> on exit should be really rare.
>"Should be", "should be". In my experience they _are_ rare, but the problem is
>there anyway: how to designate them. Saying that the set of variant-enforcing
>functions consists only of the public functions, or only of the public and
>protected functions, is IMHO not good enough by half.
New keyword.
>No, that is the reason why an exception is the wrong response to a
>variant violation.
The only other option is to terminate() right away which doesn't give the
program a chance to discard the offending objects and try to continue without
them (plugins).
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: richtesi@informatik.tu-muenchen.de (Simon Richter)
Date: Thu, 9 Sep 2004 17:59:45 GMT Raw View
Hi,
"Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
>"John Nagle" <nagle@animats.com>:
>| Thorsten Ottosen:
>| > "John Nagle" <nagle@animats.com>:
>if a flag is set saying we entered a public function.
This is why my proposal has the ability to define flags (multiple even,
so you can group invariants) that say that an invariant has been
temporarily relaxed. While one of these is set, calls to member functions
that do not have a special modifier attached to them saying that the
function can deal with relaxed invariants are forbidden.
That is, you get as many flags as you need, including none if you don't need
them.
>I don't see what the problem is here. The swap() function has as
>it responsibility to preserve the invariant of the two objects it swappes.
>That can be cheked in manually in various ways, eg,
>1. void swap( X& r ) postcondition( std::old( *this ) == r; std::old( r ) ==
>*this; }
That's not an invariant. That's a postcondition. Proof of correctness is
something different and should be handled by a tool that is designed for
that.
>yeah, I know. but since we don't know how threads will impact on the standard,
>how can
>we deal with them?
Thread handling affects us only partially. If an object is used in multiple
threads without proper locking, invariants may be violated by another thread,
or other thread synchronisation issues occur. This is outside of the scope of
invariants, as it will be broken code without them.
(That said, you can use invariants to ensure objects are properly unlocked --
but most, if not all, cases where you need that you really want to create a
temporary whose destructor unlocks rather than add stuff to your class)
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: richtesi@informatik.tu-muenchen.de (Simon Richter)
Date: Thu, 9 Sep 2004 18:47:01 GMT Raw View
Hi,
nagle@animats.com (John Nagle) writes:
>Simon Richter wrote:
> Should constructors be barred from calling public methods of
>an object for that reason?
They should be barred from calling any member methods except those that have
a modifier attached to them, which allows invariants to be violated
throughout that function.
> If the language were tight enough, you could prove
>invariants, not just test them at run time. A high percentage
>of assertions and invariants can be be machine-proven without
>human intervention. But that requires a more rigorous language than
>C++ will ever be.]
We can, however, warn for unreachable code that follows an assignment that
always violates an invariant.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: pasa@lib.hu ("Balog Pal")
Date: Thu, 9 Sep 2004 19:39:35 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message
news:c87c1cfb.0409081404.52e63d28@posting.google.com...
[mass cut]
> There's only one reason to throw: something happened in a function
> that the function can't (or doesn't want to) deal with, but maybe some
> function higher up the call stack can deal with it. And the reason a
> caller might be able to handle the problem is that the caller will
> have more context for the failure.
>
> When an invariant fails, it's an entirely local problem. There is no
> higher-level context available for a caller, so a caller is no more
> likely to know how to deal with the problem. In fact, because of
> encapsulation, callers that are not members of the broken class will
> have _less_ context in which to deal with the problem. ...
Excellent summary.
I think there's no point to go on with discussion until the OP (or other
proponents of the concept) address these issues carefully.
In general I observe wishlists tend to be stuck on technical level --
propose some low level twiddling, language do this and that -- instead of
startig with a rationale, or presenting the outcome of some change.
It can be fun to fine tune the design of the gun, but shall we not check
whether we have any gunpowder first?
Paul
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 9 Sep 2004 23:03:54 GMT Raw View
nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message news:<414020e8$0$214$14726298@news.sunsite.dk>...
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:c87c1cfb.0409081404.52e63d28@posting.google.com...
> | "Thorsten Ottosen" <nesotto@cs.auc.dk> wrote in message
> news:<413d5a94$0$205$14726298@news.sunsite.dk>...
> | > "Alf P. Steinbach" <alfps@start.no> wrote in message
> news:413d2afa.341179000@news.individual.net...
> | > | Ooops, now I see there's a problem with letting invariant checking throw
> | > | an exception... :-)
> | >
> | > what problem?
> |
> | Not sure if this is what Alf had in mind, but here's _a_ problem:
> |
> | class Foo {
> | public:
> | void A();
> | void B();
> | };
> |
> | Let's say that the compiler will insert code automatically to check
> | invariants and throw an InvariantViolation exception if they are
> | violated. Here are A and B:
> |
> | void Foo::A()
> | {
> | B();
> | }
> |
> | void Foo::B()
> | {
> | // do something to mess up the invariants
> | }
> |
> | When B() exits, the invariant checker runs,
>
> no, invariants would be disabled until A() returns.
OK. How do you get around the problem you snipped? Where we either
have two exceptions, or one object with broken invariants without a
corresponding InvariantFailure exception?
> | detects the invariants are
> | violated, and throws an InvariantViolation. As the exception passes up
> | through A(), the invariant checker runs because A is exiting.
>
> we have to agree on when the invariant is cheked. let's say the invariant is
> not checked if A() exists by exception
This just again allows objects to have broken invariants without a
corresponding InvariantFailure exception:
void Foo::F()
{
// break invariants
// the checker doesn't run because we are
// exiting via an exception
throw "hello, world.";
// restore invariants
}
If I can break invariants and they aren't checked, it seems to me the
system isn't guaranteeing my invariants.
> and lets say it is checked in the
> destructor like this
>
> Foo::~Foo()
> {
> try
> { invariant(); } catch( ... ) { std::broken_invariant(); }
> }
What is std::broken_invariant()? I'm guessing it does not throw an
exception. If not, then we have two ways of dealing with broken
invariants: one by throwing, and another via std::broken_invariant().
Why not just use std::broken_invariant() all the time? Wouldn't that
be simpler?
> | Another problem with throwing an exception to report an invariant
> | violation is: what would you do with such an exception?
> |
> | void F()
> | {
> | Foo foo;
> |
> | try {
> | foo.G();
> | }
> | catch (InvariantViolation&) {
> | // what now?
> | }
> | }
> |
> | What can we do if we catch InvariantViolation? Can we fix foo? Can we
> | even destroy it?
>
> There is probably cases where destruction is ok.
Which ones are they? When you catch an InvariantFailure, how will you
know if this is one of those OK cases?
> | When an invariant fails, it's an entirely local problem. There is no
> | higher-level context available for a caller, so a caller is no more
> | likely to know how to deal with the problem. In fact, because of
> | encapsulation, callers that are not members of the broken class will
> | have _less_ context in which to deal with the problem. So unlike the
> | normal situation with exceptions, with invariant failures the farther
> | up the call stack you go, the less likely the problem can be handled.
> | So why throw?
>
> To continue the program instead of crashing.
How will you keep the program from crashing? How do you know the
program will do anything you want it to do? Why is that better than
finding and fixing the bug?
Again, the question that hasn't been answered is: what do you do with
an InvariantFailed exception?
void F()
{
Foo a, b, c;
Bar x, y, z;
try {
a.F(b, x);
y.G();
z.H(a, b.F(c, y));
}
catch (const InvariantFailure&) {
// which object broke?
// how extensive is the damage?
// how do you fix it?
// can any of these objects be destroyed?
// if an object can't be destroyed, how do you prevent it?
// is there anything meaningful that can be done here?
}
}
> I agree, that it is a bit hard to come up with good examples; but the question
> is if it
> is worth to forbid an invariant from throwing; for example, even the
> functions called in might throw.
I'm having trouble parsing that last bit. As for the former, yes, it
is quite worth it to forbid an invariant failure from throwing;
throwing gains you nothing, and has significant problems. Invariant
failures should be signalled by a different mechanism, like assert.
All of the issues I raised come down to this point: checking
invariants is about finding bugs. Using exceptions to find bugs
interacts very badly with the other uses of exceptions. So you end up
having to choose between multiple exceptions or silently allowing
invariants to fail. Finally, recovery, which is normally possible with
exceptions, is impossible with invariant failure.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 9 Sep 2004 23:03:59 GMT Raw View
richtesi@informatik.tu-muenchen.de (Simon Richter) wrote in message news:<chppuk$50fha$2@sunsystem5.informatik.tu-muenchen.de>...
> Hi,
>
> alfps@start.no (Alf P. Steinbach) writes:
> >No, that is the reason why an exception is the wrong response to a
> >variant violation.
>
> The only other option is to terminate() right away which doesn't give the
> program a chance to discard the offending objects and try to continue without
> them (plugins).
It is far from clear that "giving the program a chance to discard
offending objects is a good idea." What is an offending object? How
much damage has it caused? How do we clean up? An invariant failure in
object A could have been caused by a bug in object B that caused it to
overwrite object A; what good would it do to discard A? Further, using
exceptions for identifying invariant failure is guaranteed to let some
invariant failures go unnoticed.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nesotto@cs.auc.dk ("Thorsten Ottosen")
Date: Thu, 9 Sep 2004 23:50:31 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message
news:c87c1cfb.0409091243.3cdd134a@posting.google.com...
| nesotto@cs.auc.dk ("Thorsten Ottosen") wrote in message
news:<414020e8$0$214$14726298@news.sunsite.dk>...
| OK. How do you get around the problem you snipped? Where we either
| have two exceptions, or one object with broken invariants without a
| corresponding InvariantFailure exception?
[example:]
| here's another example:
|
| class Foo {
| public:
| void F();
| };
|
| void Foo::F()
| {
| // break invariants
| }
|
| class Bar {
| public:
| void G();
| private:
| Foo mF;
| };
|
| void Bar::G()
| {
| // break invariants
| mF.F();
| // restore invariants
| }
|
| Bar::G() breaks Bar's invariants and then calls Foo::F(). Foo::F()
| erroneously breaks Foo's invariants and fails to restore them. At
| Foo::F() exit time, the invariant checker runs, detects the violation
| and throws InvariantViolation. When the exception passes up through
| Bar::G(), the invariant checker runs again,
actually no...we agreed that invariants are not checked if the function exits
due to an exception.
| detects the broken
| invariants in Bar() and throws another InvariantViolation. Back to
| std::terminate.
the invariant will be checked by Foo's destructor when mF is deleted due to
stack-unwinding.
Here it will call std::broken_invariant(). I see this is only after the
invariant is checked the second time.
| > | detects the invariants are
| > | violated, and throws an InvariantViolation. As the exception passes up
| > | through A(), the invariant checker runs because A is exiting.
| >
| > we have to agree on when the invariant is cheked. let's say the invariant
is
| > not checked if A() exists by exception
|
| This just again allows objects to have broken invariants without a
| corresponding InvariantFailure exception:
|
| void Foo::F()
| {
| // break invariants
|
| // the checker doesn't run because we are
| // exiting via an exception
|
| throw "hello, world.";
|
| // restore invariants
| }
|
| If I can break invariants and they aren't checked, it seems to me the
| system isn't guaranteeing my invariants.
correct...it makes no sense to say the system should guarantee your
invariants...its your job
to provide the basic guarantee of exception-safety
.. what the system can do is to check that an invariant have been broken.
The destructor
will call the invariant if the object is destroyed during unwinding...if you
catch the exception, you
might manually check the invariant is not broken.
| > and lets say it is checked in the
| > destructor like this
| >
| > Foo::~Foo()
| > {
| > try
| > { invariant(); } catch( ... ) { std::broken_invariant(); }
| > }
|
| What is std::broken_invariant()? I'm guessing it does not throw an
| exception.
yes, it doesn't.
| If not, then we have two ways of dealing with broken
| invariants: one by throwing, and another via std::broken_invariant().
| Why not just use std::broken_invariant() all the time? Wouldn't that
| be simpler?
yeah, maybe...it is definitely worth considering. I guess it would amount to
saying
that the invariant is always evaluated in a try-catch block as above.
Hm...yeah, this is simpler...I think I will adopt this approach.
If the implementation wants to continue, it can still replace
std::broken_invariant() with something.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Fri, 10 Sep 2004 17:10:06 GMT Raw View
Balog Pal wrote:
> "Bob Bell" <belvis@pacbell.net> wrote in message
> news:c87c1cfb.0409081404.52e63d28@posting.google.com...
>
> [mass cut]
>
>>There's only one reason to throw: something happened in a function
>>that the function can't (or doesn't want to) deal with, but maybe some
>>function higher up the call stack can deal with it. And the reason a
>>caller might be able to handle the problem is that the caller will
>>have more context for the failure.
>>
>>When an invariant fails, it's an entirely local problem. There is no
>>higher-level context available for a caller, so a caller is no more
>>likely to know how to deal with the problem. In fact, because of
>>encapsulation, callers that are not members of the broken class will
>>have _less_ context in which to deal with the problem. ...
If you're going to try to recover from invariant failures,
you need some notion of an object being in an "invalid" state.
An invariant failure makes an object invalid. It's not clear
what you do then. Arguably, it's then invalid to call any
public method of the object. It's not even clear that you
can call the destructor. Maybe the object has to stay around,
broken, until the program exits.
The relationship between exception handlers and invariants
is tough. So is the relationship between exception handlers
and locking. And the relationship between exception handlers,
locking, and cancellation. All those issues are tightly
interwoven.
This is a solveable problem in theory. But given the
constraint that a solution must be backwards-compatible with
existing C++ code, any sound approach will probably be
very ugly.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Fri, 3 Sep 2004 00:48:26 GMT Raw View
Hi,
some time ago it has been asked in this group whether anyone had large-scale
wishes. Here are mine (in a short form, for some of these I've started writing
longer texts about them). I'd like to have a bit of feedback whether anything
like this would be worth pursuing or whether that makes no sense at all.
- Explicit invariants
For example, a string class that allocates more memory than it actually uses
could do the following:
class mystring {
/* ... */
private:
size_t allocated;
size_t length;
(length < allocated);
};
This would mean that any attempt to store a value to allocated or length that
would violate the invariant (i.e. make the expression within the parentheses
evaluate to false) would throw an exception before the offending value has been
written (that is, the assignment operator would throw).
Invariants would be part of the API if defined in public or protected sections,
they may only reference members of the same visibility.
Optionally, there could be a special treatment for the case where the expression
is a boolean variable only: It is allowed to store "false" into this variable,
thus invalidating the object, but any code path exiting from the current
method or calling a method on this object will lead to an exception being
thrown (this still needs a lot of thinking to get consistent, but has a lot of
potential IMO).
- dynamic_cast<char>(int) and co.
This would be a range checked cast. If the variable would overflow when stored
to the smaller type, a std::bad_cast will be thrown. If the destination type is
larger or equally sized, this behaves like static_cast.
- "Inheritance" of enum values
This kind of inheritance would actually work the other way round:
enum Base {
Value1,
Value2,
Value3
};
enum Derived : Base {
Value4,
Value5,
Value6
};
Now, a variable of type Base can store Value1-3, and a variable of type Derived
can store Value1-6. It is always possible to cast a variable of type Base to
Derived, for the other direction there needs to be a dynamic_cast<Base &> that
checks if the current value is in range. Multiple "inheritance" can be done if
the values used are distinct (but this is a can of worms).
- using object names from different namespaces with a different name
namespace foo {
const char *bar = "bar";
}
namespace baz {
using foo::bar = frobboz;
}
int main(int, char **) {
std::cout << baz::frobboz << std::endl;
}
This should actually compile, I think. :-)
- "compatible" yet distinct types
This would allow to specify that a given class has the same memory layout as
another class it is derived from and thus it is basically safe to convert a
reference/pointer to the base class to a reference/pointer to the derived
class. This could be used for example where input checking is to be performed:
class string { /* ... */ };
class trusted_string : compat string;
Lvalue casts from the base to the derived class have to be explicit. Since the
memory layout is the same, such types only have a single dynamic type (the base
type). The idea is to have "markers" that you can attach to specific types that
are evaluated at compile time and can be used to distinguish purposes for
things that are really the same type (like, user names and file names).
Optionally, one could try to make the conversion operator check for specific
constraints on the object before allowing the cast; this is difficult however
since there may be references to the base type floating around which could be
used to modify the object after the checks have been performed.
- nonblocking streams and stream transactions
iostreams should support a nonblocking mode (separate for read and write) that
makes it possible to extract data only if available or insert data only if that
would not block the program, and, additionally, transaction objects that can be
constructed on top of a stream, allow the same operations as the stream itself
but make the effects on the stream permanent only when commit() is called on
them before their destruction. These would be required for proper I/O
multiplexing. (Write transactions are easy, however read transactions would
require streambufs to be able to save all characters in the buffer while
reading on, which would be nontrivial).
Comments?
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Fri, 3 Sep 2004 22:46:55 GMT Raw View
"Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message news:ch5v8s$4r1lf$1@sunsystem5.informatik.tu-muenchen.de...
| Hi,
|
| some time ago it has been asked in this group whether anyone had large-scale
| wishes. Here are mine (in a short form, for some of these I've started writing
| longer texts about them). I'd like to have a bit of feedback whether anything
| like this would be worth pursuing or whether that makes no sense at all.
|
| - Explicit invariants
This is part of my proposal about Contract Programming. The syntax I have suggested is
class string
{
invariant
{
lenght < allocated;
}
};
A new revision will be available on the 10th of september from http://www.open-std.org/JTC1/SC22/WG21/
| This would mean that any attempt to store a value to allocated or length that
| would violate the invariant (i.e. make the expression within the parentheses
| evaluate to false) would throw an exception before the offending value has been
| written (that is, the assignment operator would throw).
The invariant should be checked before and after all public functions. And then special rules
need to be considered for destructors, constructors and when public functions call public functions
of the same class.
| Invariants would be part of the API if defined in public or protected sections,
| they may only reference members of the same visibility.
Seems very restrictive.
| thrown (this still needs a lot of thinking to get consistent, but has a lot of
| potential IMO).
Agreed.
| - dynamic_cast<char>(int) and co.
see boost::numeric_cast<char>( int ) at www.boost.org;
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: John Nagle <nagle@animats.com>
Date: Sat, 4 Sep 2004 07:41:51 GMT Raw View
Thorsten Ottosen wrote:
> "Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message news:ch5v8s$4r1lf$1@sunsystem5.informatik.tu-muenchen.de...
> | Hi,
> |
> | some time ago it has been asked in this group whether anyone had large-scale
> | wishes. Here are mine (in a short form, for some of these I've started writing
> | longer texts about them). I'd like to have a bit of feedback whether anything
> | like this would be worth pursuing or whether that makes no sense at all.
> |
> | - Explicit invariants
This is a difficult area. I did proof of correctness work
for three years, so I'm well aware of what it takes to make this
a valid concept. I wouldn't propose adding this to C++
(but check out D, which does have it.)
If you want to approach this seriously, you need to deal
with at least the following issues:
-- You need to be very clear about when control is inside and
when it's outside an object. Exactly when is the invariant
true?
-- What happens when an object calls its own public methods?
-- What happens when an object alters the private members
of another object of the same class?
-- What happens when object A calls object B which calls
object A?
-- What happens when a thread blocks inside an object?
-- What about pointers to member functions?
-- Is there some way to say that control is temporarily leaving
an object, to handle some of the above cases?
-- What happens when a constructor or destructor
calls a member function?
-- How do invariants and locking interact?
-- How do you talk about the "old" value of a variable
in invariants and assertions?
-- How do exception processing and invariants interact?
If you address all of these issues properly, the resulting
language won't be C++ any more. If you don't address them
properly, the invariant mechanism will add complexity without
safety. That's the problem.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Sun, 5 Sep 2004 15:56:32 GMT Raw View
Hi,
John Nagle <nagle@animats.com> writes:
[Explicit invariants]
>-- You need to be very clear about when control is inside and
>when it's outside an object. Exactly when is the invariant
>true?
Always. Well, nearly.
Actually my original proposal is a bit too strict, I have noticed, as
it is not able to deal with the simultaneous update of two variables:
class foo {
public:
foo(void) : a(3), b(3) {}
int a;
int b;
(a == b);
};
void frobnicate(foo &f) {
f.a = 5; // Throws, because (a != b)
f.b = 5; // Would validate the object again, but is not reached
return;
}
I think we can circumvent this with the invariants on booleans that are special
by being required only at the end of functions and stating that all other
invariants may also be violated as long as one of these is violated, i.e. the
example becomes
class foo {
public:
foo(void) : a(3), b(3) {}
int a;
int b;
(a == b);
bool valid;
(valid);
};
void frobnicate(foo &f) {
f.valid = false; // Okay, this invariant is special
f.a = 5; // Okay, since the object is "invalid" anyway
f.b = 5;
f.valid = true; // Here, the delayed check for (a == b) is inserted
return; // Here, (valid) is checked.
}
> -- What happens when an object calls its own public methods?
This is one of the issues why I said that the "special" invariants need more
thinking. :-) I can see two possible "solutions" for handling method calls from
"invalid sections".
- Forbid calling any method that may get its hands on a pointer or reference
to this. This means all member functions of the current class, and "this"
may not be passed as an argument (due to aliasing, this would expand to
"no argument of a function called may be of a dynamic type that is the same
as the type of this", which is probably too restrictive and could be remedied
only by forcing people to use "restrict". In short, I don't like it).
- Create a new modifier that says that the object passed is invalid, and
disallow passing the object to anything that does not say it is prepared to
deal with invalid objects. Sub-objects of invalid objects would be invalid
as well (propagation would be the same as for const, with the same
implications. std::list<T>::const_invalid_iterator, yuck!), and the default
assignment operator would change to "T &operator=(const T &) invalid;",
saying that it is acceptable to overwrite an invalid object. This solves the
aliasing problem up to the point where it currently is with regard to
constness.
> -- What happens when an object alters the private members
> of another object of the same class?
Nothing special. If an invariant would be violated, the operator= that would
have updated the member throws instead and it is up to the method to catch the
exception.
> -- What happens when object A calls object B which calls
> object A?
> -- What happens when a thread blocks inside an object?
These two are nasty instances of the aliasing problem. The best thing I can
come up with is to use the "invalid" modifier and place the restriction that
while this points to an invalid object, only member functions that can deal
with that may be called on the object itself and its sub-objects.
class A {
public:
void foo(void);
void baz(void);
B *b;
bool valid;
(valid);
};
class B {
public:
void bar(void);
A *a;
};
void B::bar(void) {
a->baz();
}
void A::foo(void) {
valid = false;
b->bar(); // Since this is invalid here, this->b is too
valid = true;
}
In order to be able to call B::bar() on b, B::bar needs to be declared as
"void B::bar(void) invalid", which means that B::bar() would be unable to
call A::baz() on B::a, since this would be pointing to an invalid object
within B::bar. Declaring "void A::baz(void) invalid" would solve that, but
the function would have to deal with the object being invalid, which is what
we want.
B::bar() could in fact get a non-invalid pointer to A by using a global. I
think this would be the same as lying about restricted pointers.
I believe that these "invalid sections" should in fact be kept as short as
possible and consist of assignments only (the only reason to allow method
calls within such a block at all is that operator= is a method). So, the
restrictions placed are not too high IMO. An additional way to avoid such
"invalid sections" would be to have a type that defines the data layout and
methods, and another that inherits everything, wraps the constructors and adds
the invariants plus an explicit constructor from the layout type. Then objects
could be constructed step by step and copied as a whole.
> -- What about pointers to member functions?
This is handled gracefully. A PMF may not violate invariants, and can only
be called on an invalid object if its signature says that it can handle that.
> -- Is there some way to say that control is temporarily leaving
> an object, to handle some of the above cases?
When we restrict the possible ways in which control can leave our object such
that everyone knows that there is a bad object out there, this should work.
>-- What happens when a constructor or destructor
> calls a member function?
Invariants need to be met after initializers have finished. In an object that
has attached invariants, it is acceptable to disallow calls to nonstatic
member functions that cannot handle the current object being "invalid" from
initializers. After the initializers, it is acceptable to call methods that
depend on the invariants being met.
>-- How do invariants and locking interact?
Not at all. Code that locks anything must be exception safe, and invariants
just pour a bit of oil to the flame by increasing the possible points where
exceptions may be thrown but have no other effect.
>-- How do you talk about the "old" value of a variable
> in invariants and assertions?
I think this is unneccesary, an invariant will always need to be verifiable
from a global point of view, i.e. without knowing the old value. If an
operation would violate an invariant, the exception is being thrown *instead*,
so the old value still remains inside the object.
>-- How do exception processing and invariants interact?
Exception objects should, for obvious reasons, not have invariants. :-)
The hairy points are those where we allow for an object to be in an invalid
state. If we get an exception thrown at us while "this" is invalid and do not
catch this exception, the current function ends, resulting in a check for the
"special" invariants which are not met, resulting in an exception. Here is
where the runtime calls terminate().
To summarize it: The default behaviour in my opinion should be to have all
invariants met during the entire object lifetime. There are cases that are not
covered by this, namely those that require multiple steps to go from one valid
state to another or that are sufficiently complex to test that it would not
make sense to recheck after each modification of the object. Those cases,
however, account for the most work when implementing invariants.
Simon
PS: We might also want to think about helping the optimizer here, as most of
the code generated here will end up in the dead code elimination.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Bob Bell <belvis@pacbell.net>
Date: Sun, 5 Sep 2004 18:52:21 GMT Raw View
In article <che39n$4ta9f$1@sunsystem5.informatik.tu-muenchen.de>,
Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
> Hi,
>
> John Nagle <nagle@animats.com> writes:
>
> [Explicit invariants]
>
> >-- You need to be very clear about when control is inside and
> >when it's outside an object. Exactly when is the invariant
> >true?
>
> Always. Well, nearly.
There are a couple problems with this idea, as far as I can tell.
The main problem with this proposal is that it's almost always necessary
to violate the invariants of a class temporarily in the class'
implementation. Your proposal makes that difficult and awkward in some
places and impossible in others.
The other problem is that there doesn't seem to be anything meaningful
that a calling function can do with an invariant failure exception. A
class invariant failure indicates a bug in the implementation of the
class -- how can a caller respond to that? Exceptions are the wrong way
to deal with invariants in C++.
> > -- What happens when an object alters the private members
> > of another object of the same class?
>
> Nothing special. If an invariant would be violated, the operator= that would
> have updated the member throws instead and it is up to the method to catch
> the
> exception.
I thought he meant something like
class A {
private:
friend class B;
int a, b;
(a == b);
};
class B {
public:
void F(A& a)
{
a.a = 1;
a.b = 2;
}
};
Is B allowed to violate the invariants, or is this caught?
> >-- What happens when a constructor or destructor
> > calls a member function?
>
> Invariants need to be met after initializers have finished. In an object that
> has attached invariants, it is acceptable to disallow calls to nonstatic
> member functions that cannot handle the current object being "invalid" from
> initializers. After the initializers, it is acceptable to call methods that
> depend on the invariants being met.
Unduly restrictive; the whole point of constructor bodies is to execute
code that establishes invariants that can't be established in member
initializers. Saying that initializers have to establis invariants is
nearly equivalent to saying that there should be no code in the body.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: John Nagle <nagle@animats.com>
Date: Mon, 6 Sep 2004 02:09:44 GMT Raw View
Simon Richter wrote:
> John Nagle <nagle@animats.com> writes:
>
> [Explicit invariants]
>
>
>>-- You need to be very clear about when control is inside and
>>when it's outside an object. Exactly when is the invariant
>>true?
>
>
> Always. Well, nearly.
>
> Actually my original proposal is a bit too strict, I have noticed, as
> it is not able to deal with the simultaneous update of two variables:
>
> I think we can circumvent this with the invariants on booleans that are special
> by being required only at the end of functions and stating that all other
> invariants may also be violated as long as one of these is violated, i.e. the
> example becomes
>
> class foo {
> public:
> foo(void) : a(3), b(3) {}
> int a;
> int b;
> (a == b);
>
> bool valid;
> (valid);
> };
That's a wierd approach, but not unsound. Usually,
function invariants are only expected to be true at entry
and exit. The "true all the time" rule is considered too
restrictive. But it does have the advantage that it forces
you to handle all the hard cases. You're not as concerned
about entry and exit.
Rather than treating "bool" as special,
the traditional approach would be to write something like
(valid implies (a == b))
which you can actually write as
(valid <= (a == b))
(work out the truth table for "<=" for bool and compare it
with implication)
>> -- What happens when an object calls its own public methods?
>> -- What happens when an object alters the private members
>> of another object of the same class?
>> -- What happens when object A calls object B which calls
>> object A?
>> -- What happens when a thread blocks inside an object?
>
That's covered by the "invariant true all the time" and "use
a valid flag" approach.
>>-- How do invariants and locking interact?
>
>
> Not at all. Code that locks anything must be exception safe, and invariants
> just pour a bit of oil to the flame by increasing the possible points where
> exceptions may be thrown but have no other effect.
That naeeds to be addressed more seriously.
>
>
>>-- How do you talk about the "old" value of a variable
>> in invariants and assertions?
>
>
> I think this is unneccesary, an invariant will always need to be verifiable
> from a global point of view, i.e. without knowing the old value. If an
> operation would violate an invariant, the exception is being thrown *instead*,
> so the old value still remains inside the object.
>
>
>>-- How do exception processing and invariants interact?
>
>
> Exception objects should, for obvious reasons, not have invariants. :-)
If you just turn off the "valid" flag in the exception handler,
it works out.
The concept of using a "valid" flag for an object does deal with
many of the hard cases.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Mon, 6 Sep 2004 06:03:19 GMT Raw View
* Thorsten Ottosen:
>
> The invariant should be checked before and after all public functions.
The invariant should hold for all functions potentially called by code
outside the class _that expects the invariant to hold_ (this includes
code in derived classes).
So the functions that the invariant should hold for include all public
functions, but the compiler has no way of ascertaining which other
functions: even a private member function might be used as "callback".
So I think there's a need to designate the functions other than the public
ones, for which the invariant should hold (before a call and after a call),
or perhaps easier & more helpful, to designate the "non-invariant-requiring"
functions, disallowing a public function to be so designated.
> And then special rules need to be considered for destructors, constructors
Very easy: an invariant holds and only holds when the object exists. At the
start of a constructor body and at the end of a destructor body the object
doesn't exist. At least not with regard to its invariant... ;-)
> and when public functions call public functions of the same class.
Nope, except wrt. efficiency. First off, "public" does not select the full
set of functions for which the invariant should hold. Second, if the class
implementor (? is that an english word) wants a public function f() that
does Something, and Something does not require the full invariant, and wants
to have Something done when the full invariant in fact does not hold, then
it's trivial to place the code doing Something in a non-invariant-requiring
function g() that f() is then just a wrapper for. This technique can also
be used for efficiency, to avoid full-blown invariant checking at every
call. So there's really no need to address that specially in the language.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Ben Strasser <ben04_01@freenet.de>
Date: Mon, 6 Sep 2004 15:22:13 GMT Raw View
Simon Richter wrote:
> Hi,
Hi
>
> - Explicit invariants
>
> For example, a string class that allocates more memory than it actually uses
> could do the following:
>
> class mystring {
> /* ... */
> private:
> size_t allocated;
> size_t length;
> (length < allocated);
> };
>
> This would mean that any attempt to store a value to allocated or length that
> would violate the invariant (i.e. make the expression within the parentheses
> evaluate to false) would throw an exception before the offending value has been
> written (that is, the assignment operator would throw).
>
The only way to implement this is by checking after each assignment =>
overheat. If now only want this in Debug build then you are very close
to what assert is meant for.
You can always define your own assert if you prefer throwing an
exception. But an exception may be caught in Debug build so you
aren't sure to notice it. In the release build it wont be thrown
=> bum.
> - dynamic_cast<char>(int) and co.
>
> This would be a range checked cast. If the variable would overflow when stored
> to the smaller type, a std::bad_cast will be thrown. If the destination type is
> larger or equally sized, this behaves like static_cast.
>
template<class To,class From>
To overflow_cast(From from){
if(from>static_cast<From>(numeric_limits<To>::max()))
throw bad_cast();
else
return static_cast<To>(from);
}
> - "compatible" yet distinct types
>
> This would allow to specify that a given class has the same memory layout as
> another class it is derived from and thus it is basically safe to convert a
> reference/pointer to the base class to a reference/pointer to the derived
> class. This could be used for example where input checking is to be performed:
>
> class string { /* ... */ };
How the virtual mechanisme is implemented is as far as I know not
defined so you can't assume that A will have the same size as B:
class A{
char a[3333];
public:
virtual ~A();
};
class B:public A{
};
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Mon, 6 Sep 2004 15:22:26 GMT Raw View
Hi,
"Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
[Explicit invariants]
>| This would mean that any attempt to store a value to allocated or length that
>| would violate the invariant (i.e. make the expression within the parentheses
>| evaluate to false) would throw an exception before the offending value has been
>| written (that is, the assignment operator would throw).
>The invariant should be checked before and after all public functions. And then special rules
>need to be considered for destructors, constructors and when public functions call public functions
>of the same class.
I think the invariant should be fulfilled the entire lifetime of the object
(starting when the initializers are finished and the constructor body is
started, ending when the destructor is being called, that is as long as the
compiler would insert a call to the destructor should the object go out of
scope). Allowing a function to violate the invariant until the end of the
function would leave us with an obviously incorrect object, which I try to
avoid by asking for the exception to be thrown before the operation rendering
the object invalid would be committed (this also has the advantage that you can
place assignments in try blocks and have specialized error handling).
The behaviour you describe would be achieved with the special case for invariants
placed on boolean members.
>| Invariants would be part of the API if defined in public or protected sections,
>| they may only reference members of the same visibility.
>Seems very restrictive.
There is no point in having an invariant connect members of differing
visibility, as you can always meet on "private" level by giving the
attribute accessors of the desired visibility. Being able to invalidate
an object with a write to a public member without being able to check
whether the write would invalidate the object (as the attributes I need to
check against are inaccessible) does not make sense to me, so this restriction
is not a real restriction, it just enforces some sanity. :-)
>| - dynamic_cast<char>(int) and co.
>see boost::numeric_cast<char>( int ) at www.boost.org;
Yes; I believe that should be standardized.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Mon, 6 Sep 2004 15:23:41 GMT Raw View
Hi,
Bob Bell <belvis@pacbell.net> writes:
> Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
>> John Nagle <nagle@animats.com> writes:
[Explicit invariants]
>> Always. Well, nearly.
>There are a couple problems with this idea, as far as I can tell.
I haven't asserted there aren't. :-) In fact, most of this is really hairy,
and although I have a feeling that there is a clean and elegant solution
somewhere we haven't found it yet.
>The main problem with this proposal is that it's almost always necessary
>to violate the invariants of a class temporarily in the class'
>implementation. Your proposal makes that difficult and awkward in some
>places and impossible in others.
Indeed, but allowing the invariants to be temporarily violated is about
equally awkward. :-/
ATM I favor the solution to add a new modifier, as this can be ignored by most
users (in contrast to enforcing the use of "restrict", of which I'm not yet
fully convinced it even solves the problem).
>The other problem is that there doesn't seem to be anything meaningful
>that a calling function can do with an invariant failure exception. A
>class invariant failure indicates a bug in the implementation of the
>class -- how can a caller respond to that? Exceptions are the wrong way
>to deal with invariants in C++.
It can do the same thing as with an array index that is out of bounds. Both
conditions are a sure sign of a programming error (and the proposed new
exception(s) would be subclasses of std::logic_error) and the program obviously
cannot continue, unless it has special provisions for doing so (for example,
catching an exception thrown by a pluging could result in the plugin being shut
down while the main program continues).
I'm sure compilers should be able to make a reasonable guess after DCE whether
an invariant will always be met at a certain point (testing code / exception
have been removed by the DCE), the outcome is uncertain (possible warning) or
that the invariant will always be violated here (parts of the method are
unreachable). As this is only a guess, it would not be a requirement for an
implementation, but implementations that do not make this guess will have to
live with the run-time overhead.
>> > -- What happens when an object alters the private members
>> > of another object of the same class?
>class A {
> private:
> friend class B;
> int a, b;
> (a == b);
>};
>class B {
> public:
> void F(A& a)
> {
> a.a = 1;
Throws unless a.b is 1.
> a.b = 2;
Throws.
> }
>};
>Is B allowed to violate the invariants, or is this caught?
This is caught. The invariant is known, part of the private "API" which B knows
about since it is a friend. There is no reason not to respect the invariant.
>> >-- What happens when a constructor or destructor
>> > calls a member function?
>> Invariants need to be met after initializers have finished.
>Unduly restrictive; the whole point of constructor bodies is to execute
>code that establishes invariants that can't be established in member
>initializers. Saying that initializers have to establis invariants is
>nearly equivalent to saying that there should be no code in the body.
I could live with relaxing that to saying that the this pointer points to an
invalid object during the execution of the constructor when invariants are
present in the class, thus limiting the methods that can be called from the
constructor, and saying that invariants have to be met only after the
constructor has finished (i.e. start of lifetime).
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Mon, 6 Sep 2004 15:24:04 GMT Raw View
Hi,
John Nagle <nagle@animats.com> writes:
>Simon Richter wrote:
>> John Nagle <nagle@animats.com> writes:
[Explicit invariants]
>> class foo {
>> public:
>> foo(void) : a(3), b(3) {}
>> int a;
>> int b;
>> (a == b);
>>
>> bool valid;
>> (valid);
>> };
> Rather than treating "bool" as special,
>the traditional approach would be to write something like
> (valid implies (a == b))
I like that.
> which you can actually write as
> (valid <= (a == b))
Hrm, the "arrow" points in the wrong direction. :-)
>> Not at all. Code that locks anything must be exception safe, and invariants
>> just pour a bit of oil to the flame by increasing the possible points where
>> exceptions may be thrown but have no other effect.
> That naeeds to be addressed more seriously.
>>>-- How do exception processing and invariants interact?
> If you just turn off the "valid" flag in the exception handler,
>it works out.
Iek. This is what people will do. :-)
> The concept of using a "valid" flag for an object does deal with
>many of the hard cases.
Indeed, but I'd like to also have a way of expressing that a function should
either "return" the object to the caller in a valid state or throw an
exception, to avoid code like this:
std::logic_error up("I accidentally broke the object");
int foo(A a) {
a.bar();
if(!a.valid())
throw up;
a.baz();
if(!a.valid())
throw up;
}
This is why I would like to have the special treatment for booleans (while I
still don't like the implications of having to keep track of which objects
may not be valid at this time).
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Bob Bell <belvis@pacbell.net>
Date: Mon, 6 Sep 2004 20:31:26 GMT Raw View
In article <chgctn$4tpdl$1@sunsystem5.informatik.tu-muenchen.de>,
Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
> Hi,
>
> Bob Bell <belvis@pacbell.net> writes:
> >The main problem with this proposal is that it's almost always necessary
> >to violate the invariants of a class temporarily in the class'
> >implementation. Your proposal makes that difficult and awkward in some
> >places and impossible in others.
>
> Indeed, but allowing the invariants to be temporarily violated is about
> equally awkward. :-/
Perhaps, but it seems to be necessary.
> >The other problem is that there doesn't seem to be anything meaningful
> >that a calling function can do with an invariant failure exception. A
> >class invariant failure indicates a bug in the implementation of the
> >class -- how can a caller respond to that? Exceptions are the wrong way
> >to deal with invariants in C++.
>
> It can do the same thing as with an array index that is out of bounds. Both
> conditions are a sure sign of a programming error (and the proposed new
> exception(s) would be subclasses of std::logic_error) and the program
> obviously
> cannot continue, unless it has special provisions for doing so (for example,
> catching an exception thrown by a pluging could result in the plugin being
> shut
> down while the main program continues).
This seems to be a common misunderstanding. There is a big difference
between something like vector::at, which throws an exception if the
index argument is out of range, and a function Foo::F() which throws an
exception if F (or some function it calls) has a bug.
When vector::at throws, it is not indicating a bug in vector. It's
simply saying that it can't apply the index that was given to it. It is
part of vector::at's _interface_ that an exception can be thrown. A
caller could call vector::at with a different index and expect it to
work.
On the other hand, when Foo::F() throws because an invariant of Foo is
violated, it indicates a bug in Foo. Invariant failure exceptions mean
that something in Foo's _implementation_ has failed to do what it was
supposed to. In general, there's nothing meaningful that a caller of Foo
can do; there is no way a caller can know how to make another call to
Foo that will succeed.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Mon, 6 Sep 2004 20:49:53 GMT Raw View
Hi,
alfps@start.no (Alf P. Steinbach) writes:
>* Thorsten Ottosen:
>> The invariant should be checked before and after all public functions.
>So I think there's a need to designate the functions other than the public
>ones, for which the invariant should hold (before a call and after a call),
>or perhaps easier & more helpful, to designate the "non-invariant-requiring"
>functions, disallowing a public function to be so designated.
This is what I proposed with the "invalid" modifier for class objects.
I could see that at least operator=(const T&) may be public and could work
just fine when the current object is invalid (and with the "members of invalid
objects are considered invalid" rule to avoid the A calls B calls A case it is
required that we still can overwrite our members while the invariants are not
met in order to get back to a valid state).
>> And then special rules need to be considered for destructors, constructors
>Very easy: an invariant holds and only holds when the object exists. At the
>start of a constructor body and at the end of a destructor body the object
>doesn't exist. At least not with regard to its invariant... ;-)
Agreed, so "this" is of type "invalid T*" during the constructor and destructor
if the object has invariants.
>This technique can also
>be used for efficiency, to avoid full-blown invariant checking at every
>call. So there's really no need to address that specially in the language.
The "invariants true all the time" approach also has an efficiency advantage:
I believe the proper behaviour for the compiler could be described as follows:
The compiler inserts an assertion for all the invariants before the body of
every function that expects them to be met, and explicit checks for the
invariants at every point where the object is modified. After DCE, the
assertions are removed. Additionally, a full check is inserted at the end of
the constructor.
The checks are not necessary at the beginning of a function, since the
invariants should be met already, however the result of these tests can be
used for optimizing out "redundant" tests.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Mon, 6 Sep 2004 20:50:51 GMT Raw View
"Bob Bell" <belvis@pacbell.net> wrote in message news:belvis-72B9C4.11155605092004@news.la.sbcglobal.net...
| In article <che39n$4ta9f$1@sunsystem5.informatik.tu-muenchen.de>,
| Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
| The other problem is that there doesn't seem to be anything meaningful
| that a calling function can do with an invariant failure exception. A
| class invariant failure indicates a bug in the implementation of the
| class -- how can a caller respond to that?
It all depends on the severity of the bug; catching the exception might be better
than crashing the program.
| Exceptions are the wrong way
| to deal with invariants in C++.
To some extend, yes. An invariant mechanism is primarily for
- debugging
- specification and documentation
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Mon, 6 Sep 2004 20:51:41 GMT Raw View
"John Nagle" <nagle@animats.com> wrote in message news:kJc_c.15687$rt7.12376@newssvr29.news.prodigy.com...
| Thorsten Ottosen wrote:
|
| > "Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message
news:ch5v8s$4r1lf$1@sunsystem5.informatik.tu-muenchen.de...
| > | Hi,
| > | - Explicit invariants
|
| This is a difficult area. I did proof of correctness work
| for three years, so I'm well aware of what it takes to make this
| a valid concept. I wouldn't propose adding this to C++
| (but check out D, which does have it.)
good thing I'm doing it then.
| If you want to approach this seriously, you need to deal
| with at least the following issues:
|
| -- You need to be very clear about when control is inside and
| when it's outside an object. Exactly when is the invariant
| true?
I think you mean to say, when is the invariant *required* to hold.
| -- What happens when an object calls its own public methods?
a debateable issue, but I think it should be disabled during nested calls.
| -- What happens when an object alters the private members
| of another object of the same class?
nothing. If you allow that, it means you have broken encapsulation.
And if you do that, nothing can help you.
| -- What happens when object A calls object B which calls
| object A?
the usual rules apply and the invariant is not disabled.
| -- What happens when a thread blocks inside an object?
the C++ standard does not yet deal with threads, so that would be implementation defined.
| -- What about pointers to member functions?
when executed, there should be a call to the invariant of the associated object.
| -- Is there some way to say that control is temporarily leaving
| an object, to handle some of the above cases?
can you give an example?
| -- What happens when a constructor or destructor
| calls a member function?
For the constructor, the invariant checking on public functions should be disabled.
For the destructor, nothing special happens.
| -- How do invariants and locking interact?
again, the standard does not deal with these issues.
| -- How do you talk about the "old" value of a variable
| in invariants and assertions?
via std::old()
| -- How do exception processing and invariants interact?
debateable, but I think calling the invariant from the destructor is sufficient; alternatively,
one should call the invariant when exiting a function because of an exception.
| If you address all of these issues properly, the resulting
| language won't be C++ any more.
what a strange remark.
| If you don't address them
| properly, the invariant mechanism will add complexity without
| safety. That's the problem.
please show how that happens.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Mon, 6 Sep 2004 20:53:02 GMT Raw View
"Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message news:chdhvo$4t25u$1@sunsystem5.informatik.tu-muenchen.de...
| Hi,
|
| "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
|
| [Explicit invariants]
|
| >| This would mean that any attempt to store a value to allocated or length that
| >| would violate the invariant (i.e. make the expression within the parentheses
| >| evaluate to false) would throw an exception before the offending value has been
| >| written (that is, the assignment operator would throw).
|
| >The invariant should be checked before and after all public functions. And then special rules
| >need to be considered for destructors, constructors and when public functions call public functions
| >of the same class.
|
| I think the invariant should be fulfilled the entire lifetime of the object
| (starting when the initializers are finished and the constructor body is
| started, ending when the destructor is being called, that is as long as the
| compiler would insert a call to the destructor should the object go out of
| scope).
ok.
| Allowing a function to violate the invariant until the end of the
| function would leave us with an obviously incorrect object,
this is not obvious.
| >| Invariants would be part of the API if defined in public or protected sections,
| >| they may only reference members of the same visibility.
|
| >Seems very restrictive.
|
| There is no point in having an invariant connect members of differing
| visibility, as you can always meet on "private" level by giving the
| attribute accessors of the desired visibility. Being able to invalidate
| an object with a write to a public member without being able to check
| whether the write would invalidate the object (as the attributes I need to
| check against are inaccessible) does not make sense to me, so this restriction
| is not a real restriction, it just enforces some sanity. :-)
not sure what you're saying here, but surely you can
check the write is ok as part of that functions precondition.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Mon, 6 Sep 2004 22:36:35 GMT Raw View
Hi,
Ben Strasser <ben04_01@freenet.de> writes:
>Simon Richter wrote:
[Explicit invariants]
>The only way to implement this is by checking after each assignment =>
>overheat. If now only want this in Debug build then you are very close
>to what assert is meant for.
Well, assertions happen when someone cares to check. Invariants happen in
the offending stack frame, so it's easy to tell your debugger to stop on an
invariant violation and examine what led there.
[dynamic_cast<char>(int) and co.]
>template<class To,class From>
>To overflow_cast(From from){
> if(from>static_cast<From>(numeric_limits<To>::max()))
> throw bad_cast();
> else
> return static_cast<To>(from);
>}
I know I can implement it myself, but this is such a basic thing that I'd like
to see it in the standard.
["compatible" yet distinct types]
>How the virtual mechanisme is implemented is as far as I know not
>defined so you can't assume that A will have the same size as B:
That's the point of the proposal. "compatible" types can only be checked
statically since their dynamic types are the same. Think custom type modifiers
that make a type improper to pass as an argument, even though it's the same
type.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Tue, 7 Sep 2004 03:05:44 GMT Raw View
"Alf P. Steinbach" <alfps@start.no> wrote in message news:413bcd5e.251678937@news.individual.net...
| * Thorsten Ottosen:
| >
| > The invariant should be checked before and after all public functions.
|
| The invariant should hold for all functions potentially called by code
| outside the class _that expects the invariant to hold_ (this includes
| code in derived classes).
so you mean protected functions should be included too?
| So the functions that the invariant should hold for include all public
| functions, but the compiler has no way of ascertaining which other
| functions: even a private member function might be used as "callback".
If you break the encapsulation of a class like this, you're on your own.
Clients of a class should, however, be allowed to call the invariant of a
class at any time as if it was a public function.
| > And then special rules need to be considered for destructors, constructors
|
| Very easy: an invariant holds and only holds when the object exists. At the
| start of a constructor body
rather at the end.
| and at the end of a destructor body the object
| doesn't exist. At least not with regard to its invariant... ;-)
it's more complex than that; if you read my new proposal (due on friday), you'll see why.
|
| > and when public functions call public functions of the same class.
|
| Nope, except wrt. efficiency.
if efficienvy is a concern, invariants must be disabled.
| Second, if the class
| implementor (? is that an english word) wants a public function f() that
| does Something, and Something does not require the full invariant, and wants
| to have Something done when the full invariant in fact does not hold, then
| it's trivial to place the code doing Something in a non-invariant-requiring
| function g() that f() is then just a wrapper for.
true.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Tue, 7 Sep 2004 03:57:28 GMT Raw View
* Simon Richter:
>
> The compiler inserts an assertion for all the invariants before the body of
> every function that expects them to be met, and explicit checks for the
> invariants at every point where the object is modified.
"Before the body" is OK.
"At every point where the object is modified" is not OK.
Inside a function the invariant will, in general, be temporarily
broken: all that's guaranteed is that it holds on return or throw.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Tue, 7 Sep 2004 05:37:16 GMT Raw View
* Thorsten Ottosen:
> "Alf P. Steinbach" <alfps@start.no> wrote in message news:413bcd5e.251678937@news.individual.net...
> | * Thorsten Ottosen:
> | >
> | > The invariant should be checked before and after all public functions.
> |
> | The invariant should hold for all functions potentially called by code
> | outside the class _that expects the invariant to hold_ (this includes
> | code in derived classes).
>
> so you mean protected functions should be included too?
No, I mean those functions that assume the invariant holds on entry,
and guarantee the invariant on return or exception.
That includes all public functions and whatever other functions, protected
or private, that the programmer decides to include in this group.
Ooops, now I see there's a problem with letting invariant checking throw
an exception... :-)
> | So the functions that the invariant should hold for include all public
> | functions, but the compiler has no way of ascertaining which other
> | functions: even a private member function might be used as "callback".
>
> If you break the encapsulation of a class like this, you're on your own.
It is not broken encapsulation: it's the opposite, namely very careful
encapsulation, and that's precisely where language support would come in
handy (simpler cases of encapsulation already have language support).
> Clients of a class should, however, be allowed to call the invariant of a
> class at any time as if it was a public function.
That might be nice for debugging.
> | > And then special rules need to be considered for destructors, constructors
> |
> | Very easy: an invariant holds and only holds when the object exists. At the
> | start of a constructor body
>
> rather at the end.
No, at the start. The constructor body has the job of establishing the
invariant. The invariant _cannot_, in general, be assumed to hold at the
start of a constructor body.
Well, hold it: it can. But only by including a default state in the
invariant like in e.g. a C# struct or in general in Java and C# programming.
And I really really really hope that's not what you mean.
C++ isn't Java.
> | and at the end of a destructor body the object
> | doesn't exist. At least not with regard to its invariant... ;-)
>
> it's more complex than that; if you read my new proposal (due on friday), you'll see why.
I don't think so, because the point of invariants is to simplify. And if a
scheme for invariants makes things more complicated then it's not useful.
Send me a URL (to real e-mail address) and I'll take a look at it.
Oops again, my memory's making some clicking noises: I have an e-mail I've
not responded to! Sorry about that. Invoking excuse-generator... ;-)
Excuse generator: I'm now taking painkillers & penicillin for a nasty
infection and depending on the way I sit or walk, it hurts. But send that
URL and I'll take a look at it. I think "more complex than that" should not
be part of any final proposal, for if it is, then it's not useful.
> | > and when public functions call public functions of the same class.
> |
> | Nope, except wrt. efficiency.
>
> if efficienvy is a concern, invariants must be disabled.
Efficiency is a concern in testing. Both with regard to time consumed
in testing (and not just because of slow code but e.g. because of
"information overload"), and in that sometimes there are timing constraints
on code. Some people argue that all debugging aids should be there also in a
"release" build, but how an error should be handled & reported is
usually different for end-users and programmers.
In testing you do not generally want invariants disabled.
What you (or at least, I) want is invariant checking where it Really
Counts, and not in every call, and as mentioned that can easily be
accomplished simply by restructuring the code a little using
invariant-enabled wrappers for invariant-ignoring internal functions, and
also it's a tool Quality Of Implementation issue.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Tue, 7 Sep 2004 22:14:31 GMT Raw View
Hi,
"Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
>"Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message news:chdhvo$4t25u$1@sunsystem5.informatik.tu-muenchen.de...
>| "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
[Explicit invariants]
>| I think the invariant should be fulfilled the entire lifetime of the object
>| (starting when the initializers are finished and the constructor body is
>| started, ending when the destructor is being called, that is as long as the
>| compiler would insert a call to the destructor should the object go out of
>| scope).
It has been pointed out that the lifetime of an object starts at the end of
the constructor body and that it would make more sense to let the constructor
body establish invariants before they are first checked.
>| Allowing a function to violate the invariant until the end of the
>| function would leave us with an obviously incorrect object,
>this is not obvious.
If the function were allowed to leave the object in an invalid state, we would
either need a rollback mechanism that rolls back the entire object to the state
it had before the method call (but that could create inconsistencies with other
objects) or give back a totally unusable object. If invariants were to be
checked before each update of the object, it is easy to insert appropriate try
blocks within the modifying function to implement rollback or error handling if
desired, while a single check at function exit would not make it possible to
backtrace the culprit.
>| >| Invariants would be part of the API if defined in public or protected sections,
>| >| they may only reference members of the same visibility.
>| >Seems very restrictive.
>| There is no point in having an invariant connect members of differing
>| visibility, as you can always meet on "private" level by giving the
>| attribute accessors of the desired visibility. Being able to invalidate
>| an object with a write to a public member without being able to check
>| whether the write would invalidate the object (as the attributes I need to
>| check against are inaccessible) does not make sense to me, so this restriction
>| is not a real restriction, it just enforces some sanity. :-)
>not sure what you're saying here, but surely you can
>check the write is ok as part of that functions precondition.
class A {
public:
int a;
private:
int b;
(a == b);
};
How am I supposed to update A.a? It is accessible to me, but I have no way of
determining which values are acceptable. If the compiler allows me to update a,
then I can damage the object. If not, then a is obviously not public.
The solution is
class A {
public:
int a(void);
void a(int) throw(inacceptable);
private:
int a;
int b;
(a == b);
};
void A::a(int na) throw(inacceptable) {
if(na != b)
throw inacceptable();
a = na;
return;
}
Note that the accessor throws something meaningful here. Voila, we just forced
a programmer to do the right thing. :-)
(I can smell something really hairy starting to burn here: Obviously we would
like the invariant check in the "a = na" line to be optimized out since it can
never fire. This reduces to:
void foo(int a, int b) {
assert((!(a == b)) == (a != b));
/* ... */
}
Is the assertion always true?
It can be done on the tree at a later stage, but it would be a neat thing to
keep such code out of the tree as far as possible so we don't slow down
compilation too much.)
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: kuyper@wizard.net (James Kuyper)
Date: Tue, 7 Sep 2004 22:14:45 GMT Raw View
Simon Richter <richtesi@informatik.tu-muenchen.de> wrote in message news:<chgctn$4tpdl$1@sunsystem5.informatik.tu-muenchen.de>...
..
> >The main problem with this proposal is that it's almost always necessary
> >to violate the invariants of a class temporarily in the class'
> >implementation. Your proposal makes that difficult and awkward in some
> >places and impossible in others.
>
> Indeed, but allowing the invariants to be temporarily violated is about
> equally awkward. :-/
No, not equally. Most invariants require two or more seperate
statements to be executed to maintain them. If the invariant is
enforced after every statement, then it becomes impossible to write
the code that was intended to maintain it. Simple example: say that a
class invariant for a class named constProduct specifies "a*b==1.0".
Consider the following method:
void constProduct::apply_factor(double f)
{
a*=f; // Tthows, because the invariant is violated.
b/=f;
}
Allowing invariants to be temporarily violated, at least inside the
class implementation, could not possibly be more awkward than
something which prevents them from being used at all.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Tue, 7 Sep 2004 22:15:17 GMT Raw View
===================================== MODERATOR'S COMMENT:
Please hard-wrap your lines at 78 characters.
===================================== END OF MODERATOR'S COMMENT
"Alf P. Steinbach" <alfps@start.no> wrote in message news:413d2afa.341179000@news.individual.net...
| * Thorsten Ottosen:
| > so you mean protected functions should be included too?
|
| No, I mean those functions that assume the invariant holds on entry,
| and guarantee the invariant on return or exception.
|
| That includes all public functions and whatever other functions, protected
| or private, that the programmer decides to include in this group.
|
| Ooops, now I see there's a problem with letting invariant checking throw
| an exception... :-)
what problem?
| > | > And then special rules need to be considered for destructors, constructors
| > |
| > | Very easy: an invariant holds and only holds when the object exists. At the
| > | start of a constructor body
| >
| > rather at the end.
|
| No, at the start. The constructor body has the job of establishing the
| invariant. The invariant _cannot_, in general, be assumed to hold at the
| start of a constructor body.
I'm glad to see we agree afterall.
| > | and at the end of a destructor body the object
| > | doesn't exist. At least not with regard to its invariant... ;-)
| >
| > it's more complex than that; if you read my new proposal (due on friday), you'll see why.
|
| I don't think so, because the point of invariants is to simplify. And if a
| scheme for invariants makes things more complicated then it's not useful.
As an example, there probably has to be special rules for evaluating the invariant in case of inheritance.
Another example, as John asked, should the invariant be cheked during calls to the public functions in the
constructor? Probably not, since the invariant is first guaranteed to hold at the end of the of the constructor.
br
Thorsten
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Simon Richter <richtesi@informatik.tu-muenchen.de>
Date: Tue, 7 Sep 2004 22:15:27 GMT Raw View
Hi,
alfps@start.no (Alf P. Steinbach) writes:
>* Thorsten Ottosen:
>> so you mean protected functions should be included too?
>No, I mean those functions that assume the invariant holds on entry,
>and guarantee the invariant on return or exception.
Which is basically all functions except those to aid the destructor (but if
your destructor is complex enough to require a helper function, you probably
want part of your object moved to a sub-object) and those that are called
from places where it was necessary to violate the invariants temporarily
(which should not happen either, since you should build up the new object
state in local variables and commit it all at once in such a case), and a few
others that simply do not require anything about the state of the current
object (operator=() for example). So functions that do not assume invariants
on entry should be seldom, and functions that do not guarantee the invariants
on exit should be really rare.
>Ooops, now I see there's a problem with letting invariant checking throw
>an exception... :-)
Yes, that is the reason why invariant checking at function exit is pointless.
The only things my proposal includes that are done at function exit is
- the first check on the object, at constructor exit (which does not cause
trouble since the object will not have been created if the constructor
throws, so the check only happens when the constructor returns)
- verification that all "temporarily violated" invariants have in fact been
restored. Failure to do so means that you overlooked an exit path from the
function. Note that the invariants themselves are not checked here, they
are checked when the "temporarily disabled" flag is reset.
>What you (or at least, I) want is invariant checking where it Really
>Counts, and not in every call, and as mentioned that can easily be
>accomplished simply by restructuring the code a little using
>invariant-enabled wrappers for invariant-ignoring internal functions, and
>also it's a tool Quality Of Implementation issue.
"invariant-ignoring" functions tend to be a lot less efficient than
invariant-aware functions since they cannot assume anything about the
object's state. The interesting thing about invariants is that they are
something that has already been verified at function entry, so the function
can leave out any tests for these conditions. The price to pay for that is
that the condition needs to be reevaluated whenever the object is updated,
which is not too high since you just need to recheck those conditions that
depend on the variable in question, and that one is in a register at this
point.
Perhaps the most interesting invariant is
class A;
class B {
private:
A *a;
(a != 0);
public:
void foo(void);
};
This allows the programmer to change
void B::foo(void) {
if(a)
a.frobnicate();
}
to
void B::foo(void) {
a.frobnicate();
}
If a gets used in this way more often than it is updated, we already have
gained a few cycles.
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: John Nagle <nagle@animats.com>
Date: Tue, 7 Sep 2004 22:17:13 GMT Raw View
Thorsten Ottosen wrote:
> "John Nagle" <nagle@animats.com> wrote in message news:kJc_c.15687$rt7.12376@newssvr29.news.prodigy.com...
> | Thorsten Ottosen wrote:
> |
> | > "Simon Richter" <richtesi@informatik.tu-muenchen.de> wrote in message
> news:ch5v8s$4r1lf$1@sunsystem5.informatik.tu-muenchen.de...
> | > | Hi,
>
> | > | - Explicit invariants
> |
> | This is a difficult area. I did proof of correctness work
> | for three years, so I'm well aware of what it takes to make this
> | a valid concept. I wouldn't propose adding this to C++
> | (but check out D, which does have it.)
>
> good thing I'm doing it then.
>
> | If you want to approach this seriously, you need to deal
> | with at least the following issues:
> |
> | -- You need to be very clear about when control is inside and
> | when it's outside an object. Exactly when is the invariant
> | true?
>
> I think you mean to say, when is the invariant *required* to hold.
>
> | -- What happens when an object calls its own public methods?
>
> a debateable issue, but I think it should be disabled during nested calls.
Ah. But how do you tell when you have a nested call?
C++ lets you call public methods from methods of the same object.
This confuses the inside/outside distinction. It causes troubles
for both invariant and locking proposals. If the compiler is
going to check invariants at class exit, it needs to know
exactly when control is leaving the object. Right now,
it doesn't know this.
One could tighten up on the inside/outside distinction, but
it wouldn't be backwards compatible. Basically, you'd need
to prevent calling public member functions from inside the
object. There are some other holes that need to be plugged, too.
>
> | -- What happens when an object alters the private members
> | of another object of the same class?
>
> nothing. If you allow that, it means you have broken encapsulation.
> And if you do that, nothing can help you.
C++ allows this. It's routinely used for copy constructors.
It's worth noting that for passed const objects, the callee
can't break the invariant. That covers the copy constructor
case. But how do you deal with functions like "swap",
which must work on two objects simultaneously and will
temporarily break the invariants of both?
>
> | -- What happens when object A calls object B which calls
> | object A?
>
> the usual rules apply and the invariant is not disabled.
OK. But think about how hard it is to determine that
the invariant was true when control "left" A. When did
control leave A?
This is a constant nightmare in systems with too many
callbacks, like the older GUI systems. Objects are constantly
being re-entered, sometimes while in an invalid state. It's
worth thinking about this in a more rigorous fashion.
>
> | -- What happens when a thread blocks inside an object?
>
> the C++ standard does not yet deal with threads, so that would be implementation defined.
We're looking at the next version of C++ here, and there's
general agreement that threads have to be dealt with in some way.
(There's great argument over how, but that's a different thread.)
>
> | -- What about pointers to member functions?
>
> when executed, there should be a call to the invariant of the associated object.
>
> | -- Is there some way to say that control is temporarily leaving
> | an object, to handle some of the above cases?
>
> can you give an example?
See A calls B which calls A, above. At some point, some member
function of A should be able to say "control is now leaving A, and
the invariant is now valid". This also applies to locking; that's
when you unlock A.
>
> | -- What happens when a constructor or destructor
> | calls a member function?
>
> For the constructor, the invariant checking on public functions should be disabled.
So the compiler should know that some calls to public functions are
special?
>
> For the destructor, nothing special happens.
If you're partway through a destructor, and call a public member
function, is the invariant still supposed to be true?
>
> | -- How do invariants and locking interact?
>
> again, the standard does not deal with these issues.
The next version has to deal with threads in some way,
which implies dealing with locking.
>
> | -- How do you talk about the "old" value of a variable
> | in invariants and assertions?
>
> via std::old()
What gets saved? The whole object?
>
> | -- How do exception processing and invariants interact?
>
> debateable, but I think calling the invariant from the destructor is sufficient; alternatively,
> one should call the invariant when exiting a function because of an exception.
>
> | If you address all of these issues properly, the resulting
> | language won't be C++ any more.
>
> what a strange remark.
Not really. You can't change C++ very much. Too much legacy code.
>
> | If you don't address them
> | properly, the invariant mechanism will add complexity without
> | safety. That's the problem.
>
> please show how that happens.
It should be obvious at this point.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Tue, 7 Sep 2004 22:30:26 GMT Raw View
"Thorsten Ottosen" <nesotto@cs.auc.dk> wrote in message news:<413c66fd$0$202$14726298@news.sunsite.dk>...
> "Bob Bell" <belvis@pacbell.net> wrote in message news:belvis-72B9C4.11155605092004@news.la.sbcglobal.net...
> | In article <che39n$4ta9f$1@sunsystem5.informatik.tu-muenchen.de>,
> | Simon Richter <richtesi@informatik.tu-muenchen.de> wrote:
>
> | The other problem is that there doesn't seem to be anything meaningful
> | that a calling function can do with an invariant failure exception. A
> | class invariant failure indicates a bug in the implementation of the
> | class -- how can a caller respond to that?
>
> It all depends on the severity of the bug; catching the exception might be better
> than crashing the program.
Sure, it might be. Catching the exception might also be the worst
thing you could do. How will you know? It seems to me that the effort
required to make this determination is about the same (and would be
better spent) fixing the bug and not having an exception in the first
place.
> | Exceptions are the wrong way
> | to deal with invariants in C++.
>
> To some extend, yes. An invariant mechanism is primarily for
>
> - debugging
> - specification and documentation
Funny, that's what I thought assertions were for. ;-)
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Tue, 7 Sep 2004 19:43:57 CST Raw View
* Simon Richter:
> Hi,
>
> alfps@start.no (Alf P. Steinbach) writes:
>
> >* Thorsten Ottosen:
>
> >> so you mean protected functions should be included too?
>
> >No, I mean those functions that assume the invariant holds on entry,
> >and guarantee the invariant on return or exception.
>
> Which is basically all functions except those to aid the destructor (but if
> your destructor is complex enough to require a helper function, you probably
> want part of your object moved to a sub-object) and those that are called
> from places where it was necessary to violate the invariants temporarily
> (which should not happen either, since you should build up the new object
> state in local variables and commit it all at once in such a case), and a few
> others that simply do not require anything about the state of the current
> object (operator=() for example). So functions that do not assume invariants
> on entry should be seldom, and functions that do not guarantee the invariants
> on exit should be really rare.
"Should be", "should be". In my experience they _are_ rare, but the problem is
there anyway: how to designate them. Saying that the set of variant-enforcing
functions consists only of the public functions, or only of the public and
protected functions, is IMHO not good enough by half.
> >Ooops, now I see there's a problem with letting invariant checking throw
> >an exception... :-)
>
> Yes, that is the reason why invariant checking at function exit is pointless.
No, that is the reason why an exception is the wrong response to a
variant violation.
> Perhaps the most interesting invariant is
>
> class A;
>
> class B {
> private:
> A *a;
> (a != 0);
>
> public:
> void foo(void);
> };
>
> This allows the programmer to change
>
> void B::foo(void) {
> if(a)
> a.frobnicate();
> }
>
> to
>
> void B::foo(void) {
> a.frobnicate();
> }
>
> If a gets used in this way more often than it is updated, we already have
> gained a few cycles.
Thank you, I think I have a good grasp of what a (class) invariant is, but
perhaps not every reader of this thread has a clear picture.
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Thorsten Ottosen" <nesotto@cs.auc.dk>
Date: Tue, 7 Sep 2004 19:44:14 CST Raw View
"John Nagle" <nagle@animats.com> wrote in message
news:K3m%c.12694$QJ3.7059@newssvr21.news.prodigy.com...
| Thorsten Ottosen wrote:
| > "John Nagle" <nagle@animats.com> wrote in message
news:kJc_c.15687$rt7.12376@newssvr29.news.prodigy.com...
| > | -- What happens when an object calls its own public methods?
| >
| > a debateable issue, but I think it should be disabled during nested calls.
|
| Ah. But how do you tell when you have a nested call?
if a flag is set saying we entered a public function.
| C++ lets you call public methods from methods of the same object.
| This confuses the inside/outside distinction. It causes troubles
| for both invariant and locking proposals. If the compiler is
| going to check invariants at class exit, it needs to know
| exactly when control is leaving the object. Right now,
| it doesn't know this.
hm...unless one is doing long-jump or something, I don't see why
a flag per class can keep track of nested calls.
| One could tighten up on the inside/outside distinction, but
| it wouldn't be backwards compatible. Basically, you'd need
| to prevent calling public member functions from inside the
| object. There are some other holes that need to be plugged, too.
which are?
| >
| > | -- What happens when an object alters the private members
| > | of another object of the same class?
| >
| > nothing. If you allow that, it means you have broken encapsulation.
| > And if you do that, nothing can help you.
|
| C++ allows this. It's routinely used for copy constructors.
|
| It's worth noting that for passed const objects, the callee
| can't break the invariant. That covers the copy constructor
| case. But how do you deal with functions like "swap",
| which must work on two objects simultaneously and will
| temporarily break the invariants of both?
I don't see what the problem is here. The swap() function has as
it responsibility to preserve the invariant of the two objects it swappes.
That can be cheked in manually in various ways, eg,
1. void swap( X& r ) postcondition( std::old( *this ) == r; std::old( r ) ==
*this; }
2. void swap( X& r ) { ...; r.invariant(); this->invariant(); }
| >
| > | -- What happens when object A calls object B which calls
| > | object A?
| >
| > the usual rules apply and the invariant is not disabled.
|
| OK. But think about how hard it is to determine that
| the invariant was true when control "left" A. When did
| control leave A?
Ok, I misunderstood something here. I see your point and I guess it would be
wrong to require detection of control leaving A to B.
remark: we should also keep in mind that for any reasonable program, keeping
invariants in the
object code for a release build is not realistic. hence, the intended use of
invariants is
1. debugging
2. class specification and documentation
| This is a constant nightmare in systems with too many
| callbacks, like the older GUI systems. Objects are constantly
| being re-entered, sometimes while in an invalid state. It's
| worth thinking about this in a more rigorous fashion.
ok. maybe explicit calls to the invariant on certain objects can help
detect problems.
| > | -- What happens when a thread blocks inside an object?
| >
| > the C++ standard does not yet deal with threads, so that would be
implementation defined.
|
| We're looking at the next version of C++ here, and there's
| general agreement that threads have to be dealt with in some way.
yet there is no proposal yet.
| (There's great argument over how, but that's a different thread.)
yeah, I know. but since we don't know how threads will impact on the standard,
how can
we deal with them?
Anyway, what would be your worries with regard to threads and contract
programming?
| > | -- Is there some way to say that control is temporarily leaving
| > | an object, to handle some of the above cases?
| >
| > can you give an example?
|
| See A calls B which calls A, above. At some point, some member
| function of A should be able to say "control is now leaving A, and
| the invariant is now valid". This also applies to locking; that's
| when you unlock A.
I don't know how such a mechanism would look like. You can always call the
invariant manually if that would help?
| > | -- What happens when a constructor or destructor
| > | calls a member function?
| >
| > For the constructor, the invariant checking on public functions should be
disabled.
|
| So the compiler should know that some calls to public functions are
| special?
yes. I think a public constructor would need a flag like other public
functions.
| >
| > For the destructor, nothing special happens.
|
| If you're partway through a destructor, and call a public member
| function, is the invariant still supposed to be true?
he he, good question. Do you have an opinion about this?
| >
| > | -- How do you talk about the "old" value of a variable
| > | in invariants and assertions?
| >
| > via std::old()
|
| What gets saved? The whole object?
yes. it will require copy-constructible objects
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: kuyper@wizard.net (James Kuyper)
Date: Wed, 8 Sep 2004 05:45:07 GMT Raw View
Simon Richter <richtesi@informatik.tu-muenchen.de> wrote in message news:<chis7h$4uk8d$1@sunsystem5.informatik.tu-muenchen.de>...
> Hi,
>
> "Thorsten Ottosen" <nesotto@cs.auc.dk> writes:
..
> >| Allowing a function to violate the invariant until the end of the
> >| function would leave us with an obviously incorrect object,
>
> >this is not obvious.
>
> If the function were allowed to leave the object in an invalid state, we would
Who suggested leaving it in an invalid state? The case under
discussion was when the invariant may be violated "until the end", at
which point it can no longer be violated. In other word, the invariant
must be restore; it might not be restored until the very last
statement in the function executes, but it must be restored. This fits
the quite common case where the first statement in a member function
breaks a class invariant, and the class invariants are not fully
restored until after every statement in the function has been
executed.
> either need a rollback mechanism that rolls back the entire object to the state
> it had before the method call (but that could create inconsistencies with other
> objects) or give back a totally unusable object. If invariants were to be
> checked before each update of the object, it is easy to insert appropriate try
> blocks within the modifying function to implement rollback or error handling if
> desired, while a single check at function exit would not make it possible to
> backtrace the culprit.
The cost of this, of course, is that intermediate steps which violate
the invariant get incorrectly tagged as culprits, despite the fact
that breaking the invariant temporarily is an unavoidable part of the
process of changeing the state while maintaining the invariant.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]