The 10 problems are:
1) The "new" keyword.
2) Exceptions
3) Implementations of Multiple Inheritance
4) Member Pointers and Member Function Pointers
5) 0 is NULL
6) Operator Overloading
7) Template Syntax
8) The "export" Keyword
9) Classes verses Structs
10) Barriers
Unfortunately, some of the solutions aren't particularly good. (The
worst one probably is a replacement syntax for the new keyword.)
However, constructive criticism has to start somewhere.
Any comments, or flames are welcome. :-)
Steven
He he, it certainly does. :-)
But kudos (speling?) to you for writing it up.
For that can both help you and help many others.
> complaining about things like exceptions and multiple inheritance.
> However, the actual issues discussed tend to be more subtle and low-
> level than what you are probably thinking. In particular, none of the
> "problems" are complaints about the existence of features, just on
> their current syntax, or typical underlying implementation.
>
> The 10 problems are:
> 1) The "new" keyword.
OK, there's much wrong.
First, "Unfortunately, the new keyword doesn't allow [...] an override." is
wrong. You override the allocation aspect by defining operator new (and/or
operator new[], which is the allocation function, which can be overridden per
class as well as overriding the global default (where, to boot, you can override
the action taken on allocation failure via set_new_handler). And you can
"override" the initialization action by defining suitable constructors.
Second, this is wrong: "A second problem with the new keyword is that it
combines two different operations in one. The first is to allocate memory, the
second is to initialize it. This combination means that initializing an array is
impossible to do with anything other than the default constructor."
'new' is designed to very strongly couple allocation and initialization, with
automatic rollback. It's extremely tricky to do manually, that is, to do it
correctly without language support. If C++ didn't have 'new' to do it, it would
need some other mechanism to do it, which would then be used instead of 'new'.
Also, it's wrong that it's impossible to initialize an array in other ways than
using a default constructor.
For example,
std::vector<int> a( 42, 999 );
Third, this is wrong: "Similarly, destructing an array with the delete[]
operator is inefficient.". delete[] is designed for maximal efficiency. The
trade-offs made to achieve that efficiency, in particular that the programmer
must supply the knowledge of whether it's delete or delete[] (just in order to
save a byte or two of memory!) is a problem, but the efficiency is not. :-)
Now, to your (rhetorical) question, "If all backwards compatibility were thrown
out, how could construction, initialization, and memory allocation be better
done, (paying particular attention to orthogonality)?"
This is addressed in C++0x, which unfortunately seems to be a long way from
ratification, but still.
It's an initialization syntax based on curly parentheses initializers.
> 2) Exceptions
> 3) Implementations of Multiple Inheritance
> 4) Member Pointers and Member Function Pointers
> 5) 0 is NULL
> 6) Operator Overloading
> 7) Template Syntax
> 8) The "export" Keyword
> 9) Classes verses Structs
> 10) Barriers
>
> Unfortunately, some of the solutions aren't particularly good. (The
> worst one probably is a replacement syntax for the new keyword.)
> However, constructive criticism has to start somewhere.
>
> Any comments, or flames are welcome. :-)
I skimmed the section on Exceptions, it is also full of misconceptions. But
those are more arguable, questions more of design than how things work
technically. However, others will probably chime in for the rest (also about
what's wrong with section 2), and I hope my comments on section 1 help.
Cheers,
- Alf
[skip]
> Also, it's wrong that it's impossible to initialize an array in other ways than
> using a default constructor.
>
> For example,
>
> std::vector<int> a( 42, 999 );
I think, he means that 'new' uses default constructor and there is no
way (AFAIK) to use parameterized constructor with 'new'. Something
like this
int *p = new int[3] { 1, 2, 3};
Of course, I don't know why this may need
Maybe if C had a good preprocessor there would be no need for those ugly
templates in C++. And then an average-sized program would not take
infinity to compile at multi-GHz CPU. And you wouldn't get 9999
9997-character lines of unreadable error messages for missing a const
keyword or forgetting to include a header file with yet another overload
of << operator. All pipe dreams I guess..
I remember how Turbo-C and especially Turbo-Pascal used to compile on
5-MHz CPU and can't help crying :-)
How's that for a good flame igniter?
-Pavel
[...]
> > The 10 problems are:
> > 1) The "new" keyword.
> OK, there's much wrong.
> First, "Unfortunately, the new keyword doesn't allow [...] an
> override." is wrong. You override the allocation aspect by
> defining operator new (and/or operator new[], which is the
> allocation function, which can be overridden per class as well
> as overriding the global default (where, to boot, you can
> override the action taken on allocation failure via
> set_new_handler). And you can "override" the initialization
> action by defining suitable constructors.
Not to mention all the things that can be done using placement
new.
> Second, this is wrong: "A second problem with the new keyword
> is that it combines two different operations in one. The first
> is to allocate memory, the second is to initialize it. This
> combination means that initializing an array is impossible to
> do with anything other than the default constructor."
> 'new' is designed to very strongly couple allocation and
> initialization, with automatic rollback. It's extremely tricky
> to do manually, that is, to do it correctly without language
> support. If C++ didn't have 'new' to do it, it would need some
> other mechanism to do it, which would then be used instead of
> 'new'.
FWIW: you can separate the two actions, by calling the operator
new function and using placement new for initialization. New
was introduced, however, precisely because it was felt that the
separation is a bad thing---it means that you can end up with
raw memory, that the compiler thinks is a constructed object.
In all but very exceptional, low level code, this is an error.
> Also, it's wrong that it's impossible to initialize an array
> in other ways than using a default constructor.
That could be considered a default. I'm not sure, but perhaps
the new initialization syntax in C++0x addresses this as well.
> For example,
> std::vector<int> a( 42, 999 );
> Third, this is wrong: "Similarly, destructing an array with
> the delete[] operator is inefficient.". delete[] is designed
> for maximal efficiency. The trade-offs made to achieve that
> efficiency, in particular that the programmer must supply the
> knowledge of whether it's delete or delete[] (just in order to
> save a byte or two of memory!) is a problem, but the
> efficiency is not. :-)
> Now, to your (rhetorical) question, "If all backwards
> compatibility were thrown out, how could construction,
> initialization, and memory allocation be better done, (paying
> particular attention to orthogonality)?"
> This is addressed in C++0x, which unfortunately seems to be a
> long way from ratification, but still.
> It's an initialization syntax based on curly parentheses
> initializers.
Yes, but it doesn't really affect new that much (except for
array new---but I've never found a use for that anyway).
Probably because it wasn't felt that new was broken.
--
James Kanze
I think you have over done it. :)
Ok, I'll take on number 2. :-)
Constructors is just one reason for exceptions. Overloaded operators
is another one. How would you return your failed code from an
operator+()?
Bo Persson
Sorry to spoil the day, but the think reads like a collection of rants
coming from half-educated people collected; and the "possible solutions" are
certainly not there.
I stay with real work made in the field, like
Matthew Wilson
Imperfect C++: Practical Solutions for Real-Life Programming (Paperback)
http://www.amazon.com/Imperfect-Practical-Solutions-Real-Life-Programming/dp/0321228774
I suggest you read it, and discover stuff including the area you tried to
cover...
> The list kind of looks like the typical one brought up by novices,
> complaining about things like exceptions and multiple inheritance.
> However, the actual issues discussed tend to be more subtle and low-
> level than what you are probably thinking.
Indeed :) The road goes pretty far.
I think you've missed the point. In other languages, people are able
to globally override memory allocation. This is done for debugging:
To find a memory leak, you can store the file and line of the
allocation call, and see where the allocations that are never freed
come from. Another use is profiling: What sized allocations are your
program repeatedly allocating and freeing? Perhaps the use of some
other allocator other than the default could improve performance.
With C++, you can use the placement new syntax to pass extra
information to an overridden new routine. The problem is that there
is no way to globally cause this to happen, without manually editing
every single call to new in your program. (You can override
individual classes, no problem... but the only available global
overrides are operator new(size_t) and operator new[](size_t), and
these don't let you access the file and line number you need.)
If new was some sort of operator instead of a keyword, and used
brackets to surround the type, then it would be possible to use a
macro to do this. Unfortunately, this isn't the case.
i.e We'd like to do something like:
#ifdef DEBUG_ALLOCATIONS
#define new(T) new(T, __FILE__, __line__)
#endif
Imagine if new was a template... (convert it from a keyword into
something within the standard library),
and then imagine if templates used normal brackets instead of greater
than and less than signs. These "tiny" changes increase the
orthogonality enough that the above becomes possible. Unfortunately,
macros require normal brackets, which is why both changes are
required, rather than just the first.
> Second, this is wrong: "A second problem with the new keyword is that it
> combines two different operations in one. The first is to allocate memory, the
> second is to initialize it. This combination means that initializing an array is
> impossible to do with anything other than the default constructor."
>
> 'new' is designed to very strongly couple allocation and initialization, with
> automatic rollback. It's extremely tricky to do manually, that is, to do it
> correctly without language support. If C++ didn't have 'new' to do it, it would
> need some other mechanism to do it, which would then be used instead of 'new'.
>
> Also, it's wrong that it's impossible to initialize an array in other ways than
> using a default constructor.
>
> For example,
>
> std::vector<int> a( 42, 999 );
This isn't an array. This is a vector, but I think you know that.
You don't use delete[] with a vector... (Unless you have an array of
vectors. :-P )
Of course, I know this is just a nice way of saying that most people
shouldn't use raw arrays. They are out of date, and vectors should be
used instead. If so, this actually makes the insane non-compatible
change easier. The only code that would need to change is that within
the definition of vector<> itself, which is most likely using arrays
internally for performance reasons.
> Third, this is wrong: "Similarly, destructing an array with the delete[]
> operator is inefficient.". delete[] is designed for maximal efficiency. The
> trade-offs made to achieve that efficiency, in particular that the programmer
> must supply the knowledge of whether it's delete or delete[] (just in order to
> save a byte or two of memory!) is a problem, but the efficiency is not. :-)
The problem with delete[] is that it exists. You use "delete" with
everything else. Why should arrays be special? Special cases are the
very definition of non-orthogonality.
The basic argument is that if you have an array, you probably know its
size. (vector<> certainly does.) If you don't know its size, then
your code probably has a latent buffer overflow problem, and is highly
likely to be buggy elsewhere. Therefore, there should be no problem
passing that size to some deletion routine when you need to
deconstruct the objects in that array. Of course this would totally
break backwards compatibility... but the whole point of exploring
these things is to look at what is possible if we allow that.
Steven
You are right, basically you are stuck. Exceptions are sometimes the
only way to communicate failure conditions in some cases. However,
the problem with exceptions is not that they exist at all... its that
they provide a poorly documented interface to functions. In theory,
all functions should list what potential exceptions they could throw
as part of their definition / external documented interface. In
practice, this rarely happens. The reason is that far too many
exceptions are possible in any non-trivial code. The combinatorial
explosion would lead to the poor programmer having to do much extra
typing. Thus the argument leads to: we can't get rid of them... but
can we minimize their use? The answer to that is a probable "yes".
If their use is minimized, then the extra typing is minimized... and
enforcing their documentation isn't quite so much of a burden.
In short, wouldn't it be cool if the compiler could guarantee that
your code was exception safe? To do so, it simply needs more
information than what currently is usually provided.
Arrays ARE special! We got that from the C language.
In C++ this is solved by std::vector, which never ever leaks memory.
What is there left to dbug?
Bo Persson
With C++, you can use the placement new syntax to pass extra
information to an overridden new routine. The problem is that there
is no way to globally cause this to happen, without manually editing
every single call to new in your program.
<<
TMK there is a bunch of leak detectors out there.
But I never used any of them -- Visual C++ has it built-in for decades. And
all it takes a single #define new debug_new at the front of the source. If
you use it, then you get the source file/line of allocation besides the
leaked block address, size and content.
The last time I had to put together a ehap diagnostic was around '94, even
then I was possibly just not aware of a ready solution...
Manually editing lines of code?
What you ask for, is sitteng there, working, discover how it is done instead
of claiming it impossible.
OTOH, the real way to make correct code is definitely not going by that info
but through using consistent RAII-like handling, and reviews enforcing it.
As test runs will hardly cover all possible paths including errors and
exceptions, so relying on the empty leak list from a random run is nothing
but illusion of being okay. While with trivial style it is easy to make
leaks impossible.
Why invest in better patches instead of cure the problem at roots?
(You can override
individual classes, no problem... but the only available global
overrides are operator new(size_t) and operator new[](size_t), and
these don't let you access the file and line number you need.)
If new was some sort of operator instead of a keyword, and used
brackets to surround the type, then it would be possible to use a
macro to do this. Unfortunately, this isn't the case.
i.e We'd like to do something like:
#ifdef DEBUG_ALLOCATIONS
#define new(T) new(T, __FILE__, __line__)
#endif
Imagine if new was a template... (convert it from a keyword into
something within the standard library),
and then imagine if templates used normal brackets instead of greater
than and less than signs. These "tiny" changes increase the
orthogonality enough that the above becomes possible. Unfortunately,
macros require normal brackets, which is why both changes are
required, rather than just the first.
...
>You don't use delete[] with a vector... (Unless you have an array of
vectors. :-P )
You should not use delete[] at all, ever. Sending all the related problems
to limbo. (this one is even easy to "review" by grep...)
>Of course, I know this is just a nice way of saying that most people
shouldn't use raw arrays. They are out of date, and vectors should be
used instead.
vectors, or other fit collections -- they are easy to find ready to use. And
even writing one is simpler than dealing with the related problems. I'm
sure there is no use case where built-in array would beat them with one
dimension. With multi dim see the relevant chapter in Wilson.
>The problem with delete[] is that it exists.
So eradicate it for good. How much of real problem is THAT really?
Especially compared to the can of worms those raw arrays bring, especially
paired with dynamic allocation...
You can just use a macro.
Check out e.g. Microsoft's MFC.
Ugh, I never thought I'd direct *anyone* to MFC, but it does this. There was by
the way an infamous bug related to that macro. They'd originally forgotten to
define placement delete, thus leaking memory when exceptions occurred in debug
builds...
> (You can override
> individual classes, no problem... but the only available global
> overrides are operator new(size_t) and operator new[](size_t), and
> these don't let you access the file and line number you need.)
>
> If new was some sort of operator instead of a keyword, and used
> brackets to surround the type, then it would be possible to use a
> macro to do this. Unfortunately, this isn't the case.
>
> i.e We'd like to do something like:
> #ifdef DEBUG_ALLOCATIONS
> #define new(T) new(T, __FILE__, __line__)
> #endif
You're homing in on one of the solutions, yes. :-)
[snip]
>> Also, it's wrong that it's impossible to initialize an array in other ways than
>> using a default constructor.
>>
>> For example,
>>
>> std::vector<int> a( 42, 999 );
>
> This isn't an array. This is a vector, but I think you know that.
> You don't use delete[] with a vector... (Unless you have an array of
> vectors. :-P )
The example just shows that you can construct elements of an array any way you
wish. There's nothing magical about std::vector. It's just a class that you can
define yourself.
[snip]
>> Third, this is wrong: "Similarly, destructing an array with the delete[]
>> operator is inefficient.". delete[] is designed for maximal efficiency. The
>> trade-offs made to achieve that efficiency, in particular that the programmer
>> must supply the knowledge of whether it's delete or delete[] (just in order to
>> save a byte or two of memory!) is a problem, but the efficiency is not. :-)
>
> The problem with delete[] is that it exists. You use "delete" with
> everything else. Why should arrays be special? Special cases are the
> very definition of non-orthogonality.
That's the "byte or two" I mentioned in the quote.
> The basic argument is that if you have an array, you probably know its
> size. (vector<> certainly does.) If you don't know its size, then
> your code probably has a latent buffer overflow problem, and is highly
> likely to be buggy elsewhere. Therefore, there should be no problem
> passing that size to some deletion routine when you need to
> deconstruct the objects in that array. Of course this would totally
> break backwards compatibility... but the whole point of exploring
> these things is to look at what is possible if we allow that.
Think about a zero-terminated string, and functions like
char const* dup( char const* s )
{
return strcpy( new char[strlen(s)+1], s );
}
That's about "knowing the size", often, as here, you don't even know the size
specified at allocation, but would have to obtain it by e.g. using strlen.
Cheers & hth.,
- Alf
Microsoft solution is not so perfect. It causes some problems - it is not
possible to use custom placement new in the source without #undef new
first, and when one wants to add one's own per-class operator new, one
has to carefully support all the placement new and delete versions the
Microsoft has come up with (and never documented properly - the MSDN page
for DEBUG_NEW just claims that it is enough to #define new DEBUG_NEW and
everything will work magically).
Yes, the root problem is that the 'new' syntax does not align well with
the preprocessor syntax. But maybe it's the preprocessor syntax which
should be facelifted? Or maybe the reason for using the preprocessor in
the first place should be eliminated, by adding official facilities to
C++ to walk the call stack and find out the file names and line numbers?
In some cases the knowledge of just the single file and line containing
'new' does not buy much anyway
Cheers
Paavo
This is next to impossible for templates, and a maintenance nightmare for
ordinary functions (I guess this is what you mean by combinatorial
explosion).
> In
> practice, this rarely happens. The reason is that far too many
> exceptions are possible in any non-trivial code. The combinatorial
> explosion would lead to the poor programmer having to do much extra
> typing. Thus the argument leads to: we can't get rid of them... but
> can we minimize their use? The answer to that is a probable "yes".
> If their use is minimized, then the extra typing is minimized... and
> enforcing their documentation isn't quite so much of a burden.
For reducing the combinatorial explosion, all exceptions propagating out
of a library should be derived from std::exception.
In my experience, most exceptions finally get logged as a text, or
displayed to the end user as a text. So what I need is just to convert
every exception to text. This can be done by a single function, which is
called from all catch(...) clauses (... is verbatim here!). The function
rethrows and catches the exception, converting it into the text
appropriately for all kind of known exception types (language translation
can be done at this point as well).
Having special exception types only comes handy when they are used for
handling special situations near the throw point. But this should not be
visible to other libraries and should not cause combinatorial explosion.
>
> In short, wouldn't it be cool if the compiler could guarantee that
> your code was exception safe? To do so, it simply needs more
> information than what currently is usually provided.
I guess you have another meaning of exception safety than me. For me,
exception safety is achieved when I open a registry key or clipboard in
the function and use ScopeGuard to close it in any case when exiting the
function. Yes it would be cool if compiler warned me when I forgot the
ScopeGuard. Sadly, today it warns me only about the opposite - when I use
the ScopeGuard :-( (unused variable ScopeGuard332 defined on line
332...).
Cheers
Paavo
That's the wrong question to ask though because it pre-supposes exceptions.
The question is, "what are the alternatives?".
1. Don't write code that errors in constructors or operators.
2. Do default error handling instead of requiring propogation via
exceptions: exit(), perhaps.
3. Use another error handling technique.
4. ?
The argument for exceptions goes something like this: Some people want to
write code where errors can happen in constructors and operators, therefore
exceptions were created. Because exceptions were created, why not just go
ahead and use them all over the place.
It's a classic case of the exceptional case (no pun intended, but a little
humourous anyway) botching up the common case.
>
> The argument for exceptions goes something like this: Some people want
> to write code where errors can happen in constructors and operators,
> therefore exceptions were created. Because exceptions were created,
> why not just go ahead and use them all over the place.
>
> It's a classic case of the exceptional case (no pun intended, but a
> little humourous anyway) botching up the common case.
In my mind, this is the other way around. The benefits of exceptions appear
in large applications, where the error condition might be handled 10 or 30
call stack frames up. C++ is one of the few languages suitable for
developing such large applications, and the exceptions mechanism is one of
the supporting points here. The constructors or operators have little to do
with it. Yes, exceptions come handy when reporting errors from these
constructs, but this could be solved otherwise as well, as you just argued.
You cannot feasible solve error reporting 10 call frames up without
exceptions, possibly through different libraries, without turning all the
source code into a mess. Take a look e.g. in the ImageMagick code base (C
language!) to see the mess I'm talking about.
Cheers
Paavo
From the article:
"With explicit construction, the zero-argument count, and no-return-value
requirements for a constructor evaporate."
What about compiler-generated temporaries created via a constructor? You
pre-supposed only one pattern of construction: that in which the developer
is writing code. There is not just one pattern of construction to address.
From the article:
"As currently implemented, the export keyword cannot speed up compile time.
The repeated work that we try so hard to remove is simply deferred until
link time. This is a disaster, and removes the entire point of having this
feature."
I thought the gist of "export" was to give the ability to hide template
implementation source code. I never thought it's purpose was at all tied to
performance.
In the section titled "Implementation of Multiple Inheritence", you just
regurgitated well-known information that can be found in textbooks. The
solution to multiple inheritence is to not do it or only allow a restricted
form of it. Then all that thunking and offsetting simply goes away.
From the article: "Why not use this difference between plain-old-data and
more complex objects to define the difference between structs and classes?"
Bjarne and his peers wrestled with that (and probably argued about it a lot
too) when C++ was first designed. Bjarne notes in his book "The Design and
Evolution of C++" (which you should read or read again because you seem to
be oblivious to what was written in that tome) that he thought if he would
have created such a distinction (that is, ANY significant distinction)
between structs and classes that C++ would have been "stillborn". We'll
never know what would have been had structs and classes been fundamentally
different, but the reason why structs and classes are defined as they are is
well-documented.
Passages from the article are enclosed in quotation marks below).
"So the C++ language has quite a few problems."
You say that so matter-of-factly and as if you were trying to persuade
others, perhaps "novices" that the C++ designers "missed something" or
didn't think it over enough before making their design choices or that they
were wrong choices. While a tiny bit of that surely exists, C++ is what it
is for consideration of the "bigger-picture" in which it exists, rather than
in comparison of an apple to an orange. A lot of the "baggage" that C++
carries is BY DESIGN: mostly for C compatibility and compatibility with
legacy C++ code.
"However, they all seem fixable if one were willing to break source and ABI
backwards compatibility."
That would be not in the spirit of C++ and would be instead a new language.
The language D has that charter, I believe. You may want to check it out.
"Hopefully, this has provided constructive criticism, and enough detail in
the solutions proposed to be useful."
While some low-level details you spoke about in your article are news to me
(I program at a much higher level but find information about the
underpinnings an interesting hobby), I highly doubt that in the larger scope
of this newsgroup that you've noted anything that hasn't been analysed a
thousand times before by thousands of other developers and that the C++
committee members don't discuss on a daily basis. (No offense meant).
> You cannot feasible solve error reporting 10 call frames up without
> exceptions, possibly through different libraries, without turning all the
> source code into a mess.
Quite easily actually. An example is the new operator's set_new_handler.
I don't have privy to those "large applications" that everyone keeps talking
about, but if I did I'd probably "refactor" them so they wouldn't be so
unwieldly.
OTOH, if you are talking about project issues and the larger scope of
cross-component (by different providers) interoperability, that's a
different argument. Probably one of a pipe dream though since a C++ eco
system to that level isn't going to happen except maybe as a niche. I'd be
interested to hear about some of these large projects that used libraries
from multiple vendors to build large scale applications and how it went. To
me, I would think it would be just making an integration mess since coding
styles with C++ vary so much. And what about duplication? One vendor
considers the standard string to be less than useful so it creates a
proprietary one for use specifically for use with it's library, and then the
next vendor B has quirk X and on and on.
Your captcha at the web does not work, so I have to put here my comment:
I may sound critical, but it is just my inability to talk properly. I
can understand why you wanted to share your thoughts and I understand
that you have placed a great deal of concern and work into this article.
However, it is full of bull byproduct. ;) It shows clearly that there
is a lot you don't know about C++. You have serious misunderstandings.
I don't know if the blame falls on bad teachers or bad books, or what,
but your misconceptions and misunderstandings run deep and seem to be
fundamental. They remind of some colleagues from a decade ago, who have
learnt C++ from a Java guy; so all they knew about C++ were
misconceptions and a fundamental hate and distrust towards the language. :)
Fixing your language knowledge issue is simple. Get a good book. I
suggest Bjarne's new book.
But there is another problem. And that is an attitude problem. You
make wild assumptions and then you act on them as if they were true,
without checking their validity. That is a very dangerous, I would say
suicidal way of working for a programmer. I have seen one software that
was 2 times slower because the head architect assumed that dynamic
memory allocation is expensive and he has forced people to statically
allocate all memory. He did not check his assumptions. And they were
wrong.
So my suggestion to you is to be aware of your assumptions and check
them before you act on them. I have only looked at the first 3 points,
because I saw a pattern and frankly I have decided that if the quality
of your arguments is the same as in the first 3 point, I don't want to
frustrate myself with reading the rest. But even in those first 3
points you will see the examples where you have had a wrong assumption
and instead of checking if the assumption is correct, or asking for
advice on USENET about your way of thinking, you have acted upon your
assumptions. Why is that bad? Well, for one, posting the link to this
article on the USENET triggered two things:
1.) Experienced people are frustrated, concerned or laugh about your
article. Some, like I, may even go into damage control mode, because of
reason 2
2.) Unexperienced people won't be able to see that your assumptions are
wrong, because you state your wrong assumptions as facts. So they will
accept what you say. And that will cause them harm, because later in
life they will have to spend serious time and effort to unlearn all the
bad things they have read here.
*** The complaints in item #1 are based on 3 serious misconceptions and
some more:
1.) operator new cannot be replaced. It can. See your favorite C++
textbook.
2.) The new operator combines 2 operations: allocation and
initialization. Clearly a misconception. The new operator *only*
allocates memory. It does not combine anything else. Constructors do
the initialization.
3.) Array allocation is inefficient. It is not. It is more efficient
than any hand crafted code any of us could ever write.
Your problem seems to be here that you want to work in C++ as you would
work in C or assembler. There is no need for you to fiddle around with
allocating arrays and with placement new. Use a vector and initialize
it from a pair of input iterators. You get the most effective code
possible.
*** The complaints in item #2 are based on 2 serious misconceptions and
some more:
1.) Exceptions are means to decouple the site of error detection and
error handling. They have nothing to do with goto. They are not jumps.
They are based on sound principles that does not break or even bend
the rules of structuring programming.
2.) Exceptions have not been created because constructors cannot return
values. Serious misconception. They had to be added into the language,
because of it. But the motivation for the design and existence of
exceptions has nothing to do with constructors. It has to do with the
desire to make errors harder to hide (error codes can be ignored without
consequences); the need to make code between the detection site and the
handling site be "untouched" by the fact that an error may occur
(error/return codes have to be delegated by all functions on the call
stack); and the needs to fully separate the detecting code and the handler.
Your example with the class with the crazy error code reference argument
is as bad as it could be. If your constructor exists normally, while
not creating the object properly, you are living in
undefined-behavior-land. You have a variable with the type
Transmogrifier, but it is *not* a transmogrifier! You ignore the error
code, or you do not return from your function or exit the block where
the variable is living... anyone later may start using that object as if
it were properly constructed. The compilers won't and cannot stop you
from doing it (in any language I know). But that object is not
constructed properly, so your code will do stupid things.
*** The complaints in item #3 are based on at least one serious
misconception:
You think that compilers are dumb. That they will do 2 additions when
one suffices. They don't. That extra addition is removed at compile
time. If a compiler is requested to access an element of an object (any
element) it will use a compound access with a base register and an
offset. ALWAYS. Compilers are not stupid. The expression to access an
element in case of multiple inheritance is something like this:
address[base_adjustment+element_offset]
where both base_adjustment and element_offset are compile time
constants! So in the final binary/machine code you will see:
address[base_adjusted_element_offset]
No speed loss whatsoever.
BR, WW
> 2.) The new operator combines 2 operations: allocation and initialization.
> Clearly a misconception. The new operator *only* allocates memory. It
> does not combine anything else. Constructors do the initialization.
This one is actually true. The "new operator" combines several things:
- call "operator new" (not to confuse!)
- initialize calling ctor
- call part destructors and "operator delete" in case exception escapes the
ctor call
The misconception lies in the claim it is a bad thing. :-o
new operator example:
int * pInt = new int(42);
operator new example:
void* operator new(size_t s) { /* return pointer to properly allocated
memory */ }
I assumed he is talking about operator new, since he wanted to replace
it, and his motivating example was about about replacing malloc. And
its equivalent is not the new operator, but operator new.
So I have assumed that the guy is simply confusing things and using the
wrong terminology, as he does in the rest of his article.
--
BR, WW
> 2.) The new operator combines 2 operations: allocation and
> initialization. Clearly a misconception. The new operator *only*
> allocates memory. It does not combine anything else. Constructors do
> the initialization.
Operator new allocates memory and calls a constructor. It does do 2 things.
Placement new does one thing: it calls a constructor.
> 1.) Exceptions are means to decouple the site of error detection and
> error handling. They have nothing to do with goto. They are not
> jumps. They are based on sound principles that does not break or
> even bend the rules of structuring programming.
I believe he said that they can be like a goto in what they CAN turn code
into: spaghetti.
>
> 2.) Exceptions have not been created because constructors cannot
> return values. Serious misconception. They had to be added into the
> language, because of it. But the motivation for the design and
> existence of exceptions has nothing to do with constructors. It has
> to do with the desire to make errors harder to hide (error codes can
> be ignored without consequences); the need to make code between the
> detection site and the handling site be "untouched" by the fact that
> an error may occur (error/return codes have to be delegated by all
> functions on the call stack); and the needs to fully separate the
> detecting code and the handler.
Why does it have to be one or the other extreme? Exceptions are just one of
many ways to handle errors. Use what is most appropriate.
Sorry, but no. The global new operator, when it is called, does the two
things you talk about. Operator new does only one thing. Sorry, no
bonus for you in knowing the terminology.
>> 1.) Exceptions are means to decouple the site of error detection and
>> error handling. They have nothing to do with goto. They are not
>> jumps. They are based on sound principles that does not break or
>> even bend the rules of structuring programming.
>
> I believe he said that they can be like a goto in what they CAN turn code
> into: spaghetti.
And I believe I have described clearly why there aren't at all like
goto, and instead of coupling things (which turns code into spaghetti)
exceptions decouple things (which turn code into ravioli).
>> 2.) Exceptions have not been created because constructors cannot
>> return values. Serious misconception. They had to be added into the
>> language, because of it. But the motivation for the design and
>> existence of exceptions has nothing to do with constructors. It has
>> to do with the desire to make errors harder to hide (error codes can
>> be ignored without consequences); the need to make code between the
>> detection site and the handling site be "untouched" by the fact that
>> an error may occur (error/return codes have to be delegated by all
>> functions on the call stack); and the needs to fully separate the
>> detecting code and the handler.
>
> Why does it have to be one or the other extreme? Exceptions are just one of
> many ways to handle errors. Use what is most appropriate.
Where did I say it has to be one or the other extreme?
--
BR, WW
No "bonus" for you playing (English) language lawyer instead of recognizing
the context in which 'new' was being discussed. :P
>
>>> 1.) Exceptions are means to decouple the site of error detection and
>>> error handling. They have nothing to do with goto. They are not
>>> jumps. They are based on sound principles that does not break or
>>> even bend the rules of structuring programming.
>>
>> I believe he said that they can be like a goto in what they CAN turn
>> code into: spaghetti.
>
> And I believe I have described clearly why there aren't at all like
> goto, and instead of coupling things (which turns code into spaghetti)
> exceptions decouple things (which turn code into ravioli).
Bad is bad, period. You seem to have agenda (a childish one).
>
>>> 2.) Exceptions have not been created because constructors cannot
>>> return values. Serious misconception. They had to be added into
>>> the language, because of it. But the motivation for the design and
>>> existence of exceptions has nothing to do with constructors. It has
>>> to do with the desire to make errors harder to hide (error codes can
>>> be ignored without consequences); the need to make code between the
>>> detection site and the handling site be "untouched" by the fact that
>>> an error may occur (error/return codes have to be delegated by all
>>> functions on the call stack); and the needs to fully separate the
>>> detecting code and the handler.
>>
>> Why does it have to be one or the other extreme? Exceptions are just
>> one of many ways to handle errors. Use what is most appropriate.
>
> Where did I say it has to be one or the other extreme?
I didn't say "you" specifically.
I have clarified your confused use of C++ (and not English) terminology.
This was supposed to be helpful. The terms "operator new" and "the
new operator" (there is only one, conceptual thing) are not the same
thing. They have two distinct definition in C++.
>>>> 1.) Exceptions are means to decouple the site of error detection and
>>>> error handling. They have nothing to do with goto. They are not
>>>> jumps. They are based on sound principles that does not break or
>>>> even bend the rules of structuring programming.
>>> I believe he said that they can be like a goto in what they CAN turn
>>> code into: spaghetti.
>> And I believe I have described clearly why there aren't at all like
>> goto, and instead of coupling things (which turns code into spaghetti)
>> exceptions decouple things (which turn code into ravioli).
>
> Bad is bad, period. You seem to have agenda (a childish one).
Oh my. Which one is childish? Using actual arguments to prove a point
(what I did) or accusations and personal insults (what you did)?
>>>> 2.) Exceptions have not been created because constructors cannot
>>>> return values. Serious misconception. They had to be added into
>>>> the language, because of it. But the motivation for the design and
>>>> existence of exceptions has nothing to do with constructors. It has
>>>> to do with the desire to make errors harder to hide (error codes can
>>>> be ignored without consequences); the need to make code between the
>>>> detection site and the handling site be "untouched" by the fact that
>>>> an error may occur (error/return codes have to be delegated by all
>>>> functions on the call stack); and the needs to fully separate the
>>>> detecting code and the handler.
>>> Why does it have to be one or the other extreme? Exceptions are just
>>> one of many ways to handle errors. Use what is most appropriate.
>> Where did I say it has to be one or the other extreme?
>
> I didn't say "you" specifically.
Nice of you. Since the post was addressed to me, I wrongly assumed that
all comments were about what I wrote, especially since it has followed a
quote of mine, talking about the same topic.
--
BR, WW
I know that Mr. Insinuator. Get some intuitiveness already. :P
>
>>>>> 1.) Exceptions are means to decouple the site of error detection
>>>>> and error handling. They have nothing to do with goto. They are
>>>>> not jumps. They are based on sound principles that does not
>>>>> break or even bend the rules of structuring programming.
>>>> I believe he said that they can be like a goto in what they CAN
>>>> turn code into: spaghetti.
>>> And I believe I have described clearly why there aren't at all like
>>> goto, and instead of coupling things (which turns code into
>>> spaghetti) exceptions decouple things (which turn code into
>>> ravioli).
>>
>> Bad is bad, period. You seem to have agenda (a childish one).
>
> Oh my. Which one is childish? Using actual arguments to prove a
> point (what I did) or accusations and personal insults (what you did)?
I don't accuse or insult. You Mr., need to introspect.
>
>>>>> 2.) Exceptions have not been created because constructors cannot
>>>>> return values. Serious misconception. They had to be added into
>>>>> the language, because of it. But the motivation for the design
>>>>> and existence of exceptions has nothing to do with constructors. It
>>>>> has to do with the desire to make errors harder to hide (error
>>>>> codes can be ignored without consequences); the need to make code
>>>>> between the detection site and the handling site be "untouched"
>>>>> by the fact that an error may occur (error/return codes have to
>>>>> be delegated by all functions on the call stack); and the needs
>>>>> to fully separate the detecting code and the handler.
>>>> Why does it have to be one or the other extreme? Exceptions are
>>>> just one of many ways to handle errors. Use what is most
>>>> appropriate.
>>> Where did I say it has to be one or the other extreme?
>>
>> I didn't say "you" specifically.
>
> Nice of you. Since the post was addressed to me, I wrongly assumed
> that all comments were about what I wrote, especially since it has
> followed a quote of mine, talking about the same topic.
You seem keen on exploiting nits in attempt to create false impressions.
Children do that. That's what grown-ups know though, so it just confirms to
them that they are doing the proverbial "arguing with a child" thing. So, if
you want to stop appearing childish, grow up!
:P
Just a clarification. The OP said exceptions are bad, because they are
like goto. I have provided an argument to show why exceptions are not
like goto and why they will not turn the code into spaghetti the way
goto does. So I have proven the argument that they are like goto, and
they will turn your code into a spaghetti is not true. Therefore the
original assumption that they are bad does not hold.
After that, your answer is above...
I have no idea why you think that this is some kind of pissing contest.
I come here to talk about C++ and to help people to understand it, and
to help them to discover their mistakes in thinking, or in understanding
- for their benefit. This is not about me being right or not. Most of
the time we are talking about scientific facts. Things that can be
proven. With logic. Vulcan style. Emotions suppressed.
Exceptions and and goto are nothing alike. Those attributes of goto
that make goto easy to misuse are not present with exceptions:
- goto can jump anywhere in the code as long as it does not jump over
the initialization of a variable
- goto creates a new control flow that is independent of anything
"normal" (without goto)
- goto preserves states, and it is very hard to tell where those states
come from
- therefore the code before the goto and after the goto may
"communicate" with each other in unforeseen ways
Exceptions are quite different:
- exceptions cannot go just anywhere in the code
- exceptions may abort the code (no handler) or "track back" on the same
way the code came to the throw site
- so exceptions do not create a completely new control flow, they just
automate one that would exist otherwise: the many ifs checking return
codes and returning to the caller if there is a failure
- exceptions are just an automated way of creating the control flow that
would exist anyway to handle errors
- exceptions do not preserve state, they destroy state created in all
blocks that they exit
- of course, side effects of those functions won't seize to exist unless
we take explicit actions (in destructors implementing a transactional
behavior) but we would have to make those also if we used return codes
- the only way exceptions communicate with the other end of the "jump"
is the object being thrown,and that object is like the return code we
would have to return anyway
I don't know if you can follow my reasoning, I hope I have managed to
explain what I mean.
Please believe me, I am not writing to the USENET so that I can start
arguments with people. I am writing so that I can share what I have
learnt, and hopefully provide some valuable insight for those who read.
--
BR, WW
As for insults, please save some bandwidth and disk space for the future
generations, and do not post them here. Let's agree: For every post I
read from you addressed to me, I will implicitly assume that you have
said that I am a childish jerk. You have repeated that assertion of
yours enough times that your message is clear. There is no need to keep
repeating.
In that way we can save a lot of typing for you, and also keep the
discussion on the technical topics. That would be the reason for this
forum anyway.
--
BR, WW
I wouldn't do that. Ask anyone who knows me.
> If it makes you happy, call me childish.
It'll be OK. Get some sleep.
> OTOH I would be more interested in adult conversation.
You have time for that later. Enjoy your youth.
> So if you happen to
> find a technical inaccuracy in what I say or have said, please let me
> know.
Oh ho, oh OK. I will do that. (hehehe).
>
> As for insults, please save some bandwidth and disk space for the
> future generations, and do not post them here.
Your current tactic: redirect away from the issue at hand. "Nice try".
> Let's agree: For
> every post I read from you addressed to me, I will implicitly assume
> that you have said that I am a childish jerk.
Is my teaching helping you understand? You seem more keen on testing your
tactics than learning and accepting.
> You have repeated that assertion of
> yours enough times that your message is clear. There is no need to
> keep repeating.
OK, quiz time to see if you have learned anything. Please write down what my
purpose is in entertaining and "exposing" your childish tactics. Also,
please go back in this thread and write down what the meaning is of every
word I enclosed in quotation marks and how the context would differ had I
not used the quotation marks (single words only, as quoted passages are
distinctly different). Use as much space as you wish and take as much time
as you need.
(OK, you don't have to write anything: just THINK about it and think about
how you are perceived by others).
>
> In that way we can save a lot of typing for you, and also keep the
> discussion on the technical topics. That would be the reason for this
> forum anyway.
"the bottom line is...", is a cliche with intent to summarize that which one
cannot summarize but which one feels the need to "sum up" anyway. You
homework is to THINK!
Oh brother. At this rate, you'll be reposting the entire Wikipedia in short
order.
--
BR, WW
Avoiding the issue will not help you. And no amount of "smoke and mirrors"
tactics can change the past. Save the posts for a time when you are more
secure if reality is too much for you right now. I assure you that you will
chuckle then. :)
> Maybe if C had a good preprocessor there would be no need for those ugly
> templates in C++. And then an average-sized program would not take
> infinity to compile at multi-GHz CPU. And you wouldn't get 9999
> 9997-character lines of unreadable error messages for missing a const
> keyword or forgetting to include a header file with yet another overload
> of << operator. All pipe dreams I guess..
>
> I remember how Turbo-C and especially Turbo-Pascal used to compile on
> 5-MHz CPU and can't help crying :-)
>
> How's that for a good flame igniter?
As for flames, I'm pretty sure I saw Turbo Pascal actually cause at
least two machines burst into flames it was so fast.
I remember it took me two days to be convinced it wasn't a joke and it
really was that fast. At the time we were timesharing several machines
but mostly an HP mini and until TP I actually thought the HP wasn't
too bad.
He he, I see a little flame war erupted over that. :-)
A good way to talk about this is IMO "new expression" versus "operator new" or
"new operator". The operator is an allocation function only (whether it's per
class or global). The expression, involving the new keyword, calls the
allocation function with the supplied allocation function arguments, and then,
if that succeeds, calls the constructor with the supplied constructor arguments.
"Placement new" was probably originally referring to the ability to specify some
existing storage, a placement of the object, but in the standard's terminology
"placement" refers to any allocation function with extra arguments, or new
expression supplying such arguments.
Technically a placement new expression does the same as a non-placement new
expression.
Also, there's no big difference for a global new operator.
Cheers,
- Alf
And what will be the cost of these additions/removals?
And what would be the cost of dispatching different errors if all are to
be processed after the same code point (something that you achieve with
several catch blocks when using the exception)?
And what will it cost you to program stopping normal flow, destroying
automatic objects and actually arriving to the desired error-processing
point in code?
Long story short, for a significant share of tasks set_new_handler
approach is certainly not a solution, not even good enough for operator
new itself (that's why std::bad_alloc was added).
-Pavel
OK, I mis-comprehended your thought, probably because you used "10" instead
of "3".
> Of course you can create a class for every
> overloaded + operator that can overflow, create a method
> "onOverflow()" in it etc. but what level of cooperation would it
> require from the client code to add and remove interested parties?
I don't have the data/information across a wide variety of software to
assess how important that is, BUT, I feel it's probably "a detail".
"Operator overloading is for numerics", to say a cliche.
>
> And what will be the cost of these additions/removals?
>
> And what would be the cost of dispatching different errors if all are
> to be processed after the same code point (something that you achieve
> with several catch blocks when using the exception)?
Error handling code is usually not performance-critical, so it doesn't
matter.
>
> And what will it cost you to program stopping normal flow, destroying
> automatic objects and actually arriving to the desired
> error-processing point in code?
Performance-wise? Who cares? Mechanics-wise? A little bit of code that may
indeed help to focus on doing error handling rather than dispatching errors.
>
> Long story short, for a significant share of tasks set_new_handler
> approach is certainly not a solution, not even good enough for
> operator new itself (that's why std::bad_alloc was added).
>
If by "solution" you mean "one size fits all", there is no answer. Not
without concession anyway.
If you like exceptions, use them. I prefer to avoid them when/wherever
possible. I don't do large-scale development and I have a toolbox full of
error-handling techniques. I never understood this quest for "one and only
one" when that is hardly ever appropriate: one error-management mechanism,
one "standard" library, one <your choice here>...
Locally handle-able conditions (errors) are much more numerous than global
errors handle-able appropriately by a technique such as set_new_handler.
There has to be a number of approaches to error handling or else it is a
lame solution to the problem of error management, so I say. C++ exceptions
can replace those set_new_handler-like things. Sure, you can use them at the
local level too, but then with that comes all the confusion of when to just
crutch on the mechanics or think about the problem and handle the error, and
it's the whole "exceptional case botching up the common case" thing again.
The real sad thing about C++ exceptions is that it suppresses thought about
error handling in general and the science of error handling almost
stagnates, in C++ groups anyway.
No it didn't. He was trying to exploit colloquial usage of "new operator"
even though it was very clear what was being discussed. I called him out on
his childish tactics and agenda. It was a general discussion about
allocation, inititalization and construction (or was about to be) until the
child saw an avenue to exploit.
>
> A good way to talk about this is IMO "new expression" versus
> "operator new" or "new operator".
It was immaterial, for in the context, it was clear what was being
discussed.
> "Placement new"
is manually calling a constructor. It is "CONSTRUCTION" (a bad choice of
terminology, but so it is in C++).
It sounds as if you degree with my description. In that case, sorry but whatever
you mean is incorrect. Placement new does exactly the same as non-placement new:
calling an allocation function with specified allocation function arguments, and
if that succeeds, calling a constructor with specified constructor arguments.
For example,
#include <stdio.h>
struct S
{
void* operator new( size_t const size, char const* const blah )
{
printf( "allocation function called, arg = \"%s\"\n", blah );
return ::operator new( size );
}
S( int const whatever )
{
printf( "constructor called, arg = %d\n", whatever );
}
};
int main()
{
S* const p = new( "Get it?" ) S( 42 );
}
>> was probably originally referring to the ability to
>> specify some existing storage, a placement of the object, but in the
>> standard's terminology "placement" refers to any allocation function
>> with extra arguments, or new expression supplying such arguments.
>>
>> Technically a placement new expression does the same as a
>> non-placement new expression.
>>
>> Also, there's no big difference for a global new operator.
Cheers & hth.,
- Alf
Obviously (but too, not afraid to "be stupid")
> In that case, sorry
> but whatever you mean is incorrect.
Sure it is, but you had to say that BEFORE you proved it, huh.
> Placement new does exactly the
> same as non-placement new: calling an allocation function
In practice (MY practice), it does no allocation. Don't bore me with your
laws please Mr.
Go find another corner to place a camera on to control your offspring.
> with
> specified allocation function arguments, and if that succeeds,
> calling a constructor with specified constructor arguments.
Maybe I don't care? Meaning, maybe I don't care to recognize that subtlety?
> Cheers & hth.,
>
> - Alf
Siglines are so yesterday. Stop wasting bandwidth for self-serving purposes.
I don't trust anyone with a sigline.
plink
doh... forgot about those.
Ok - there are three types of temporaries. (Correct me if I'm wrong.)
The first are implicit conversion constructors, and the second are
copy constructors used for things like function arguments.
Implicit conversion is seen as problematic these days. (It breaks the
type-safety) So it probably can be depreciated in favor of explicit
conversion instead.
Function arguments are a different story. I suppose one way to remove
implicit copy constructors is to change the default to pass-by-
reference for classes. This is probably a bit too much of a change,
however. Perhaps a nicer solution exists. :-)
Finally, you get temporaries in expressions involving operator
overloading. I'll ignore those... since you are going to have to wrap
the expression in a catch statement anyway if the type is so complex
that its constructor could throw an exception.
Steven
Bjarne had different constraints than the ones I was working with.
Bjarne required C backwards compatibility, whereas I am explicitly
dropping all backwards compatibility requirements. Bjarne also wanted
people to use his new language. I am under no delusions, and agree
that the chances of anyone making the suggested changes are extremely
small to non-existent. :-)
The key problem is that C++ is too complex. However, there are
reasons it has all the features it does. You cannot just delete
features to reduce the complexity. The hard part is thinking of ways
to increase the orthogonality of the language to reduce complexity but
without removing any functionality. For example, I think the trick of
converting member function pointers into normal function pointers by
the magic thunks is pretty cool. You can use it to get rid of three
operators, and also simplify the ABI at the same time. (Not to
mention that those offending operators are a pain to use due to their
wrong precedence level... who likes adding all the extra brackets
required to use them?)
Steven
Oops, sorry. The cgi script didn't like the "++" in the url. I've
hacked around that now, but the main page now ends in "cpp/" instead
of "c++/".
Steven
>
>> And what will be the cost of these additions/removals?
>>
>> And what would be the cost of dispatching different errors if all are
>> to be processed after the same code point (something that you achieve
>> with several catch blocks when using the exception)?
>
> Error handling code is usually not performance-critical, so it doesn't
> matter.
Error handling is usually not performance-critical. Setting up for
catching an error has to be done every time no matter whether the error
actually occurs so it very well may be performance-critical. Just try to
start using set_new_handler() to report errors to different targets in
some pipeline- say, when message reader reads a message and memory
cannot be allocated, some higher level in message reader is to be
notified, but when this message is processed, a higher level at the
processor has to be notified. With exceptions, this happens
automatically (although not for free, either).
But actually I meant more design/coding/training/acceptance/maintenance
costs.
>
>> And what will it cost you to program stopping normal flow, destroying
>> automatic objects and actually arriving to the desired
>> error-processing point in code?
>
> Performance-wise? Who cares? Mechanics-wise? A little bit of code that may
> indeed help to focus on doing error handling rather than dispatching errors.
Quite a bit of code will you need to properly destroy automatic objects
(which is quite often the required part of "error handling". If handling
of an error does not require stepping way back, that error can be IMHO
renamed to "yet another condition arising at normal course of given
business").
>> Long story short, for a significant share of tasks set_new_handler
>> approach is certainly not a solution, not even good enough for
>> operator new itself (that's why std::bad_alloc was added).
>>
>
> If by "solution" you mean "one size fits all", there is no answer.
Totally agree, I was the first to say "for a significant share of
tasks". :-)
Not
> without concession anyway.
> If you like exceptions, use them. I prefer to avoid them when/wherever
> possible.
Essentially to me it sounds like: I prefer not to mistake business
requirements for errors. And I could not agree with your more if that's
what you mean. To me an error means: I can't continue this action. Now
what am I going to do? At this point, to have at least the second choice
(the first is to terminate the program and it does not require
exceptions), a robust context-switching mechanism is needed
(longjmp/setjmp, EPOC32/Symbian's "Leaves" with home-made cleanup stack
etc -- you name it). In most situations where the failed "action" is not
a separate "task" (whatever this means -- a thread, a process or
whatever -- in which case you could send a message to another task and
terminate the failed task), exceptions seem to be the most convenient
and least costly (in terms of programming/code maintenance) mechanism.
I don't do large-scale development and I have a toolbox full of
> error-handling techniques. I never understood this quest for "one and only
> one" when that is hardly ever appropriate: one error-management mechanism,
> one "standard" library, one <your choice here>...
See above.. I am not sure about you definition of an error though.
>
>
> Locally handle-able conditions (errors)
This is telling.. what is you definition of an error as opposed to any
other "locally handle-able" condition?
> are much more numerous than global errors handle-able appropriately by a technique such as set_new_handler.
Again, how do you destroy C++ local objects with using a handler?
> There has to be a number of approaches to error handling or else it is a
> lame solution to the problem of error management, so I say. C++ exceptions
> can replace those set_new_handler-like things. Sure, you can use them at the
> local level too, but then with that comes all the confusion of when to just
> crutch on the mechanics or think about the problem and handle the error, and
> it's the whole "exceptional case botching up the common case" thing again.
> The real sad thing about C++ exceptions is that it suppresses thought about
> error handling in general and the science of error handling almost
> stagnates, in C++ groups anyway.
People tend to misuse exceptions, that's true. But IMHO they misuse
other C++ features much more. For example they tend to misuse function
overloading. In particular operator overloads. As well as namespaces.
And passing by references. As well as name hiding. And especially
templates. And compile-time policies. And SFINAE. And recursive template
instatiations. Typelists in particular. And compiler time in general.
And even old poor preprocessor. And what not. And did I mention operator
overloading yet? Of the above misuses, misusing exceptions looks a
smaller sin to me.
-Pavel
Another simple answer from moi: RAII. Problem solved. RAII and exceptions
are orthogonal concepts. RAII works as good with other error handling
strategies as it does with exceptions.
>
>>> Long story short, for a significant share of tasks set_new_handler
>>> approach is certainly not a solution, not even good enough for
>>> operator new itself (that's why std::bad_alloc was added).
>>>
>>
>> If by "solution" you mean "one size fits all", there is no answer.
> Totally agree, I was the first to say "for a significant share of
> tasks". :-)
>
> Not
>> without concession anyway.
>> If you like exceptions, use them. I prefer to avoid them
>> when/wherever possible.
> Essentially to me it sounds like: I prefer not to mistake business
> requirements for errors.
I just meant that I don't want to use the complex thing when another simple
thing will work just fine.
> And I could not agree with your more if
> that's what you mean. To me an error means: I can't continue this
> action. Now what am I going to do? At this point, to have at least
> the second choice (the first is to terminate the program and it does
> not require exceptions),
Termination is only a choice in application code, not in library code. A
library has to propogate or handle the error. Maybe if a precondition is
violated you can make a case for termination from a library, not sure.
> a robust context-switching mechanism is
> needed (longjmp/setjmp, EPOC32/Symbian's "Leaves" with home-made
> cleanup stack etc -- you name it).
I wouldn't call setjmp/longjmp a "context switching mechanism", but I know
what you meant. setjmp/longjmp is not an option in C++ because destructors
aren't called during "the unwinding" of the "call stack".
> In most situations where the
> failed "action" is not a separate "task" (whatever this means -- a
> thread, a process or whatever -- in which case you could send a
> message to another task and terminate the failed task),
Doesn't appear to be an option because that won't "unwind the stack".
> exceptions
> seem to be the most convenient and least costly (in terms of
> programming/code maintenance) mechanism.
That's one way to do it, yes. I'm sure there are others, but that
necessarily commits you to using more than one EH thing.
>
> I don't do large-scale development and I have a toolbox full of
>> error-handling techniques. I never understood this quest for "one
>> and only one" when that is hardly ever appropriate: one error-management
>> mechanism, one "standard" library, one <your choice here>...
> See above.. I am not sure about you definition of an error though.
I'm not either, LOL! ;) I've never sat down and wrote a definition of what
an error is, but I agree with the general usage of the term as used in any
of a number of books. But then again, errors are application-specific too
and categorizing and defining and handling them is application-specific then
also.
>
>>
>>
>> Locally handle-able conditions (errors)
> This is telling.. what is you definition of an error as opposed to any
> other "locally handle-able" condition?
I don't want to get into a glossary debate.
>> are much more numerous than global errors handle-able appropriately
>> by a technique such as set_new_handler.
> Again, how do you destroy C++ local objects with using a handler?
Well, perhaps you can resume after the handler runs if that is how your
handler system works. If still no go, RAII time to do it's thing. How you
setup functions to do cleanup is a stylistic preference. A few macros and a
goto macro perhaps.
>> There has to be a number of approaches to error handling or else it
>> is a lame solution to the problem of error management, so I say. C++
>> exceptions can replace those set_new_handler-like things. Sure, you can
>> use
>> them at the local level too, but then with that comes all the confusion
>> of when
>> to just crutch on the mechanics or think about the problem and handle the
>> error, and it's the whole "exceptional case botching up the common case"
>> thing
>> again. The real sad thing about C++ exceptions is that it suppresses
>> thought about error handling in general and the science of error handling
>> almost
>> stagnates, in C++ groups anyway.
> People tend to misuse exceptions, that's true. But IMHO they misuse
> other C++ features much more. For example they tend to misuse function
> overloading. In particular operator overloads. As well as namespaces.
> And passing by references. As well as name hiding. And especially
> templates. And compile-time policies. And SFINAE. And recursive
> template instatiations. Typelists in particular. And compiler time in
> general. And even old poor preprocessor. And what not. And did I
> mention operator overloading yet? Of the above misuses, misusing
> exceptions looks a smaller sin to me.
I think error management is the most important and the hardest to get right.
It is both permeating and subsystem-ish whereas the other things are
detail-ish.
You seem to be not on one side of the fence or the other: On one side, you
say "these changes should be made to C++"; On the other, you say "new
language based on C++". Which is it?
>
> The key problem is that C++ is too complex.
As I noted though, that is by design rather than by error of omission or
similar. It's impossible to make C++ simple because then it wouldn't meet
the requirements for C++ so it wouldn't be C++ anymore. See what I mean?
> However, there are
> reasons it has all the features it does.
That's all I said.
> You cannot just delete
> features to reduce the complexity. The hard part is thinking of ways
> to increase the orthogonality of the language to reduce complexity but
> without removing any functionality.
Thay may have been "hard" back in the days of C++'s invention, but it's
quite just a gathering task today. Since you have in your article some
common knowledge and maybe presented as novel, I assume everything there is
nothing new. Finally, I don't see "the C++ community" as a place to make any
long term or sweeping changes (the long committee process, etc.): that's the
domain of new language development like Google is doing with Go.
>> Essentially to me it sounds like: I prefer not to mistake business
>> requirements for errors.
>
> I just meant that I don't want to use the complex thing when another simple
> thing will work just fine.
see below..
>
>> And I could not agree with your more if
>> that's what you mean. To me an error means: I can't continue this
>> action. Now what am I going to do? At this point, to have at least
>> the second choice (the first is to terminate the program and it does
>> not require exceptions),
>
> Termination is only a choice in application code, not in library code. A
> library has to propogate or handle the error. Maybe if a precondition is
> violated you can make a case for termination from a library, not sure.
see below...
>
>> a robust context-switching mechanism is
>> needed (longjmp/setjmp, EPOC32/Symbian's "Leaves" with home-made
>> cleanup stack etc -- you name it).
>
> I wouldn't call setjmp/longjmp a "context switching mechanism", but I know
> what you meant. setjmp/longjmp is not an option in C++ because destructors
> aren't called during "the unwinding" of the "call stack".
Exactly my point. It won't automatically, you will have to hand-craft
some stupid mechanism like EPOC32/Symbian cleanup stack and never forget
put your important objects on it. I am just saying: for error processing
some mechanism of context-switching is needed (BTW longjmp/setjmp is
context-switching: the context in this case are the registers including
instruction pointer or equivalent and stack frame pointers or
equivalents. In practice, it's not enough as you want application-level
"unwinding" of automatic objects as you change the stack pointers or
equivalents; that's why I am saying exceptions are convenient and cheap
without obvious drawbacks for the purpose (as long as you do not require
more flexibility in selection of error processing code targets than
exception give you), library-based cleanup-stack is worse and
longjms/setjmp is still worse.. )
>
>> In most situations where the
>> failed "action" is not a separate "task" (whatever this means -- a
>> thread, a process or whatever -- in which case you could send a
>> message to another task and terminate the failed task),
>
> Doesn't appear to be an option because that won't "unwind the stack".
If you terminate the process, the automatic objects are destroyed; if
the "task" is something else, you are right, it's on you again :-).
That's again why I say exceptions are convenient.
>
>> exceptions
>> seem to be the most convenient and least costly (in terms of
>> programming/code maintenance) mechanism.
>
> That's one way to do it, yes. I'm sure there are others, but that
> necessarily commits you to using more than one EH thing.
I mentioned few above; but don't know anything as convenient as exceptions.
>> I don't do large-scale development and I have a toolbox full of
>>> error-handling techniques. I never understood this quest for "one
>>> and only one" when that is hardly ever appropriate: one error-management
>>> mechanism, one "standard" library, one<your choice here>...
>> See above.. I am not sure about you definition of an error though.
>
> I'm not either, LOL! ;) I've never sat down and wrote a definition of what
> an error is, but I agree with the general usage of the term as used in any
> of a number of books. But then again, errors are application-specific too
> and categorizing and defining and handling them is application-specific then
> also.
>
>>
>>>
>>>
>>> Locally handle-able conditions (errors)
>> This is telling.. what is you definition of an error as opposed to any
>> other "locally handle-able" condition?
>
> I don't want to get into a glossary debate.
I am not trying going there either; but we seem to be talking about
different things when we mention "error" term and this is not
productive. I would suggest to exclude A "locably-handleable" conditions
from the discussion. I do not see a reason for segregating them into a
separate category of "errors" from other application-level conditions;
you seem to argue exceptions is not the best way of processing them; I
do not challenge this at all, I just do not call these "business cases"
or "uses cases" "errors".
>
>>> are much more numerous than global errors handle-able appropriately
>>> by a technique such as set_new_handler.
>
>> Again, how do you destroy C++ local objects with using a handler?
>
> Well, perhaps you can resume after the handler runs if that is how your
> handler system works.
See above paragraph this time :-).
> If still no go, RAII time to do it's thing.
So, how do you kick-off the destructors for objects you initialized
under RAII paradigm?
> How you
> setup functions to do cleanup is a stylistic preference. A few macros and a
> goto macro perhaps.
goto is local withing the function, right? You can call these situations
"locally-handleable errors", too, if you like but they IMHO do not
require macros (no more than other business logic, that is). If your
desired control point is few call stacks up, goto can't help you and
this is a distinctive characteristic of an error to me.
>> People tend to misuse exceptions, that's true. But IMHO they misuse
>> other C++ features much more. For example they tend to misuse function
>> overloading. In particular operator overloads. As well as namespaces.
>> And passing by references. As well as name hiding. And especially
>> templates. And compile-time policies. And SFINAE. And recursive
>> template instatiations. Typelists in particular. And compiler time in
>> general. And even old poor preprocessor. And what not. And did I
>> mention operator overloading yet? Of the above misuses, misusing
>> exceptions looks a smaller sin to me.
>
> I think error management is the most important and the hardest to get right.
> It is both permeating and subsystem-ish whereas the other things are
> detail-ish.
It definitely is if you do not limit the scope of your effort first.
"Fixing the world" is rarely productive enterprise. So, the clear
definition of what is and, most importantly, what is not an error, is in
order as well as some definition of what you would expect from error
management.
-Pavel
> RAII is a great idea but you need something to kick off the destructors.
> It may be an important component of error handling strategy but
> exception or something else is needed to actually kick off object
> destruction if error processing requires changing the context outside of
> normal flow control operation.
That's exactly what RAII does for you. You manage resources so they
will be freed when the managing object goes out of scope.
--
Ian Collins
-Pavel
Not really. Local objects will have their destructors called when they go
out of scope. That has nothing to do with exceptions, PER SE. Yes, if you
create a C++ exception mechanism, then you must, in turn, create a new way
for destructor calling. Otherwise though, destructors get called when the
object goes out of scope.
Is there a reason why there is so much pedantic and elementary discussion
necessary here? Personally, _I_ assume that everyone here knows everything
in anything ever written in a commonly available C++ book/programming book,
etc. Save for the "harder" stuff. While it is "fun" a little bit to "burn
things in" or "retrive data in the back of the mind", sometimes it appears
that some people in these language groups are just "chomping at the bit" for
an opportunity to recite common knowledge, at the expense of another maybe,
and for what? Is there some contest I don't know about? I felt the need to
"reprimand" and "take to the mat" the other guy for childish tactics for the
very same reason. This little passage is not to curb questions and thoughts
of those who trully don't know and seek to know: there is NO dumb question.
I mean, I remember when I didn't know everything and how it sucked so bad,
but I digress. :)
(Yes, when I say something that is incorrect, I'm just faking it: I really
do know. Yeah, that's the ticket!)
>
>>> Essentially to me it sounds like: I prefer not to mistake business
>>> requirements for errors.
>>
>> I just meant that I don't want to use the complex thing when another
>> simple thing will work just fine.
> see below..
>
>>
>>> And I could not agree with your more if
>>> that's what you mean. To me an error means: I can't continue this
>>> action. Now what am I going to do? At this point, to have at least
>>> the second choice (the first is to terminate the program and it does
>>> not require exceptions),
>>
>> Termination is only a choice in application code, not in library
>> code. A library has to propogate or handle the error. Maybe if a
>> precondition is violated you can make a case for termination from a
>> library, not sure.
> see below...
>
>>
>>> a robust context-switching mechanism is
>>> needed (longjmp/setjmp, EPOC32/Symbian's "Leaves" with home-made
>>> cleanup stack etc -- you name it).
>>
>> I wouldn't call setjmp/longjmp a "context switching mechanism", but
>> I know what you meant. setjmp/longjmp is not an option in C++
>> because destructors aren't called during "the unwinding" of the
>> "call stack".
> Exactly my point. It won't automatically, you will have to hand-craft
> some stupid
Ah, now we are getting "real". You say "stupid" and you meant it
passionately. OK, noted. There's nothing wrong with passion and/or
personality (as if "boring" language newsgroups didn't need help in that
department). :P
> mechanism like EPOC32/Symbian cleanup stack and never
> forget put your important objects on it. I am just saying: for error
> processing some mechanism of context-switching is needed (BTW
> longjmp/setjmp is context-switching: the context in this case are the
> registers including instruction pointer or equivalent and stack frame
> pointers or equivalents.
I would and do reserve that terminology for the commonly known usage of it:
switching from user mode to kernel mode, and switching threads too.
> In practice, it's not enough as you want
> application-level "unwinding" of automatic objects as you change the
> stack pointers or equivalents;
You don't have to do that if you aren't "setjmping/longjmping" or "C++
exceptioning": it happens just as in normal program flow. Destructors get
called when class objects go out of scope.
> that's why I am saying exceptions are
> convenient and cheap
You're are juxtaposing "cause and effect".
> without obvious drawbacks for the purpose (as
> long as you do not require more flexibility in selection of error
> processing code targets than exception give you), library-based
> cleanup-stack is worse and longjms/setjmp is still worse.. )
OK, this is turning into a tangent about "why you should use exceptions".
Save to say, you are "preaching to the choir". I don't want to age standing
still and this thread is becoming one of those rehashes of stuff everyone
already knows and makes their own choices about. Up next for sure: "why you
should use garbage collection". No offense, but I have better, more
important, more fun, <more other>, things to do. Unless you have some "new
twist" on the old material, I don't want to hear it again. No offense.
>
>>
>>> In most situations where the
>>> failed "action" is not a separate "task" (whatever this means -- a
>>> thread, a process or whatever -- in which case you could send a
>>> message to another task and terminate the failed task),
>>
>> Doesn't appear to be an option because that won't "unwind the stack".
> If you terminate the process, the automatic objects are destroyed;
While I don't not believe you, I don't believe you. I probably don't have to
know that either because:
1. It is probably platform-specific.
2. It's not a separate thing to design around the whole envelope of such
behavior.
> if
> the "task" is something else, you are right, it's on you again :-).
> That's again why I say exceptions are convenient.
"Convenience" is not even on the list of requirements though.
>
>>
>>> exceptions
>>> seem to be the most convenient and least costly (in terms of
>>> programming/code maintenance) mechanism.
>>
>> That's one way to do it, yes. I'm sure there are others, but that
>> necessarily commits you to using more than one EH thing.
> I mentioned few above; but don't know anything as convenient as
> exceptions.
"I gotta go".
>>>> Locally handle-able conditions (errors)
>>> This is telling.. what is you definition of an error as opposed to
>>> any other "locally handle-able" condition?
>>
>> I don't want to get into a glossary debate.
> I am not trying going there either; but we seem to be talking about
> different things when we mention "error" term and this is not
> productive.
I didn't see that as hampering the elementary and pendantic dialog, but I do
agree that a reasonably agreed-upon glossary of terms is required for
efficient discussion or debate or problem resolution. I think TOO much time
is spent discussing different things ("talking past each other") in these
language groups for that very reason: everyone starts jabbering in their own
foreign English/Technolang. Which is fine if it is clear from the context,
else it is very wasteful.
> I would suggest to exclude A "locably-handleable"
> conditions from the discussion.
There is no more discussion unless you have something new (to the larger
scope of the body of knowledge that is this stuff) to present.
> I do not see a reason for segregating
> them into a separate category of "errors" from other
> application-level conditions;
Well you ponder that some more if you want to. I consider classification of
errors an important step in error management. Without thought in that task,
I would assess the development process deficient. Nuff said. I'm not here to
talk about my development methods.
> you seem to argue exceptions is not the best way of processing them;
"best" sounds very final. Don't you think? It's a good way. A car may be
"the best way" to go to work, but I still ride my bike sometimes. Go figure!
> I
> do not challenge this at all, I just do not call these "business
> cases" or "uses cases" "errors".
I can't spend more time on this.
>
>>
>>>> are much more numerous than global errors handle-able appropriately
>>>> by a technique such as set_new_handler.
>>
>>> Again, how do you destroy C++ local objects with using a handler?
>>
>> Well, perhaps you can resume after the handler runs if that is how
>> your handler system works.
> See above paragraph this time :-).
>
>> If still no go, RAII time to do it's thing.
> So, how do you kick-off the destructors for objects you initialized
> under RAII paradigm?
"kick off" what? There's nothing to "kick off", unless you are under the
impression that "unwind" is something tied to an exception mechanism. Do you
know what an exception is? Is it different from an "error"? How? Have you
considered that someone like maybe Google who "shuns C++ exceptions" may
not be using ANY other kind of exception either? What is an "exception"? I
believe that answer will answer your questions about "kicking off
destructors".
>
>> How you
>> setup functions to do cleanup is a stylistic preference. A few
>> macros and a goto macro perhaps.
> goto is local withing the function, right?
Yes.
> You can call these
> situations "locally-handleable errors", too, if you like but they
> IMHO do not require macros (no more than other business logic, that is).
By now I'm "getting to know you", and "your take" on macros I'm sure can be
easily ascertained from "your take" on exceptions. No offense, but you are
repeating programming "cliches".
> If your
> desired control point is few call stacks up, goto can't help you and
> this is a distinctive characteristic of an error to me.
So why are you talking at me about it? Thought + time = resolution.
>
>
>>> People tend to misuse exceptions, that's true. But IMHO they misuse
>>> other C++ features much more. For example they tend to misuse
>>> function overloading. In particular operator overloads. As well as
>>> namespaces. And passing by references. As well as name hiding. And
>>> especially templates. And compile-time policies. And SFINAE. And
>>> recursive template instatiations. Typelists in particular. And
>>> compiler time in general. And even old poor preprocessor. And what
>>> not. And did I mention operator overloading yet? Of the above
>>> misuses, misusing exceptions looks a smaller sin to me.
>>
>> I think error management is the most important and the hardest to
>> get right. It is both permeating and subsystem-ish whereas the other
>> things are detail-ish.
> It definitely is if you do not limit the scope of your effort first.
> "Fixing the world" is rarely productive enterprise. So, the clear
> definition of what is and, most importantly, what is not an error, is
> in order as well as some definition of what you would expect from
> error management.
"I don't know what an error is". I want you to tell me. Unfortunately, I
have an idea of how long that will take until you know and I can't wait that
long. Don't ever again try to suck me into an elementary/pendantic
discussion about this stuff, thanks. When you have something new to add to
the existing "body of knowledge", I'll probably be reading it.
That's all I had to say? I just spent a half hour replying to him. Twas time
well-spent though. I don't regret it at all. (OK, I do, but for selfish/time
reasons).
Sorry, that was bad form relying out of context.
--
Ian Collins
On 20 Nov, 19:55, "Balog Pal" <p...@lib.hu> wrote:
> "sfuerst" <svfue...@gmail.com>
sfuerst:
> >>
> I think you've missed the point. In other languages, people are able
> to globally override memory allocation. This is done for debugging:
> To find a memory leak, you can store the file and line of the
> allocation call, and see where the allocations that are never freed
> come from. Another use is profiling: What sized allocations are your
> program repeatedly allocating and freeing? Perhaps the use of some
> other allocator other than the default could improve performance.
>
> With C++, you can use the placement new syntax to pass extra
> information to an overridden new routine. The problem is that there
> is no way to globally cause this to happen, without manually editing
> every single call to new in your program.
Balog Pal:
> TMK there is a bunch of leak detectors out there.
but if I want to build one?
Balog Pal:
> But I never used any of them -- Visual C++ has it built-in for decades. And
> all it takes a single #define new debug_new at the front of the source. If
> you use it, then you get the source file/line of allocation besides the
> leaked block address, size and content.
>
> The last time I had to put together a [cheap?] diagnostic was around '94, even
> then I was possibly just not aware of a ready solution...
>
> Manually editing lines of code?
I've used perl scripts to do things like that
Balog Pal:
> What you ask for, is sitteng there, working, discover how it is done instead
> of claiming it impossible.
>
> OTOH, the real way to make correct code is definitely not going by that info
> but through using consistent RAII-like handling, and reviews enforcing it.
I keep on seeing things like this. How exactly does RAII deal with
allocations that don't follow a simple model?
Balog Pal:
> As test runs will hardly cover all possible paths including errors and
> exceptions, so relying on the empty leak list from a random run is nothing
> but illusion of being okay. While with trivial style it is easy to make
> leaks impossible.
so explain to me how I trivially remove all possible memory leaks from
my programs.
Balog Pal:
> Why invest in better patches instead of cure the problem at roots?
because you can't remove the problem at its root. If you want true
dynamic allocation then you need to trigger delete somehow. Unless you
add garbage collection to C++.
<snip>
> >> > Hello, I've written an article [...] detailing ten
> >> > perceived problems with C
<snip>
> >> > 2) Exceptions
>
> >> Ok, I'll take on number 2. :-)
>
> >> Constructors is just one reason for exceptions. Overloaded operators
> >> is another one. How would you return your failed code from an
> >> operator+()?
>
> > You are right, basically you are stuck. Exceptions are sometimes the
> > only way to communicate failure conditions in some cases. [...]
> > the problem with exceptions is [...] that
> > they provide a poorly documented interface to functions. In theory,
> > all functions should list what potential exceptions they could throw
> > as part of their definition / external documented interface.
> [...]
> > The reason is that far too many
> > exceptions are possible in any non-trivial code.
> [...]
> For reducing the combinatorial explosion, all exceptions propagating out
> of a library should be derived from std::exception.
which rules out using much of boost
> In my experience, most exceptions finally get logged as a text, or
> displayed to the end user as a text.
not in mine. many exceptions are swallowed silently. Even if they are
reported to a log file that isn't the primary reason for the
exception. Think automatic invocation of dtors and passing control
back up the stack.
> So what I need is just to convert
> every exception to text.
use what()
> This can be done by a single function, which is
> called from all catch(...) clauses (... is verbatim here!). The function
> rethrows and catches the exception, converting it into the text
> appropriately for all kind of known exception types (language translation
> can be done at this point as well).
sounds complicated
> Having special exception types only comes handy when they are used for
> handling special situations near the throw point. But this should not be
> visible to other libraries and should not cause combinatorial explosion.
define "near". What if I want to pack out of some deeply nested
transaction? The call can't proceed and all the associated resources
need to be freed- some of which are actually physical resources like
radio channels or comms links.
> > In short, wouldn't it be cool if the compiler could guarantee that
> > your code was exception safe? To do so, it simply needs more
> > information than what currently is usually provided.
>
> I guess you have another meaning of exception safety than me. For me,
> exception safety is achieved when I open a registry key or clipboard in
> the function and use ScopeGuard to close it in any case when exiting the
> function. Yes it would be cool if compiler warned me when I forgot the
> ScopeGuard. Sadly, today it warns me only about the opposite - when I use
> the ScopeGuard :-( (unused variable ScopeGuard332 defined on line
> 332...).
> >> 2) Exceptions
>
> > Constructors is just one reason for exceptions. Overloaded operators is
> > another one. How would you return your failed code from an operator+()?
>
> That's the wrong question to ask though because it pre-supposes exceptions.
> The question is, "what are the alternatives?".
>
> 1. Don't write code that errors in constructors or operators.
what do I do if construction fails? There is no memory available.
There're aren't any working radio channels. That PDU won't parse.
> 2. Do default error handling instead of requiring propogation via
> exceptions: exit(), perhaps.
you'd love that if your mobile phone worked that way...
"The entire mobile phone network crashed today when Tracey Smith to
tried to phone her boyfriend during her lunch break".
I think I'll go back to carrier pidgeons
> 3. Use another error handling technique.
> 4. ?
make constructors return a value and ban operator redefinition.
> The argument for exceptions goes something like this: Some people want to
> write code where errors can happen in constructors and operators,
no. We can't avoid construction sometimes failing. The physical layer
is imperfect.
> therefore
> exceptions were created. Because exceptions were created, why not just go
> ahead and use them all over the place.
>
> It's a classic case of the exceptional case (no pun intended, but a little
> humourous anyway) botching up the common case.
exceptions make your code look much cleaner.
--
As our circle of knowledge expands,
so does the circumference of darkness surrounding it.
(Albert Einstein)
>you have an odd way of quoting that makes it hard to distinguish what
>you are saying from what you are responding to.
A few messages are not getting quoted, even with OE-quotefix' help...
probably some headers confuse the system.
Like this one, it is correctly colored, but the text is elft as is, no >
inserted. Fortunately it is rare.
>> TMK there is a bunch of leak detectors out there.
>but if I want to build one?
Guess then you take all the pain of the redundant work... Should a language
be tuned for such activity in general?
>> OTOH, the real way to make correct code is definitely not going by that
>> info
>> but through using consistent RAII-like handling, and reviews enforcing
>> it.
>I keep on seeing things like this. How exactly does RAII deal with
>allocations that don't follow a simple model?
Insert some example of the model you think problematic.
>> As test runs will hardly cover all possible paths including errors and
>> exceptions, so relying on the empty leak list from a random run is
>> nothing
>> but illusion of being okay. While with trivial style it is easy to make
>> leaks impossible.
>so explain to me how I trivially remove all possible memory leaks from
>my programs.
"Remove" is not the way. You start from nothing -- that is assumed
leak-free ;-) and add all the stuff in a way that can't leak. So your
program is always leak-free.
The style is like:
- 'delete' is forbidden in "client" code. It is privilige of the the few
library classes that serve as managers. Like auto_ptr.
- The result of every new goes immediately to one such manager.
That's about it. Certainly there are a few cases where ownership is
transferred -- so look what happens to result of release(), Detach() and
similar calls, that is IME natural.
Same thing applies to other resources: files, handles, GDI resources, locks,
transactions.
As an example, you may look some old Petzold examples to struggle with raw
WIN API in pure C -- and see how the same thing looks using MFC's CFont,
CBrush and similar wrappers. The difference is incredible in readability
and clearness. As a side effect DBWIN no longer explodes on any random
program reporting a zillion of lost resources.
If you want a very simple example, think a program that processes text
manipulating strings all its time. A C++ solution would use std::string,
(or any of the much better string classes). Doing all the passing-around,
cutting, concatenating, etc.
Without having a single new or other allocation in the *client* code of the
program. While obvoiusly doing a zillion alllocations and deallocations.
Can you describe a way to introduce a leak?
>> Why invest in better patches instead of cure the problem at roots?
>because you can't remove the problem at its root. If you want true
>dynamic allocation then you need to trigger delete somehow. Unless you
>add garbage collection to C++.
Sure you can, and many of us do it in practice. C++ has destructors that
are called automaticly at well defined points -- and that automation can
reliably be used to do the deletes you need. All of them.
As, unless you start doing WTF things deliberately just to prove idiots'
endless resources, destructors will be called matching constructors, and
when leaving a scope by *any* means. So the programmer's responsibility
is just to not leave non-managed resources around.
(Certainly for certain tasks you can insert GC too, I didn;t work with such
problem yet, but read success stories.)
Like which? All Boost exceptions I have seen have been derived from
std::exception. But I admit I have used only a few libraries.
In any case if there are other unrelated exception types, it does not
rule out anything, it just makes life a bit harder.
>> In my experience, most exceptions finally get logged as a text, or
>> displayed to the end user as a text.
>
> not in mine. many exceptions are swallowed silently. Even if they are
> reported to a log file that isn't the primary reason for the
> exception. Think automatic invocation of dtors and passing control
> back up the stack.
This is even easier. If you are using a particular exception type, you
can throw and catch it exactly in the points you like. This has nothing
to do with the original problem raised up in the thread, at least not as
far as I have understood, namely that by using multiple libraries one
often does not know exactly which exceptions might arise, and how to deal
with them. Obivously, if you are using a custom exception just to wind up
the stack, this is no problem.
>
>> So what I need is just to convert
>> every exception to text.
>
> use what()
Yes, if the exception is derived from std::exception. It gets more
difficult when it isn't.
>
>
>> This can be done by a single function, which is
>> called from all catch(...) clauses (... is verbatim here!). The
>> function rethrows and catches the exception, converting it into the
>> text appropriately for all kind of known exception types (language
>> translation can be done at this point as well).
>
> sounds complicated
Not really. The alternative would be to have multiple catch clauses in
each try block. This was actually needed for MSVC++ 6.0 because of a
compiler bug.
I typically have something like this, in pseudocode:
while(true) {
try {
choose-and-process-request;
return-result-to-client;
} catch(...) {
Log("request failed: " + RecoverExceptionTextInCatch());
return-failure-to-client;
}
}
Where RecoverExceptionTextInCatch() might look something like:
std::string RecoverExceptionTextInCatch() {
try {
throw;
} catch(const std::exception& e) {
return e.what();
} catch(const char* e) {
return e? e: "NULL";
#ifdef _MSC_VER
} catch(CException* pe) {
std::string message = TranslateCException(pe);
pe->Delete();
return message;
#endif
/// ... other types as appropriate
} catch(...) {
return "Unrecognized exception";
}
}
>
>> Having special exception types only comes handy when they are used
>> for handling special situations near the throw point. But this should
>> not be visible to other libraries and should not cause combinatorial
>> explosion.
>
> define "near". What if I want to pack out of some deeply nested
> transaction? The call can't proceed and all the associated resources
> need to be freed- some of which are actually physical resources like
> radio channels or comms links.
This is fine, and this is exactly what exceptions are meant for.
What I meant by "near" was that it should be in borders of a single
library, or more generally in the borders of code you directly control.
If the exception passes through another library which you don't have much
control over, it might not get through reliably. It can be swallowed or
translated to another exception type (if not today, then in the next
version). In your own library you can modify all intermediate catch
blocks (if there are any!) to let this special exception propagate
through untouched, until the proper handling point is reached.
Regards
Paavo
Uh... I think you missed the boat on this one. I think dragan meant
"return error codes" when he said "other error handling strategies".
(This includes logging the specific error then returning a generic
"failure" error code. It's still returning an error code.) Any other
kind of error handling is extremely exotic.
He meant that RAII works quite well if you use only error return codes
and do not use exceptions, and he is correct. What you do to "kick off
the destructors" is exit the current scope, generally by returning an
error code (or success code, or any kind of returning).
> I am just saying: for error processing
> some mechanism of context-switching is needed (BTW longjmp/setjmp is
> context-switching: the context in this case are the registers including
> instruction pointer or equivalent and stack frame pointers or
> equivalents. In practice, it's not enough as you want application-level
> "unwinding" of automatic objects as you change the stack pointers or
> equivalents; that's why I am saying exceptions are convenient and cheap
> without obvious drawbacks for the purpose (as long as you do not require
> more flexibility in selection of error processing code targets than
> exception give you), library-based cleanup-stack is worse and
> longjms/setjmp is still worse.. )
I might have to disagree. Is "returning an error code" contained in
"context switching"? Returning an error code is a perfectly fine way
to handle errors, and returning an error code is not in the context of
programming what someone would call "context switching".
<snip>
> >> [...] all exceptions propagating
> >> out of a library should be derived from std::exception.
>
> > which rules out using much of boost
>
> Like which? All Boost exceptions I have seen have been derived from
> std::exception. But I admit I have used only a few libraries.
I'd encountered something called boost::exception which didn't derive
from std::exception. Reading the boost documentation more closely it
appears you are supposed (or encouraged) to derive from both std:: and
boost:: exception.
> >> In my experience, most exceptions finally get logged as a text, or
> >> displayed to the end user as a text.
>
> > not in mine. many exceptions are swallowed silently. Even if they are
> > reported to a log file that isn't the primary reason for the
> > exception. Think automatic invocation of dtors and passing control
> > back up the stack.
>
> This is even easier. If you are using a particular exception type, you
> can throw and catch it exactly in the points you like.
Oh I know I was just pointing out the rasion d'etre of exceptions was
*not* to produce a human readable message.
> This has nothing
> to do with the original problem raised up in the thread, at least not as
> far as I have understood, namely that by using multiple libraries one
> often does not know exactly which exceptions might arise, and how to deal
> with them.
you'd hope the library would document it somehow.
> Obivously, if you are using a custom exception just to wind up
> the stack, this is no problem.
whats the difference between a "library exception" and a "custom
exception"
> >> So what I need is just to convert every exception to text.
>
> > use what()
>
> Yes, if the exception is derived from std::exception. It gets more
> difficult when it isn't.
there's all sorts of nasty things people do. I liked the one that made
all the exception data private. With no access methods. Were you
supposed to trap it in the debugger?
<snip>
> >> Having special exception types only comes handy when they are used
> >> for handling special situations near the throw point. But this should
> >> not be visible to other libraries and should not cause combinatorial
> >> explosion.
>
> > define "near". What if I want to [break? (I've no idea what word I meant to use here!)]
> > out of some deeply nested
> > transaction? The call can't proceed and all the associated resources
> > need to be freed- some of which are actually physical resources like
> > radio channels or comms links.
>
> This is fine, and this is exactly what exceptions are meant for.
>
> What I meant by "near" was that it should be in borders of a single
> library,
I don't agree
> or more generally in the borders of code you directly control.
> If the exception passes through another library which you don't have much
> control over, it might not get through reliably.
It will if the library is written sanely. Don't catch anything you
don'ty know how to handle [The Ferret Catchers Handbook]
<snip>
> >> OTOH, the real way to make correct code is definitely not going by that
> >> info but through using consistent RAII-like handling, and reviews enforcing
> >> it.
>
> >I keep on seeing things like this. How exactly does RAII deal with
> >allocations that don't follow a simple [stack based] model?
>
> Insert some example of the model you think problematic.
anything that doesn't follow a stack model. A graphical editor. A
mobile phone system. You are at the mercy of the end user as to when
objects are created and destroyed. With the phone system people can
drive into tunnels or JCBs (back-hoes) can dig up comms links (that
really happened once).
> >> As test runs will hardly cover all possible paths including errors and
> >> exceptions, so relying on the empty leak list from a random run is
> >> nothing but illusion of being okay. While with trivial style it is easy
> >> to make leaks impossible.
>
> >so explain to me how I trivially remove all possible memory leaks from
> >my programs.
>
> "Remove" is not the way. You start from nothing -- that is assumed
> leak-free ;-) and add all the stuff in a way that can't leak. So your
> program is always leak-free.
ah, my computer science lecturer used to tell us that a blank piece of
paper had no bugs. Programmers then just went on to add bugs.
> The style is like:
> - 'delete' is forbidden in "client" code. It is privilige of the the few
> library classes that serve as managers. Like auto_ptr.
and who holds the auto-ptr?
> - The result of every new goes immediately to one such manager.
> That's about it.
these things always seem to solve the easy cases (ok they used to be
the very hard cases!) but not the hard cases.
> Certainly there are a few cases where ownership is
> transferred --
a few!
> so look what happens to result of release(), Detach() and
> similar calls, that is IME natural.
>
> Same thing applies to other resources: files, handles, GDI resources, locks,
> transactions.
except for transactions, yes
> As an example, you may look some old Petzold examples to struggle with raw
> WIN API in pure C -- and see how the same thing looks using MFC's CFont,
> CBrush and similar wrappers.
I've wrappered Win32 in C++. Yes, RAII makes life easier.
> The difference is incredible in readability
> and clearness. As a side effect DBWIN no longer explodes on any random
> program reporting a zillion of lost resources.
>
> If you want a very simple example,
no, I don't want a simple example
> think a program that processes text
> manipulating strings all its time. A C++ solution would use std::string,
> (or any of the much better string classes). Doing all the passing-around,
> cutting, concatenating, etc.
consider a text editor that allows the user to delete text. Who
deletes the string that holds the deleted stuff and when. What if the
editor has Undo/Redo?
> Without having a single new or other allocation in the *client* code of the
> program.
the client code is gonna have to do something to trigger the new. Call
a factory for instance.
> While obvoiusly doing a zillion alllocations and deallocations.
> Can you describe a way to introduce a leak?
forget to call the thing that triggers the delete.
> >> Why invest in better patches instead of cure the problem at roots?
>
> >because you can't remove the problem at its root. If you want true
> >dynamic allocation then you need to trigger delete somehow. Unless you
> >add garbage collection to C++.
>
> Sure you can, and many of us do it in practice. C++ has destructors that
> are called automaticly at well defined points
the points are not always so well defined.
> -- and that automation can
> reliably be used to do the deletes you need. All of them.
>
> As, unless you start doing WTF things deliberately just to prove idiots'
I'm not *trying* to break things.
> endless resources, destructors will be called matching constructors, and
> when leaving a scope by *any* means.
but leaving scope is *not* the correct time to delete some objects!
CallManager::process_event (EventAP event)
{
CallAP call = 0;
if (is_new_call (event))
{
call = CallFactory::create_call (event);
CallList::add_call (call);
}
else
{
CallAp call = CallList::lookup_call (event);
}
call.process_event(event);
}
when this finishes the call should very definitly not be destroyed!
The CallManager (or some class this is delegated to has to track what
goes into CallList (the list of currently active calls) and also be
careful about rmoving things from it- when they are destroyed.
> So the programmer's responsibility
> is just to not leave non-managed resources around.
oh, *that* all!
> (Certainly for certain tasks you can insert GC too, I didn;t work with such
> problem yet, but read success stories.)
I've never used garbage collection in C++ either.
--
Nick Keighley
The world you perceive is drastically simplified model of the real
world
(Herbert Simon)
> > "NickKeighley" <nick_keighley_nos...@hotmail.com>
> <snip>
> > >> OTOH, the real way to make correct code is definitely not
> > >> going by that info but through using consistent RAII-like
> > >> handling, and reviews enforcing it.
> > >I keep on seeing things like this. How exactly does RAII
> > >deal with allocations that don't follow a simple [stack
> > >based] model?
> > Insert some example of the model you think problematic.
> anything that doesn't follow a stack model. A graphical
> editor. A mobile phone system. You are at the mercy of the end
> user as to when objects are created and destroyed. With the
> phone system people can drive into tunnels or JCBs (back-hoes)
> can dig up comms links (that really happened once).
Certainly. RAII doesn't apply to entity objects (which means
most dynamically allocated memory). On the other hand, it's
very useful, for enforcing transactional semantics within the
transaction which handles the events: it probably applies to 95%
or more semaphore locks, for example.
> > >> As test runs will hardly cover all possible paths
> > >> including errors and exceptions, so relying on the empty
> > >> leak list from a random run is nothing but illusion of
> > >> being okay. While with trivial style it is easy to make
> > >> leaks impossible.
> > >so explain to me how I trivially remove all possible memory
> > >leaks from my programs.
> > "Remove" is not the way. You start from nothing -- that is
> > assumed leak-free ;-) and add all the stuff in a way that
> > can't leak. So your program is always leak-free.
> ah, my computer science lecturer used to tell us that a blank
> piece of paper had no bugs. Programmers then just went on to
> add bugs.
It's true, and the secret to quality programming is to not add
bugs. That's why people use code review and unit tests and any
number of other techniques (like not writing overly complicated
code to begin with).
> > The style is like:
> > - 'delete' is forbidden in "client" code. It is privilige
> > of the the few library classes that serve as managers.
> > Like auto_ptr.
> and who holds the auto-ptr?
It's a stupid rule anyway. It doesn't work in practice. The
real rule for memory management is not to use dynamic allocation
at all, except when the object lifetime is explicit (e.g. a call
in a telephone system). And of course then, your design (or
more directly, your requirements specification) determines when
the object should be deleted.
> a few!
> except for transactions, yes
That's probably not a good example. The text buffer holds the
text before it's deleted, and the deleted text itself is never a
separate object, unless...
> What if the editor has Undo/Redo?
Then you save the deleted text in a redo record. Which will be
deleted when the requirements specifications says it should be
deleted.
> > Without having a single new or other allocation in the
> > *client* code of the program.
> the client code is gonna have to do something to trigger the
> new. Call a factory for instance.
Which comes down to the same. Sometimes the factory method is
justified---it may be preferable to check pre-conditions
beforehand, or to register the created object with a transaction
(so it can be correctly deleted in case of rollback).
> > While obvoiusly doing a zillion alllocations and
> > deallocations. Can you describe a way to introduce a leak?
> forget to call the thing that triggers the delete.
> > >> Why invest in better patches instead of cure the problem
> > >> at roots?
> > >because you can't remove the problem at its root. If you
> > >want true dynamic allocation then you need to trigger
> > >delete somehow. Unless you add garbage collection to C++.
> > Sure you can, and many of us do it in practice. C++ has
> > destructors that are called automaticly at well defined
> > points
> the points are not always so well defined.
> > -- and that automation can reliably be used to do the
> > deletes you need. All of them.
> > As, unless you start doing WTF things deliberately just to
> > prove idiots'
> I'm not *trying* to break things.
> > endless resources, destructors will be called matching
> > constructors, and when leaving a scope by *any* means.
> but leaving scope is *not* the correct time to delete some
> objects!
It it is the correct time, then you don't want dynamic
allocation to begin with.
> CallManager::process_event (EventAP event)
> {
> CallAP call = 0;
> if (is_new_call (event))
> {
> call = CallFactory::create_call (event);
> CallList::add_call (call);
> }
> else
> {
> CallAp call = CallList::lookup_call (event);
> }
> call.process_event(event);
> }
> when this finishes the call should very definitly not be
> destroyed! The CallManager (or some class this is delegated
> to has to track what goes into CallList (the list of currently
> active calls) and also be careful about rmoving things from
> it- when they are destroyed.
I'm not sure what CallAP is, but this looks like a familiar
pattern (except that I'd probably keep the newly created call
object in an auto_ptr until I'd successfully returned from
CallList::add_call). And of course, if the event is "hang up",
and that brings the connection count in the call down to zero,
it is the call itself (in a function called from
Call::process_event) which will do the delete.
--
James Kanze
A new way I've been thinking about RAII is the following: All "free
resource calls", like delete, release mutex, close connection, etc.,
should be made only inside destructors. Specifically, all resources at
all times should have a clearly identified owner who frees the
resource in its destructor, or the resource is a stack object. It's
easy to extend this idea to shared ownership. Optionally, you can free
resources early as long as the resource still has an owner which would
still free the resource if you "accidentally" commented out the early
release.
The idea is that maintaining the invariant of "ownership" using
destructors produces easier to follow code, and less leaky code. RAII
is all about ownership responsibilities. I haven't taken enough time
to look through all examples, so please treat this as a tentative idea
from myself. Obviously there will be exceptions to this rule, I think.
That's practicly the same thing I was talking about. Not stupid at all, and
passed the test of the real life in practice. Possibly my ise of "client"
code is not clear -- I keep keep the handlers themselves (that have the
dtors) as "library" code. Which has a different life cycle. Maybe
"framework" would be a better name.
Early drop can be done through the .reset() interface (or its equivalent in
the manager), and commenting it out just results in keeping the thing little
longer.
I don't get what would be the problem with non-stack-frame limited
resources -- the manager may be at some outer block, or a member of the
class, but eventually it will bite the dust too.
I meant ALL other handling strategies, not just "return an error code".
(But then again, how many are there?).
> (This includes logging the specific error then returning a generic
> "failure" error code. It's still returning an error code.) Any other
> kind of error handling is extremely exotic.
Not hardly. To say that C++ exceptions is one and returning an error code is
two, and everything else is "exotic", is probably wrong (depending on your
definition of 'exotic').
>
> He meant that RAII works quite well if you use only error return codes
> and do not use exceptions, and he is correct. What you do to "kick off
> the destructors" is exit the current scope, generally by returning an
> error code (or success code, or any kind of returning).
Yes, it is that easy: the C++ scoping mechanisms which guarantee local
object destructor calls. Given that, you can build an alternative to
exceptions around that. The other guy though seemed to be starting with a
"world" where exception mechanisms are the norm (C++) and is oblivious to
the orthogonality of the scoping mechanisms with the exception mechanism.
(At least that's what I remember without going back and reading what I said.
I don't really like long threads because that indicates lack of
communication, or teaching the newbies, or posturing for "advantage", or
"religious issue", or...).
I think C++ may indeed be "dumbing down" programmers. (I'll start that
separate thread so this one can rest in peace). (Posted new thread topic).
>
>> I am just saying: for error processing
>> some mechanism of context-switching is needed (BTW longjmp/setjmp is
>> context-switching: the context in this case are the registers
>> including instruction pointer or equivalent and stack frame pointers
>> or
>> equivalents. In practice, it's not enough as you want
>> application-level "unwinding" of automatic objects as you change the
>> stack pointers or equivalents; that's why I am saying exceptions are
>> convenient and cheap without obvious drawbacks for the purpose (as
>> long as you do not require more flexibility in selection of error
>> processing code targets than exception give you), library-based
>> cleanup-stack is worse and longjms/setjmp is still worse.. )
>
> I might have to disagree. Is "returning an error code" contained in
> "context switching"? Returning an error code is a perfectly fine way
> to handle errors, and returning an error code is not in the context of
> programming what someone would call "context switching".
He used 'context switching' incorrectly or something. Not to be curbing
though (to the other guy): keep on researching dude! I don't believe anyone
can learn faster than by making mistakes, BUT, I suggest you analyze others'
mistakes rather than go through all the hard knocks on your own. The
difference between man and animal is supposedly accumulated knowledge across
generations. All evidence to the contrary: knowledge has historically taken
a back seat to oppression and politics. While you have more than I did, and
I did envision a Wikepedia-like thing when I was a child, imagine the future
possibilities given that information is freer more now than ever. The trend
is positive, but lame: evolution of animals seems to be on the order of the
purported differentiation between people and animals. Held back by whom?
"library exception" has to be minimize in a library (until 0.0 if possible)
because the programmer that use the library has to choose of what to do
in an error condition (and not the programmer that write the library)
are you sure all your "const" are right?
are you sure that the memory point from p is not a memory leak?
Is there anhoter way to call
"void* operator new( size_t const size, char const* const blah )"
other than "new( "Get it?" ) S( 42 )"?
even if i write
> int main()
> {
> S* const p = new( "Get it?" ) S( 42 );
delete p;
> }
it calls, the "delete p" string, the [default] distructor of S?
------------------------
#include <stdio.h>
#include <stdlib.h>
#define P printf
#define i8 signed char
#define V void
struct S{
public:
void* (operator new)(size_t size, char* blah)
{printf( "allocation function called, arg = \"%s\"\n", blah );
return (::operator new)(size);
}
S(int whatever)
{Sarr=0;
printf( "constructor called, arg = %d\n", whatever );
if(whatever<=0) return;
Sarr= (i8*) malloc(whatever);
P("S(int)");
}
S(){
printf( "void costructor\n");
Sarr= (i8*) malloc(1024);
P("S()");
}
~S(){
printf("distructor\n");
free(Sarr);
}
i8* Sarr;
};
int main(void)
{// S* p=new("Get it?") S( 42 );
// S* p=new("Get it?") S;
// S* p=new("Get it?") S();
S* p=new("Get it?")S;
delete p;
P("\nEND\n");
return 0;
}
here the memory pointed from p [ came from (::operator new)(size)]
is really free?
As I'm not a C++ expert I was wondering if this post (and others in
this thread) was going to earn me a Larting. I do however have
opinions (opinions are like...) on softwasre developmne tand I have
developed and maintained C++ systems.
I just get bugged by the "RAII solves all memory management problems"
line and the implicit assumption that all objects follow a simple FIFO
allocation/deallocation protocol. Don't these people ever tackle real
world problems!
> > > >> OTOH, the real way to make correct code is definitely not
> > > >> going by that info but through using consistent RAII-like
> > > >> handling, and reviews enforcing it.
>
> > > >I keep on seeing things like this. How exactly does RAII
> > > >deal with allocations that don't follow a simple [stack
> > > >based] model?
>
> > > Insert some example of the model you think problematic.
>
> > anything that doesn't follow a stack model. A graphical
> > editor. A mobile phone system. You are at the mercy of the end
> > user as to when objects are created and destroyed. With the
> > phone system people can drive into tunnels or JCBs (back-hoes)
> > can dig up comms links (that really happened once).
>
> Certainly. RAII doesn't apply to entity objects (which means
> most dynamically allocated memory). On the other hand, it's
> very useful, for enforcing transactional semantics within the
> transaction which handles the events: it probably applies to 95%
> or more semaphore locks, for example.
ah, thanks. Entity objects... is that what you call 'em. And yes RAII
fits very nicely with tansactions. If the transaction can't complete
then all the things it allocated are safely freed.
> > > >> As test runs will hardly cover all possible paths
> > > >> including errors and exceptions, so relying on the empty
> > > >> leak list from a random run is nothing but illusion of
> > > >> being okay. While with trivial style it is easy to make
> > > >> leaks impossible.
>
> > > >so explain to me how I trivially remove all possible memory
> > > >leaks from my programs.
>
> > > "Remove" is not the way. You start from nothing -- that is
> > > assumed leak-free ;-) and add all the stuff in a way that
> > > can't leak. So your program is always leak-free.
>
> > ah, my computer science lecturer used to tell us that a blank
> > piece of paper had no bugs. Programmers then just went on to
> > add bugs.
>
> It's true, and the secret to quality programming is to not add
> bugs.
that's why it stuck in my head. Even if its an ideal not entirely
reachable ("never make mistakes"!) its worth keeping at the back of
your mind. Avoid "oh it doesn't matter I can always clean it up
later".
> That's why people use code review and unit tests and any
> number of other techniques (like not writing overly complicated
> code to begin with).
yes
> > > The style is like:
> > > - 'delete' is forbidden in "client" code. It is privilige
> > > of the the few library classes that serve as managers.
> > > Like auto_ptr.
>
> > and who holds the auto-ptr?
>
> It's a stupid rule anyway. It doesn't work in practice.
oh, goody so I wasn't so far out in the out-field
> The
> real rule for memory management is not to use dynamic allocation
> at all, except when the object lifetime is explicit (e.g. a call
> in a telephone system).
if things are of variable size? Oh yes use std::vector (or other
suitable container).
I'm beginning to form the rule of thumb that all uses of new[] are
errors. Or at least need looking at very hard.
> And of course then, your design (or
> more directly, your requirements specification) determines when
> the object should be deleted.
yes. There is no doubt when a call finishes. Well /almost/ no doubt,
some group calls get a little hairy.
<snip>
[simple example] (too simple if you ask me)
> > > think a program that processes text manipulating strings all
> > > its time. A C++ solution would use std::string, (or any of
> > > the much better string classes). Doing all the
> > > passing-around, cutting, concatenating, etc.
>
> > consider a text editor that allows the user to delete text.
> > Who deletes the string that holds the deleted stuff and when[?]
>
> That's probably not a good example. The text buffer holds the
> text before it's deleted, and the deleted text itself is never a
> separate object, unless...
sorry, "who deletes the text buffer?"
> > What if the editor has Undo/Redo?
>
> Then you save the deleted text in a redo record. Which will be
> deleted when the requirements specifications says it should be
> deleted.
but it's no longer solved by RAII alone
> > > Without having a single new or other allocation in the
> > > *client* code of the program.
>
> > the client code is gonna have to do something to trigger the
> > new. Call a factory for instance.
>
> Which comes down to the same.
exactly, the client code has to cause the object to be created somehow
> Sometimes the factory method is
> justified---it may be preferable to check pre-conditions
> beforehand, or to register the created object with a transaction
> (so it can be correctly deleted in case of rollback).
>
> > > While obvoiusly doing a zillion alllocations and
> > > deallocations. Can you describe a way to introduce a leak?
>
> > forget to call the thing that triggers the delete.
>
> > > >> Why invest in better patches instead of cure the problem
> > > >> at roots?
>
> > > >because you can't remove the problem at its root. If you
> > > >want true dynamic allocation then you need to trigger
> > > >delete somehow. Unless you add garbage collection to C++.
>
> > > Sure you can, and many of us do it in practice. C++ has
> > > destructors that are called automaticly at well defined
> > > points
>
> > the points are not always so well defined.
sorry, the points at which delete are called is well defined with
RAII, it's just the point at which an "entity object" is released is
not specified by a FIFO protocol and hence RAII is not a complete
solution.
> > > -- and that automation can reliably be used to do the
> > > deletes you need. All of them.
<snip>
> > > endless resources, destructors will be called matching
> > > constructors, and when leaving a scope by *any* means.
>
> > but leaving scope is *not* the correct time to delete some
> > objects!
>
> It it is the correct time, then you don't want dynamic
> allocation to begin with.
thanks
> > CallManager::process_event (EventAP event)
> > {
> > CallAP call = 0;
> > if (is_new_call (event))
> > {
> > call = CallFactory::create_call (event);
> > CallList::add_call (call);
> > }
> > else
> > {
> > CallAP call = CallList::lookup_call (event);
> > }
> > call.process_event(event);
> > }
> > when this finishes the call should very definitly not be
> > destroyed! The CallManager (or some class this is delegated
> > to has to track what goes into CallList (the list of currently
> > active calls) and also be careful about rmoving things from
> > it- when they are destroyed.
>
> I'm not sure what CallAP is,
stupidly I went to The Hungarian Side of the programming nature. I
meant of course std::auto_ptr<Call>. The example was cobbled together
rather quickly and may be buggy. Second attempt:
CallManager::process_event (std::auto_ptr<Event> event)
{
std::auto_ptr<Call> call(0);
if (is_new_call (event))
{
call = CallFactory::create_call (event);
CallList::add_call (call);
}
else
{
call = CallList::lookup_call (event);
}
call->process_event(event);
}
> but this looks like a familiar
> pattern (except that I'd probably keep the newly created call
> object in an auto_ptr until I'd successfully returned from
> CallList::add_call). And of course, if the event is "hang up",
> and that brings the connection count in the call down to zero,
> it is the call itself (in a function called from
> Call::process_event) which will do the delete.
I was assuming that happened inside
call->process_event(event);
> >> > > The style is like:
> >> > > - 'delete' is forbidden in "client" code. It is privilige
> >> > > of the the few library classes that serve as managers.
> >> > > Like auto_ptr.
>
> >> > and who holds the auto-ptr?
>
> >> It's a stupid rule anyway. It doesn't work in practice. The
> >> real rule for memory management is not to use dynamic allocation
> >> at all, except when the object lifetime is explicit (e.g. a call
> >> in a telephone system). And of course then, your design (or
> >> more directly, your requirements specification) determines when
> >> the object should be deleted.
this is a good summary
> >A new way I've been thinking about RAII is the following: All "free
> >resource calls", like delete, release mutex, close connection, etc.,
> >should be made only inside destructors.
ok, fine
> >Specifically, all resources at
> >all times should have a clearly identified owner who frees the
> >resource
yes.
> > in its destructor,
less sure about this.
> >or the resource is a stack object.
there are objects with very long lifetimes. Lifetimes comparable but
not necessarily equal to the lifetime of the system. For instance
calls in some mobile radio systems can be of unlimited duration. But
not all calls are like this. So clearing/hanging up calls must be
properly managed or all sorts of precious reocurces will leak away.
I can't see anyway to do this apart from having objects
(CurrentCallList) that are effectivly global.
> It's
> >easy to extend this idea to shared ownership. Optionally, you can free
> >resources early as long as the resource still has an owner which would
> >still free the resource if you "accidentally" commented out the early
> >release.
so the CurrentCallList in my example gets destructed when the program
terminates and any calls left in are destructed then. Which might be a
bit late. I suppose it would close the call log and tell the user his
call has terminated.
> >The idea is that maintaining the invariant of "ownership" using
> >destructors produces easier to follow code, and less leaky code.
yes
>RAII
> >is all about ownership responsibilities. I haven't taken enough time
> >to look through all examples, so please treat this as a tentative idea
> >from myself. Obviously there will be exceptions to this rule, I think.
objects that don't follow a FIFO life cycle. Which is pretty much
anything in the real world!
> That's practicly the same thing I was talking about. Not stupid at all, and
> passed the test of the real life in practice.
managed equipment, calls, circuits, channels, routes, messages, logged
in users, drawable objects in animations, etc. etc. etc.
> Possibly my ise of "client"
> code is not clear -- I keep [] the handlers themselves (that have the
> dtors) as "library" code. Which has a different life cycle. Maybe
> "framework" would be a better name.
don't follow you
> Early drop can be done through the .reset() interface (or its equivalent in
> the manager), and commenting it out just results in keeping the thing little
> longer.
if the system nver terminates that means the object is kept forever.
So as soon as we've used all the available radio channels the system
hangs.
> I don't get what would be the problem with non-stack-frame limited
> resources -- the manager may be at some outer block, or a member of the
> class, but eventually it will bite the dust too.
oh it will bite the dust eventually but system termination time (weeks
later? months later?) is waay too late. There are well defined times
when a call terminates (the user hang up, we lose contact with him,
something breaks)- BUT THEY DONT FOLLOW A FIFO LIFE CYCLE!
And I submit, many real world things don't!
> > whats the difference between a "library exception" and a "custom
> > exception"
>
> "library exception" has to be minimize in a library (until 0.0 if possible)
> because the programmer that use the library has to choose of what to do
> in an error condition (and not the programmer that write the library)
well I tend to write libraraies to implement my application. Are the
exceptions these libraries throw "library exceptions" or "custom
exceptions"? I think I draw the library/application line more blurrily
than you. 3rd party libraries are plainly more "library-like" than
some app-specific libraries.
>As I'm not a C++ expert I was wondering if this post (and others in
>this thread) was going to earn me a Larting. I do however have
>opinions (opinions are like...) on softwasre developmne tand I have
>developed and maintained C++ systems.
>I just get bugged by the "RAII solves all memory management problems"
>line and the implicit assumption that all objects follow a simple FIFO
>allocation/deallocation protocol. Don't these people ever tackle real
>world problems!
Not sure how you arrived at the everything is FIFO assumption.
I was stating to use managers -- and that managers can be non-locals. After
all we don't have so much places, so if you have a manager it can be:
- local in a block in the immediate function
- local in a block in a function somewhere up the call chain
- a global(/singleton)
you put responsibility of deallocation to the manager, and it will
eventually happen for all of them. There is a chance for pseudo-leaks, we
probably not yet discussed. Like when you have a list with long life, keep
adding nodes and never remove any. Stuff can get stuck in managers that are
global or live wery much up (say right in main()). Getting the resources
released only at prog exit may be not good enough, and act like a regular
leak in all practical terms. No silver bullets. Think that is hardly news
to anyone reading this group.
And pseudo-leaks are damn hard to discuss in abstract, in a forum: how you
tell whether some such node is yet needed in a system or not? Except by the
particular design? ;-)
The point is that the managers in the latter category are not flooding the
app -- you have only a few of them hopefully, and can pay close attention on
the content. And normally they are helped out with more local RAII
sub-managers.
Many real systems work processing 'requests', serving events from outsude.
That have a well defined point of entry, that is fortunately a
function/stack frame too. And you can use that to manage all the stuff that
is local-to that request. When it ends, you can be sure the app's state is
as before. Or changed *only* as expected by the req itself.
The more problematic RL cases when request create a "session" to work with
and must carry state of that further. So you will have a session manager
upwards. Normally some of the further events will bring the session to its
final state where it can be disposed -- then certainly releasing all
associated resources. If driven by outside events, certainly only a proper
design can make sure it happens consistently.
>> > > The style is like:
>> > > - 'delete' is forbidden in "client" code. It is privilige
>> > > of the the few library classes that serve as managers.
>> > > Like auto_ptr.
>>
>> > and who holds the auto-ptr?
>>
>> It's a stupid rule anyway. It doesn't work in practice.
>oh, goody so I wasn't so far out in the out-field
Still is is not at all stupid, and it works perfetcly fine in my practice.
Possibly my separation of things to "client" and "library" (aka framework,
support, etc) is not clear and misleads judgement.
The most power of C++ comes from the fact it gives superior tools to create
a quasi-language in which you can most directly and expressively state the
real aim of the program. To em the client or application code is what is
written in that latter language. That definitely don't have place for
keyword delete.
>> The
>> real rule for memory management is not to use dynamic allocation
>> at all, except when the object lifetime is explicit (e.g. a call
>> in a telephone system).
>if things are of variable size? Oh yes use std::vector (or other
>suitable container).
Like in these very cases. Client code shall no way attempt to implement
containers, string, etc -- but use them for good. :) It asks dynamic
stuff in indirect way, like resize, or invoking factory calls.
Eliminating dynamic memory use is rare -- probably extinct outside embedded.
>I'm beginning to form the rule of thumb that all uses of new[] are
>errors. Or at least need looking at very hard.
Absolutely. delete[] was the first thing I blacklisted and it certainly
dragged new[]. And didn't meet a single case it was needed in the last 15
years.
[simple example] (too simple if you ask me)
>> > > think a program that processes text manipulating strings all
>> > > its time. A C++ solution would use std::string, (or any of
>> > > the much better string classes). Doing all the
>> > > passing-around, cutting, concatenating, etc.
>>
>> > consider a text editor that allows the user to delete text.
>> > Who deletes the string that holds the deleted stuff and when[?]
>
>> That's probably not a good example. The text buffer holds the
>> text before it's deleted, and the deleted text itself is never a
>> separate object, unless...
>sorry, "who deletes the text buffer?"
In a single-doc editor the text buffer can be permanent.
(I guess text editor is not a good forum example, the questions you rised
can be easily answered with a multitude of alternative designs, all with
pros&cons, yet trivially ensuring resource control -- but it would take a
book-worth amount of words. If really needed, pick some sub-part, or narrow
it down.)
>> > What if the editor has Undo/Redo?
>>
>> Then you save the deleted text in a redo record. Which will be
>> deleted when the requirements specifications says it should be
>> deleted.
>but it's no longer solved by RAII alone
Undo also can be made with several approaches, like keeping snapshots of the
current state, or record request to play forward or back.
What you mean by "RAII alone"? Possibly we speak on distict tracks because
you fill that term by different content...
The meaning tied to original mosaic is actually rearly meant literally --
the most common use is RRID (res. release is destruction) leaving
allocation freeform -- still routinely referred as RAII. :-o
As I use it RAII stays for an even relaxed form, that allows the full
interface auto_ptr has, including reset() and release() -- just makes sure
dedicated ownership is maintained, and destruction of a manager does mean
release of anything owned.
Maybe we could call it Controlled Resource Management, or Controller-based
RM, or something like, but it would end up unused like RRID, and I did not
see in practice RAII's tendency to force restrictions.
>> > > Sure you can, and many of us do it in practice. C++ has
>> > > destructors that are called automaticly at well defined
>> > > points
>>
>> > the points are not always so well defined.
IMO discounting UB and other blacklisted cases they are.
>sorry, the points at which delete are called is well defined with
>RAII, it's just the point at which an "entity object" is released is
>not specified by a FIFO protocol and hence RAII is not a complete
>solution.
If you mean the "is-initialization" part, sure, it covers only a subset of
cases.
>>
CallManager::process_event (std::auto_ptr<Event> event)
{
std::auto_ptr<Call> call(0);
if (is_new_call (event))
{
call = CallFactory::create_call (event);
CallList::add_call (call);
}
else
{
call = CallList::lookup_call (event);
}
call->process_event(event);
}
<<
This does not make much sense to me, id call_list is the manager/owner of
calls, you don't use auto_ptr like that, and especially don't place the
result of lookup in auto_ptr. There must be exactly one owner. Maybe you
meant shared_ptr, but it looks like overkill too.
And this is a good example what client code shall NOT do by any means --
leave call management with the call manager. So the whole thing becomes a
one-liner:
CallList::get_call(event).process_event(event);
get_call within the manager can see whether it is existing or a new call,
create as necessary and return one by ref. (This example implies no NULL
possibility, if there is one, make it
if(Call * pCall = CallList::get_call(event))
pCall->process_event(event);
>there are objects with very long lifetimes. Lifetimes comparable but
>not necessarily equal to the lifetime of the system. For instance
>calls in some mobile radio systems can be of unlimited duration. But
>not all calls are like this. So clearing/hanging up calls must be
>properly managed or all sorts of precious reocurces will leak away.
>I can't see anyway to do this apart from having objects
>(CurrentCallList) that are effectivly global.
See my other post.
> It's
> >easy to extend this idea to shared ownership. Optionally, you can free
> >resources early as long as the resource still has an owner which would
> >still free the resource if you "accidentally" commented out the early
> >release.
>so the CurrentCallList in my example gets destructed when the program
>terminates and any calls left in are destructed then. Which might be a
>bit late. I suppose it would close the call log and tell the user his
>call has terminated.
Exactly what I just said. But... what can you do about it? The call is
either needed to linger, than it is the correct thing, or it must be
terminated, that was due well before exit. That is likely a leak in the
*design* (like at the abstract/UML level). If not, it's a bug of translation
of design to code.
>>RAII
> >is all about ownership responsibilities. I haven't taken enough time
> >to look through all examples, so please treat this as a tentative idea
> >from myself. Obviously there will be exceptions to this rule, I think.
>objects that don't follow a FIFO life cycle. Which is pretty much
>anything in the real world!
My observation are the "is-init" part, that truly enforces FIFO is pretty
rare kind of use. Partly due to some widespread library designs.
MFC uses 2-phase init extensively, and even the true RAII cases are broken
with the interface, see CSingleLock -- who in right mind would make it take
= false as default param?
Still RRID handles most of the practical cases for good -- and the order is
many times not as direct.
>> That's practicly the same thing I was talking about. Not stupid at all,
>> and
>> passed the test of the real life in practice.
>managed equipment, calls, circuits, channels, routes, messages, logged
>in users, drawable objects in animations, etc. etc. etc.
LOL, made ton of all all those and more :)
>> Possibly my ise of "client"
>> code is not clear -- I keep [] the handlers themselves (that have the
>> dtors) as "library" code. Which has a different life cycle. Maybe
>> "framework" would be a better name.
>don't follow you
Too bad. More specificly?
>> Early drop can be done through the .reset() interface (or its equivalent
>> in
>> the manager), and commenting it out just results in keeping the thing
>> little
>> longer.
>if the system nver terminates that means the object is kept forever.
>So as soon as we've used all the available radio channels the system
>hangs.
See earlier and other post.
>> I don't get what would be the problem with non-stack-frame limited
>> resources -- the manager may be at some outer block, or a member of the
>> class, but eventually it will bite the dust too.
>oh it will bite the dust eventually but system termination time (weeks
>later? months later?) is waay too late. There are well defined times
>when a call terminates (the user hang up, we lose contact with him,
>something breaks)- BUT THEY DONT FOLLOW A FIFO LIFE CYCLE!
Never occoured to me they are ;-) The important part is they do follow a
LIFE CYCLE. You maintain that. Most importantly at paper design. I saw
way more problems messed up right there, what leaves no chance in the
implementation to be correct. If your system allows (or overlooks) endless
calls, and it is a permanent system, yes, it will eventually run out of
juice. Protocol steps that wait trigger from outside have attached timers
for a good reason...
(Actually one of my main lines of work is duning all kind of communications.
At high level, middle level, low level, you name it, and most systems
planned for non-stop use. And they work too, many years a chunk, sometimes
stopped for some hardware maitainance or accident.... When I say it works it
is not theory, but life-proven practice, and on failure I'd probably not
talk here unless usenet is mainstream in jails. ;-)
"OTOH, the real way to make correct [non-leaking] code is definitely
not going by [new/delete logging] info but through using consistent
RAII-like handling, and reviews enforcing it."
and
"As test runs will hardly cover all possible paths including errors
and exceptions, so relying on the empty leak list from a random run is
nothing but illusion of being okay. While with trivial style it is
easy to make leaks impossible."
I disputed that RAII cured all emory leak problems (I was wrong to
suggest garbage collection as that is equally helpless in the face of
foolish application coders). You now appear to be agreeing with me,
but without explicitly saying so.
On 1 Dec, 11:27, "Balog Pal" <p...@lib.hu> wrote:
> "Nick Keighley" <nick_keighley_nos...@hotmail.com>
<snip>
> >I just get bugged by the "RAII solves all memory management problems"
> >line and the implicit assumption that all objects follow a simple FIFO
> >allocation/deallocation protocol. Don't these people ever tackle real
> >world problems!
>
> Not sure how you arrived at the everything is FIFO assumption.
what else does RAII enforce apart from a FIFO allocation/deallocation
policy? If RAII is the solution to all memory management problems then
all memory management must be a FIFO based policy.
> I was stating to use managers
you never mentioned them before. What is a "manager"? Like the
CallManager class in my example? Or like the CallList class in my
example?
> -- and that managers can be non-locals. After
> all we don't have so much places, so if you have a manager it can be:
>
> - local in a block in the immediate function
> - local in a block in a function somewhere up the call chain
> - a global(/singleton)
ok.
> you put responsibility of deallocation to the manager, and it will
> eventually happen for all of them. There is a chance for pseudo-leaks,
what you call a pseudo-leak *I* call a leak.
> we
> probably not yet discussed. Like when you have a list with long life, keep
> adding nodes and never remove any. Stuff can get stuck in managers that are
> global or live wery much up (say right in main()). Getting the resources
> released only at prog exit may be not good enough, and act like a regular
> leak in all practical terms.
right so you agree that RAII doesn't solve all resource management
problems? Goody.
> No silver bullets. Think that is hardly news
> to anyone reading this group.
so why did you say "with trivial style it is easy to make leaks
impossible"?
> And pseudo-leaks are damn hard to discuss in abstract, in a forum: how you
> tell whether some such node is yet needed in a system or not? Except by the
> particular design? ;-)
>
> The point is that the managers in the latter category are not flooding the
> app -- you have only a few of them hopefully, and can pay close attention on
> the content. And normally they are helped out with more local RAII
> sub-managers.
ok. Not what you originally said though. I'll stop saying this.
<snip>
> The most power of C++ comes from the fact it gives superior tools to create
> a quasi-language in which you can most directly and expressively state the
> real aim of the program. To em the client or application code is what is
> written in that latter language. That definitely don't have place for
> keyword delete.
ah, now this is clearer. Ban new/delete from application code. Or
certainly delete.
> >> The
> >> real rule for memory management is not to use dynamic allocation
> >> at all, except when the object lifetime is explicit (e.g. a call
> >> in a telephone system).
>
> >if things are of variable size? Oh yes use std::vector (or other
> >suitable container).
>
> Like in these very cases. Client code shall no way attempt to implement
> containers, string, etc -- but use them for good. :) It asks dynamic
> stuff in indirect way, like resize, or invoking factory calls.
>
> Eliminating dynamic memory use is rare -- probably extinct outside embedded.
yes but you don't have to have it application code
<snip>
> [simple example] (too simple if you ask me)
>
> >> > > think a program that processes text manipulating strings all
> >> > > its time. A C++ solution would use std::string, (or any of
> >> > > the much better string classes). Doing all the
> >> > > passing-around, cutting, concatenating, etc.
>
> >> > consider a text editor that allows the user to delete text.
> >> > Who deletes the string that holds the deleted stuff and when[?]
>
> >> That's probably not a good example. The text buffer holds the
> >> text before it's deleted, and the deleted text itself is never a
> >> separate object, unless...
>
> >sorry, "who deletes the text buffer?"
>
> In a single-doc editor the text buffer can be permanent.
>
> (I guess text editor is not a good forum example,
I thought it was a good example...
:-)
> the questions you rised
> can be easily answered with a multitude of alternative designs, all with
> pros&cons, yet trivially ensuring resource control -- but it would take a
> book-worth amount of words. If really needed, pick some sub-part, or narrow
> it down.)
>
> >> > What if the editor has Undo/Redo?
>
> >> Then you save the deleted text in a redo record. Which will be
> >> deleted when the requirements specifications says it should be
> >> deleted.
>
> >but it's no longer solved by RAII alone
>
> Undo also can be made with several approaches, like keeping snapshots of the
> current state, or record request to play forward or back.
>
> What you mean by "RAII alone"? Possibly we speak on distict tracks because
> you fill that term by different content...
well to me RAII enforces a stack discipline of resource allocation
(you yourself early said something about the resouce being released
when the stack frame is left).
> The meaning tied to original mosaic is actually rearly meant literally --
> the most common use is RRID (res. release is destruction) leaving
> allocation freeform -- still routinely referred as RAII. :-o
>
> As I use it RAII stays for an even relaxed form, that allows the full
> interface auto_ptr has, including reset() and release() -- just makes sure
> dedicated ownership is maintained, and destruction of a manager does mean
> release of anything owned.
>
> Maybe we could call it Controlled Resource Management, or Controller-based
> RM, or something like, but it would end up unused like RRID, and I did not
> see in practice RAII's tendency to force restrictions.
ok. But these more sophisticated resource management strategies can no
longer be termed "trivial"
> >> > > Sure you can, and many of us do it in practice. C++ has
> >> > > destructors that are called automaticly at well defined
> >> > > points
>
> >> > the points are not always so well defined.
>
> IMO discounting UB and other blacklisted cases they are.
>
> >sorry, the points at which delete are called is well defined with
> >RAII, it's just the point at which an "entity object" is released is
> >not specified by a FIFO protocol and hence RAII is not a complete
> >solution.
>
> If you mean the "is-initialization" part, sure, it covers only a subset of
> cases.
oh by RAII I include RRID, I regard it as a single term for the entire
concept I don't see any in chopping it up.
> CallManager::process_event (std::auto_ptr<Event> event)
> {
> std::auto_ptr<Call> call(0);
> if (is_new_call (event))
> {
> call = CallFactory::create_call (event);
> CallList::add_call (call);
> }
> else
> {
> call = CallList::lookup_call (event);
> }
> call->process_event(event);}
>
> <<
>
> This does not make much sense to me, id call_list is the manager/owner of
> calls, you don't use auto_ptr like that, and especially don't place the
> result of lookup in auto_ptr.
I probably should have used a normal pointer.
> There must be exactly one owner. Maybe you
> meant shared_ptr, but it looks like overkill too.
>
> And this is a good example what client code shall NOT do by any means --
> leave call management with the call manager. So the whole thing becomes a
> one-liner:
>
> CallList::get_call(event).process_event(event);
and if it's a new call?
> get_call within the manager can see whether it is existing or a new call,
> create as necessary and return one by ref.
ok... That looks like a mere re-arrangement to me.
> (This example implies no NULL
> possibility, if there is one, make it
I was assuming lookup_call threw if it failed
> if(Call * pCall = CallList::get_call(event))
> pCall->process_event(event);
I'll not dispute my example should have more thought put into it
> > > On 27 Nov, 10:59, "Balog Pal" <p...@lib.hu> wrote:
> > > > "NickKeighley" <nick_keighley_nos...@hotmail.com>
> > > <snip>
> > > > The style is like:
> > > > - 'delete' is forbidden in "client" code. It is privilige
> > > > of the the few library classes that serve as managers.
> > > > Like auto_ptr.
> > > and who holds the auto-ptr?
> > It's a stupid rule anyway. It doesn't work in practice.
> > The real rule for memory management is not to use dynamic
> > allocation at all, except when the object lifetime is
> > explicit (e.g. a call in a telephone system). And of course
> > then, your design (or more directly, your requirements
> > specification) determines when the object should be deleted.
> A new way I've been thinking about RAII is the following: All
> "free resource calls", like delete, release mutex, close
> connection, etc., should be made only inside destructors.
There are two major problems with this: it doesn't always apply,
and it only concerns resources. What you're concerned about is
program coherence: freeing a mutex lock (in a destructor) when
you've left the locked data in an inconsistent state, for
example, is not going to help. You must ensure that the
destructor also leaves the object in a coherent state. (This is
what is often meant by exception safety, but it really concerns
anything which might affect program flow.)
> Specifically, all resources at all times should have a clearly
> identified owner who frees the resource in its destructor, or
> the resource is a stack object.
This is fine to a point, but I am supposing that the "identified
owner" is an object, and instance of a class. If that object is
dynamically allocated, and you're considering memory as a
resource, then you have a vicious circle.
> It's easy to extend this idea to shared ownership. Optionally,
> you can free resources early as long as the resource still has
> an owner which would still free the resource if you
> "accidentally" commented out the early release.
> The idea is that maintaining the invariant of "ownership"
> using destructors produces easier to follow code, and less
> leaky code. RAII is all about ownership responsibilities. I
> haven't taken enough time to look through all examples, so
> please treat this as a tentative idea from myself. Obviously
> there will be exceptions to this rule, I think.
The problem is that in many cases, the "owner" of an object is
the object itself. The whole basis of OO is that objects have
behavior; that behavior often determines their lifetime. (It's
not for nothing that most deletes in well written code are
"delete this", at least in some application domains.)
--
James Kanze
> > On Nov 30, 10:11 am, NickKeighley<nick_keighley_nos...@hotmail.com>
> > > On 27 Nov, 10:59, "Balog Pal" <p...@lib.hu> wrote:
> > > > "NickKeighley" <nick_keighley_nos...@hotmail.com>
> As I'm not a C++ expert I was wondering if this post (and
> others in this thread) was going to earn me a Larting. I do
> however have opinions (opinions are like...) on softwasre
> developmne tand I have developed and maintained C++ systems.
> I just get bugged by the "RAII solves all memory management
> problems" line and the implicit assumption that all objects
> follow a simple FIFO allocation/deallocation protocol. Don't
> these people ever tackle real world problems!
It's the latest silver bullet. It's quite possible that it
actually works for some real world problems. I've just never
seen any where it does: it doesn't work for anything which looks
like a server, for example, or handles external events (which
includes GUI's). (It does work when the "dynamic objects" are
variable length arrays, used in numeric
calculations---std::vector is a good example. Except, of
course, that an implementation of std::vector that only deletes
in the destructor doesn't work.)
> > > > >> OTOH, the real way to make correct code is definitely
> > > > >> not going by that info but through using consistent
> > > > >> RAII-like handling, and reviews enforcing it.
> > > > >I keep on seeing things like this. How exactly does
> > > > >RAII deal with allocations that don't follow a simple
> > > > >[stack based] model?
> > > > Insert some example of the model you think problematic.
> > > anything that doesn't follow a stack model. A graphical
> > > editor. A mobile phone system. You are at the mercy of the
> > > end user as to when objects are created and destroyed.
> > > With the phone system people can drive into tunnels or
> > > JCBs (back-hoes) can dig up comms links (that really
> > > happened once).
> > Certainly. RAII doesn't apply to entity objects (which
> > means most dynamically allocated memory). On the other
> > hand, it's very useful, for enforcing transactional
> > semantics within the transaction which handles the events:
> > it probably applies to 95% or more semaphore locks, for
> > example.
> ah, thanks. Entity objects... is that what you call 'em.
I forget where I got the word from; I know I looked for a word
for it for a long time. Anyway, it seems as good as anything
else, and I've not heard any other suggestions, so that's what I
call them.
> And yes RAII fits very nicely with tansactions. If the
> transaction can't complete then all the things it allocated
> are safely freed.
Yes. And once you know the transaction has succeeded, you
release the permanent objects from the RAII holders.
> > > > >> As test runs will hardly cover all possible paths
> > > > >> including errors and exceptions, so relying on the
> > > > >> empty leak list from a random run is nothing but
> > > > >> illusion of being okay. While with trivial style it
> > > > >> is easy to make leaks impossible.
> > > > >so explain to me how I trivially remove all possible
> > > > >memory leaks from my programs.
> > > > "Remove" is not the way. You start from nothing -- that
> > > > is assumed leak-free ;-) and add all the stuff in a way
> > > > that can't leak. So your program is always leak-free.
> > > ah, my computer science lecturer used to tell us that a
> > > blank piece of paper had no bugs. Programmers then just
> > > went on to add bugs.
> > It's true, and the secret to quality programming is to not
> > add bugs.
> that's why it stuck in my head. Even if its an ideal not entirely
> reachable ("never make mistakes"!) its worth keeping at the back of
> your mind. Avoid "oh it doesn't matter I can always clean it up
> later".
In a well run organization, it's not "I never make mistakes",
but rather, "Even the best programmers make mistakes, so we set
up various controls (code review, unit tests) to catch them
before they actually get into production code."
> > That's why people use code review and unit tests and any
> > number of other techniques (like not writing overly
> > complicated code to begin with).
> yes
> > > > The style is like:
> > > > - 'delete' is forbidden in "client" code. It is privilige
> > > > of the the few library classes that serve as managers.
> > > > Like auto_ptr.
> > > and who holds the auto-ptr?
> > It's a stupid rule anyway. It doesn't work in practice.
> oh, goody so I wasn't so far out in the out-field
No. That doesn't mean never use std::auto_ptr---I use it a lot.
But most of the time, if the transaction succeeds, the pointer
will be recovered by calling the release function. At the
simplest, auto_ptr holds the pointer until the object is
successfully enrolled in all of the places necessary for it to
receive the appropriate incoming events.
> > The real rule for memory management is not to use dynamic
> > allocation at all, except when the object lifetime is
> > explicit (e.g. a call in a telephone system).
> if things are of variable size? Oh yes use std::vector (or
> other suitable container).
Exactly. Let the standard library worry about dynamic
allocation. (There are obviously exceptions to this. For
example, if you're implementing std::vector:-).)
> I'm beginning to form the rule of thumb that all uses of new[]
> are errors. Or at least need looking at very hard.
That's been my rule since at least 1992 (when I implemented my
first array classes). Within the implementation, you almost
always want to separate allocation from initialiation, so it's
::operator new() and a placement new per element. And outside
the implementation of such classes, you use such classes, rather
than new[].
> > And of course then, your design (or more directly, your
> > requirements specification) determines when the object
> > should be deleted.
> yes. There is no doubt when a call finishes. Well /almost/ no
> doubt, some group calls get a little hairy.
And what happens if someone goes off, and forgets to hang up?
There are also time-outs, and such. But it's application level
logic: not something you define as a coding rule, but rather
something the domain specialists define in the requirements
specifications.
> <snip>
> [simple example] (too simple if you ask me)
> > > > think a program that processes text manipulating strings
> > > > all its time. A C++ solution would use std::string, (or
> > > > any of the much better string classes). Doing all the
> > > > passing-around, cutting, concatenating, etc.
> > > consider a text editor that allows the user to delete
> > > text. Who deletes the string that holds the deleted stuff
> > > and when[?]
> > That's probably not a good example. The text buffer holds
> > the text before it's deleted, and the deleted text itself is
> > never a separate object, unless...
> sorry, "who deletes the text buffer?"
The user? When the text buffer disappears is generally part of
the requirements specifications, see e.g. the commands bdelete
and bwipeout in vim. My point was just that you don't generally
delete an std::string or whatever when you delete text in an
editor.
> > > What if the editor has Undo/Redo?
> > Then you save the deleted text in a redo record. Which will
> > be deleted when the requirements specifications says it
> > should be deleted.
> but it's no longer solved by RAII alone
Certainly not. (My comments were meant to expand on yours, not
to disagree.)
> > > > Without having a single new or other allocation in the
> > > > *client* code of the program.
> > > the client code is gonna have to do something to trigger
> > > the new. Call a factory for instance.
>
> > Which comes down to the same.
> exactly, the client code has to cause the object to be created
> somehow
Yes. I was responding to you AND the people you responded to,
at the same time.
> > Sometimes the factory method is justified---it may be
> > preferable to check pre-conditions beforehand, or to
> > register the created object with a transaction (so it can be
> > correctly deleted in case of rollback).
> > > > While obvoiusly doing a zillion alllocations and
> > > > deallocations. Can you describe a way to introduce a
> > > > leak?
> > > forget to call the thing that triggers the delete.
> > > > >> Why invest in better patches instead of cure the
> > > > >> problem at roots?
> > > > >because you can't remove the problem at its root. If
> > > > >you want true dynamic allocation then you need to
> > > > >trigger delete somehow. Unless you add garbage
> > > > >collection to C++.
> > > > Sure you can, and many of us do it in practice. C++ has
> > > > destructors that are called automaticly at well defined
> > > > points
> > > the points are not always so well defined.
> sorry, the points at which delete are called is well defined
> with RAII, it's just the point at which an "entity object" is
> released is not specified by a FIFO protocol and hence RAII is
> not a complete solution.
The quoting above is deep enough that I'm no longer sure who's
saying what, but some smart pointers are not that deterministic.
Saying that an object will be deleted when the last pointer to
it is destructed is saying that unless you know the exact
lifetimes of all of the pointers, you don't know when it will be
deleted. You're basically using smart pointers as a slow and
bugging implementation of garbage collection. Except that the
indeterminisms of garbage collection are well known.
[...]
> > > CallManager::process_event (EventAP event)
> > > {
> > > CallAP call = 0;
> > > if (is_new_call (event))
> > > {
> > > call = CallFactory::create_call (event);
> > > CallList::add_call (call);
> > > }
> > > else
> > > {
> > > CallAP call = CallList::lookup_call (event);
> > > }
> > > call.process_event(event);
> > > }
> > > when this finishes the call should very definitly not be
> > > destroyed! The CallManager (or some class this is
> > > delegated to has to track what goes into CallList (the
> > > list of currently active calls) and also be careful about
> > > rmoving things from it- when they are destroyed.
> > I'm not sure what CallAP is,
> stupidly I went to The Hungarian Side of the programming
> nature. I meant of course std::auto_ptr<Call>.
But you don't want std::auto_ptr here.
> The example was cobbled together rather quickly and may be
> buggy. Second attempt:
> CallManager::process_event (std::auto_ptr<Event> event)
> {
> std::auto_ptr<Call> call(0);
> if (is_new_call (event))
> {
> call = CallFactory::create_call (event);
> CallList::add_call (call);
> }
> else
> {
> call = CallList::lookup_call (event);
> }
> call->process_event(event);
> }
> > but this looks like a familiar pattern (except that I'd
> > probably keep the newly created call object in an auto_ptr
> > until I'd successfully returned from CallList::add_call).
> > And of course, if the event is "hang up", and that brings
> > the connection count in the call down to zero, it is the
> > call itself (in a function called from Call::process_event)
> > which will do the delete.
> I was assuming that happened inside
> call->process_event(event);
Which is a member function of of call.
I think we basically agree. (I learned the pattern when I
worked on telephone systems, but I've since learned that it
applies in just about all servers and all event driven systems.)
--
James Kanze
As I always understood it, this is how C++ automatic objects and
hierarchies work.
--
Francesco S. Carta, http://fscode.altervista.org
You can add some if you want.
> are you sure that the memory point from p is not a memory leak?
p does leak.
It's not an example of memory management.
> Is there anhoter way to call
> "void* operator new( size_t const size, char const* const blah )"
> other than "new( "Get it?" ) S( 42 )"?
Yes.
> even if i write
>
>> int main()
>> {
>> S* const p = new( "Get it?" ) S( 42 );
> delete p;
>> }
>
> it calls, the "delete p" string, the [default] distructor of S?
Yes.
Don't know if you have introduced any undefined behavior or the like, but if
not, then yes it's freed.
Cheers & hth.,
- Alf
>> A new way I've been thinking about RAII is the following: All
>> "free resource calls", like delete, release mutex, close
>> connection, etc., should be made only inside destructors.
>
> There are two major problems with this: it doesn't always apply,
Possibly it is just a wrong selection of wording.
I wrote many manager classes and read implementation of stock ones -- IME it
is rare to put the release code in the dtor. And too often I put it there
first just to move away very soon. To a private function like Dispose (I
would call it release, but it that got used for a different thing), and call
that from dtor.
And it is needed for other functions like reset() or operator =.
There is some beauty in the simplest RAII form -- the manager is NOCOPY and
has absolutely nothing but ctor and dtor. It really leaves no chance at all
to mess up. But I used those only for mutex-lock/flag/counter guards that
are so absolutely tied to a scope block and such simple use case.
For many other situations I just used a policy-based smart pointer
(interface functions like that of auto_ptr, have a nocopy and a deepcopy
variant, and the payload of Dispose mentioned above is injected as policy).
Normally I'm against having funtions in the interface that are not known to
be needed up front, but this looks like exception. Possibly because the
mentioned functions are getting special attention in review anyway.
> and it only concerns resources. What you're concerned about is
> program coherence: freeing a mutex lock (in a destructor) when
> you've left the locked data in an inconsistent state, for
> example, is not going to help. You must ensure that the
> destructor also leaves the object in a coherent state. (This is
> what is often meant by exception safety, but it really concerns
> anything which might affect program flow.)
Yeah, this is a way tougher problem than maintain symmetry of alloc
functions. In general at least. There are ways to shove off some of the
burden by sticking to just basic guarantees, and compensate by discarding a
major context entirely on exception. So stronger guarantees are needed only
at handling the few objects that are beyond that and mutable by the thing --
ideally nothing or just a few.
>> Specifically, all resources at all times should have a clearly
>> identified owner who frees the resource in its destructor, or
>> the resource is a stack object.
>
> This is fine to a point, but I am supposing that the "identified
> owner" is an object, and instance of a class. If that object is
> dynamically allocated, and you're considering memory as a
> resource, then you have a vicious circle.
Precisely you have the circle if your if is true indefinitely in the
recursion. In practice there will be a roof. If you stick to the 'every
allocation function can happen only in an ownership-taking parameter place'
you will naturally discover should you missed a real circle.
>> It's easy to extend this idea to shared ownership. Optionally,
>> you can free resources early as long as the resource still has
>> an owner which would still free the resource if you
>> "accidentally" commented out the early release.
>
>> The idea is that maintaining the invariant of "ownership"
>> using destructors produces easier to follow code, and less
>> leaky code. RAII is all about ownership responsibilities. I
>> haven't taken enough time to look through all examples, so
>> please treat this as a tentative idea from myself. Obviously
>> there will be exceptions to this rule, I think.
>
> The problem is that in many cases, the "owner" of an object is
> the object itself. The whole basis of OO is that objects have
> behavior; that behavior often determines their lifetime. (It's
> not for nothing that most deletes in well written code are
> "delete this", at least in some application domains.)
Actually I heard this statistic (though many times) only from a single
person, you, James. While anywhere I looked delete this had zero instances
or very few. Including the well-written programs. So I have
reservations, that it is a tech for indded "some" domains, and not the rest.
Say if a program processes text (and for whatever reason cose C++ instead of
...) does it realisticly need delete this anywhere?
Now as I think of it, MFC has a plenty of classes with self-deleting, and it
makes sense indeed, as part of the framework. And I used those classes
extensively too (stock or as parent classes), but hardly ever needed to do
the same in mine directly. So I tend to not count'em as part of *my*
application, maybe that creates the difference. ;-)
> >As I'm not a C++ expert I was wondering if this post (and
> >others in this thread) was going to earn me a Larting. I do
> >however have opinions (opinions are like...) on softwasre
> >developmne tand I have developed and maintained C++ systems.
> >I just get bugged by the "RAII solves all memory management
> >problems" line and the implicit assumption that all objects
> >follow a simple FIFO allocation/deallocation protocol. Don't
> >these people ever tackle real world problems!
> Not sure how you arrived at the everything is FIFO assumption.
> I was stating to use managers -- and that managers can be
> non-locals. After all we don't have so much places, so if you
> have a manager it can be:
> - local in a block in the immediate function
> - local in a block in a function somewhere up the call chain
> - a global(/singleton)
The destructors of those all occur at very specified points in
time. Specified points which don't necessarily (or even often,
in a lot of application domains) with where the program's
requirements specification say that the object's lifetime ends.
> you put responsibility of deallocation to the manager, and it
> will eventually happen for all of them.
Not necessarily. The typical server runs in an endless loop.
Variables in the same scope as the loop and global variables are
never destructed. Even if you reboot the server from time to
time, you can't wait until then to delete things like call
records (supposing a telephone switch or transmission
equipment).
> There is a chance for pseudo-leaks, we probably not yet
> discussed.
I'm not sure what you mean by "leak" and "pseudo-leak". If we
take the earlier example of a call handling system, if any
resources associated with the call are not released when the
call is terminated, it is a leak.
> Like when you have a list with long life, keep adding nodes
> and never remove any.
If you continue to need to access those nodes, you don't have a
leak, pseudo- or not (although you may have a requirements
specification that is impossible to implement on a machine with
finite memory). If you don't continue to access those nodes,
then you have a leak---there's nothing pseudo- about it, since
it will bring the system down (no pseudo-core-dump, but a real
one) sooner or later.
> Stuff can get stuck in managers that are global or live wery
> much up (say right in main()). Getting the resources released
> only at prog exit may be not good enough, and act like a
> regular leak in all practical terms. No silver bullets.
> Think that is hardly news to anyone reading this group.
Getting resources released only at program exit is *not* good
enough. His example was call handling: program exit probably
won't be for a couple of years or more, and the system might be
handling 10's of thousands of calls a day.
> And pseudo-leaks are damn hard to discuss in abstract, in a
> forum: how you tell whether some such node is yet needed in a
> system or not? Except by the particular design? ;-)
That's true for most objects.
> The point is that the managers in the latter category are not
> flooding the app -- you have only a few of them hopefully, and
> can pay close attention on the content. And normally they are
> helped out with more local RAII sub-managers.
> Many real systems work processing 'requests', serving events
> from outsude. That have a well defined point of entry, that
> is fortunately a function/stack frame too. And you can use
> that to manage all the stuff that is local-to that request.
> When it ends, you can be sure the app's state is as before.
> Or changed *only* as expected by the req itself.
Yes. And the expected change, in some cases, is that a new
object was created or that an existing object was destructed.
> The more problematic RL cases when request create a "session"
> to work with and must carry state of that further. So you
> will have a session manager upwards.
That was basically his example. A call manager is really a sort
of a session manager. (Unlike most "sessions", a call typically
involves several parties, and the session only ends when the
last partie leaves it.)
> Normally some of the further events will bring the session to
> its final state where it can be disposed -- then certainly
> releasing all associated resources. If driven by outside
> events, certainly only a proper design can make sure it
> happens consistently.
Certainly. And in the absense of such cases, when do you use
dynamic memory?
> >> > > The style is like:
> >> > > - 'delete' is forbidden in "client" code. It is privilige
> >> > > of the the few library classes that serve as managers.
> >> > > Like auto_ptr.
> >> > and who holds the auto-ptr?
> >> It's a stupid rule anyway. It doesn't work in practice.
> >oh, goody so I wasn't so far out in the out-field
> Still is is not at all stupid, and it works perfetcly fine in
> my practice. Possibly my separation of things to "client" and
> "library" (aka framework, support, etc) is not clear and
> misleads judgement.
> The most power of C++ comes from the fact it gives superior
> tools to create a quasi-language in which you can most
> directly and expressively state the real aim of the program.
> To em the client or application code is what is written in
> that latter language. That definitely don't have place for
> keyword delete.
The keyword "delete" is a red-herring. Suppose a call object is
processing a "hang-up" event, and it determines that this event
corresponds to the last party in the call. Whether it does
"delete this" or "manager.please_please_delete_me(this)" really
doesn't change anything. (Actually, it does. When you see
"delete this" in the code, you know that the only thing you can
do is return. It's more tempting to continue processing after
something like "manager.deenrol(this)".)
> >> The real rule for memory management is not to use dynamic
> >> allocation at all, except when the object lifetime is
> >> explicit (e.g. a call in a telephone system).
> >if things are of variable size? Oh yes use std::vector (or
> >other suitable container).
> Like in these very cases. Client code shall no way attempt to
> implement containers, string, etc -- but use them for good.
> :) It asks dynamic stuff in indirect way, like resize, or
> invoking factory calls.
I think we all agree here. There are two major reasons to use
dynamic memory: the object's lifetime doesn't correspond to any
of the predefined lifetimes (local or global), or the object's
size or actual type aren't known at compile time. For the
second, I don't think there's any real disagreement: you use
some sort of "container" (preferrably the standard containers)
to manage the memory. For the second, the issues are more
complex: a transaction typically does correspond to (or end at)
an established scope, a connection only does if the server
dedicates a thread to each connection (which for various
reasons isn't really an option in call handling). In such
cases, you might want to use something like auto_ptr to hold
any objects until you commit, but once you've committed, there's
no real point, and even a few drawbacks, to using some special
manager object to avoid an explicit "delete".
> Eliminating dynamic memory use is rare -- probably extinct
> outside embedded.
Come now, you don't use "new int" when what you want is an int
with the same lifetime as the function. Practically, because
int is a value object which supports copy, you don't use "new
int" ever. (About the only time you might dynamically allocate
a value object is when it is big enough to make copying too
expensive. Otherwise, you copy.)
> >I'm beginning to form the rule of thumb that all uses of
> >new[] are errors. Or at least need looking at very hard.
> Absolutely. delete[] was the first thing I blacklisted and it
> certainly dragged new[]. And didn't meet a single case it was
> needed in the last 15 years.
Beat you there:-). I've never used a new[] or a delete[], and
I've been programming in C++ for 17 or 18 years now.
Yes. The name is historical, and actually slightly confusing,
because what characterizes RAII is the release in a destructor.
But the name is ubiquious, so we're more or less stuck with it.
But that doesn't change his comment: the decision as to when to
"drop" an redo record (regardless of how the information is
stored) will almost certainly not be made in a destructor.
> >> > > Sure you can, and many of us do it in practice. C++ has
> >> > > destructors that are called automaticly at well defined
> >> > > points
> >> > the points are not always so well defined.
> IMO discounting UB and other blacklisted cases they are.
I was thinking about things like shared_ptr, which are
definitely undeterministic. More importantly, of course, the
points in which a destructor are called are very limited (except
for explicit deletes), and often don't occur in places related
to the required lifetime of the object.
> >sorry, the points at which delete are called is well defined
> >with RAII, it's just the point at which an "entity object" is
> >released is not specified by a FIFO protocol and hence RAII
> >is not a complete solution.
> If you mean the "is-initialization" part, sure, it covers only
> a subset of cases.
He explicitly said "is released". He's not mentionned creation.
> CallManager::process_event (std::auto_ptr<Event> event)
> {
> std::auto_ptr<Call> call(0);
> if (is_new_call (event))
> {
> call = CallFactory::create_call (event);
> CallList::add_call (call);
> }
> else
> {
> call = CallList::lookup_call (event);
> }
> call->process_event(event);}
> <<
> This does not make much sense to me, id call_list is the
> manager/owner of calls, you don't use auto_ptr like that, and
> especially don't place the result of lookup in auto_ptr.
> There must be exactly one owner. Maybe you meant shared_ptr,
> but it looks like overkill too.
Yep. For this, a raw pointer is the most appropriate solution.
(Long term. I'd use an auto_ptr between the
CallFactory::create_call and the successful addition of the call
to the list.)
In the case of a call, the "owner" is the call itself. No other
owner makes sense.
> And this is a good example what client code shall NOT do by
> any means -- leave call management with the call manager. So
> the whole thing becomes a one-liner:
> CallList::get_call(event).process_event(event);
> get_call within the manager can see whether it is existing or
> a new call, create as necessary and return one by ref.
Maybe. There are different levels, and at the lowest level, you
don't want to rewrite std::map so that it will handle creation of
new objects. Note that Call is probably a virtual base class,
and CallFactory will create different derived classes according
to information in the event. And of course, what makes it a new
call depends on data in the event as well, and possibly the
state of other objects.
The example is considerably simplified. Consider the off-hook
event, which occurs when you lift the receiver off the hook: if
the current status of the terminal is "ringing", then you
process the event with the call object which generated the
"ringing" status; if the current status is inactive, you create
a new call object. In at least one possible design, you would
in fact first obtain an equipment object from the event, and ask
it for the call object. But once created, the call object
manages itself.
--
James Kanze