 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Simon Richter Guest
|
Posted: Fri Sep 03, 2004 12:48 am Post subject: C++ wishlist |
|
|
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
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 ]
|
|
| Back to top |
|
 |
Thorsten Ottosen Guest
|
Posted: Fri Sep 03, 2004 10:46 pm Post subject: Re: C++ wishlist |
|
|
"Simon Richter" <richtesi (AT) informatik (DOT) tu-muenchen.de> wrote
| Quote: | 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/
| Quote: | 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.
| Quote: | 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.
| Quote: | thrown (this still needs a lot of thinking to get consistent, but has a lot of
potential IMO).
|
Agreed.
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 ]
|
|
| Back to top |
|
 |
John Nagle Guest
|
Posted: Sat Sep 04, 2004 7:41 am Post subject: Re: C++ wishlist |
|
|
Thorsten Ottosen wrote:
| Quote: | "Simon Richter" <richtesi (AT) informatik (DOT) tu-muenchen.de> 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
|
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 ]
|
|
| Back to top |
|
 |
Simon Richter Guest
|
Posted: Sun Sep 05, 2004 3:56 pm Post subject: Re: C++ wishlist |
|
|
Hi,
John Nagle <nagle (AT) animats (DOT) com> writes:
[Explicit invariants]
| Quote: | -- 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.
}
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: | -- 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 ]
|
|
| Back to top |
|
 |
Bob Bell Guest
|
Posted: Sun Sep 05, 2004 6:52 pm Post subject: Re: C++ wishlist |
|
|
In article <che39n$4ta9f$1 (AT) sunsystem5 (DOT) informatik.tu-muenchen.de>,
Simon Richter <richtesi (AT) informatik (DOT) tu-muenchen.de> wrote:
| Quote: | Hi,
John Nagle <nagle (AT) animats (DOT) 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++.
| Quote: | -- 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?
| Quote: | -- 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 ]
|
|
| Back to top |
|
 |
John Nagle Guest
|
Posted: Mon Sep 06, 2004 2:09 am Post subject: Re: C++ wishlist |
|
|
Simon Richter wrote:
| Quote: | John Nagle <nagle (AT) animats (DOT) 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)
| Quote: | -- What happens when an object calls its own public methods?
|
| Quote: | -- 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.
| Quote: | -- 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.
| Quote: |
-- 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 ]
|
|
| Back to top |
|
 |
Alf P. Steinbach Guest
|
Posted: Mon Sep 06, 2004 6:03 am Post subject: Re: C++ wishlist |
|
|
* Thorsten Ottosen:
| Quote: |
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.
| Quote: | 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... ;-)
| Quote: | 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 ]
|
|
| Back to top |
|
 |
Ben Strasser Guest
|
Posted: Mon Sep 06, 2004 3:22 pm Post subject: Re: C++ wishlist |
|
|
Simon Richter wrote:
Hi
| Quote: |
- 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.
| Quote: | - 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);
}
| Quote: | - "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 ]
|
|
| Back to top |
|
 |
Simon Richter Guest
|
Posted: Mon Sep 06, 2004 3:22 pm Post subject: Re: C++ wishlist |
|
|
Hi,
"Thorsten Ottosen" <nesotto (AT) cs (DOT) auc.dk> writes:
[Explicit invariants]
| Quote: | | 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.
| Quote: | | 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. :-)
| Quote: | | - 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 ]
|
|
| Back to top |
|
 |
Simon Richter Guest
|
Posted: Mon Sep 06, 2004 3:23 pm Post subject: Re: C++ wishlist |
|
|
Hi,
Bob Bell <belvis (AT) pacbell (DOT) net> writes:
| Quote: | Simon Richter <richtesi (AT) informatik (DOT) tu-muenchen.de> wrote:
John Nagle <nagle (AT) animats (DOT) com> writes:
|
[Explicit invariants]
| Quote: | 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.
| Quote: | 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).
| Quote: | 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.
| Quote: | -- 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.
Throws.
| Quote: | }
};
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.
| Quote: | -- 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 ]
|
|
| Back to top |
|
 |
Simon Richter Guest
|
Posted: Mon Sep 06, 2004 3:24 pm Post subject: Re: C++ wishlist |
|
|
Hi,
John Nagle <nagle (AT) animats (DOT) com> writes:
| Quote: | Simon Richter wrote:
John Nagle <nagle (AT) animats (DOT) com> writes:
|
[Explicit invariants]
| Quote: | 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.
| Quote: | which you can actually write as
(valid <= (a == b))
|
Hrm, the "arrow" points in the wrong direction. :-)
| Quote: | 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. :-)
| Quote: | 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 ]
|
|
| Back to top |
|
 |
Bob Bell Guest
|
Posted: Mon Sep 06, 2004 8:31 pm Post subject: Re: C++ wishlist |
|
|
In article <chgctn$4tpdl$1 (AT) sunsystem5 (DOT) informatik.tu-muenchen.de>,
Simon Richter <richtesi (AT) informatik (DOT) tu-muenchen.de> wrote:
| Quote: | Hi,
Bob Bell <belvis (AT) pacbell (DOT) 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.
| Quote: | 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 ]
|
|
| Back to top |
|
 |
Simon Richter Guest
|
Posted: Mon Sep 06, 2004 8:49 pm Post subject: Re: C++ wishlist |
|
|
Hi,
[email]alfps (AT) start (DOT) no[/email] (Alf P. Steinbach) writes:
| Quote: | * 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).
| Quote: | 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.
| Quote: | 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 ]
|
|
| Back to top |
|
 |
Thorsten Ottosen Guest
|
Posted: Mon Sep 06, 2004 8:50 pm Post subject: Re: C++ wishlist |
|
|
"Bob Bell" <belvis (AT) pacbell (DOT) net> wrote
| Quote: | In article <che39n$4ta9f$1 (AT) sunsystem5 (DOT) informatik.tu-muenchen.de>,
Simon Richter <richtesi (AT) informatik (DOT) 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.
| Quote: | 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 ]
|
|
| Back to top |
|
 |
Thorsten Ottosen Guest
|
Posted: Mon Sep 06, 2004 8:51 pm Post subject: Re: C++ wishlist |
|
|
"John Nagle" <nagle (AT) animats (DOT) com> wrote
| Quote: | Thorsten Ottosen wrote:
"Simon Richter" <richtesi (AT) informatik (DOT) tu-muenchen.de> wrote
| 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.
| Quote: | 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.
| Quote: | -- What happens when an object calls its own public methods?
|
a debateable issue, but I think it should be disabled during nested calls.
| Quote: | -- 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.
| Quote: | -- What happens when object A calls object B which calls
object A?
|
the usual rules apply and the invariant is not disabled.
| Quote: | -- What happens when a thread blocks inside an object?
|
the C++ standard does not yet deal with threads, so that would be implementation defined.
| Quote: | -- What about pointers to member functions?
|
when executed, there should be a call to the invariant of the associated object.
| Quote: | -- 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?
| Quote: | -- 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.
| Quote: | -- How do invariants and locking interact?
|
again, the standard does not deal with these issues.
| Quote: | -- How do you talk about the "old" value of a variable
in invariants and assertions?
|
via std::old()
| Quote: | -- 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.
| Quote: | If you address all of these issues properly, the resulting
language won't be C++ any more.
|
what a strange remark.
| Quote: | 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 ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|