The 'finally' debate

P

Peter Koch Larsen

Stefan Arentz said:
Neh, we keep the beer in the fridge. The device is a MIPS based device
with not too much RAM/Flash. Think <= 8MB. which needs to be shared
with a kernel, libraries some tools.

It is not very special, you just can't use all nice tricks that are
obvious on a normal 1GB workstation with a standard 80GB drive :)

S.

Ahhh... that is lots and lots of space for C++.

/Peter
 
D

Dietmar Kuehl

Stefan said:
Wrapper classes introduce more code.

I'd rate this as FUD. I just tested the following two translation units
with gcc on a StrongARM (I currently don't have access to gcc on an
Intel machine):

| // file tst1.cpp
| extern void f(int* i);
| void g() {
| int* i = new int(10);
| try { f(i); }
| catch ( ... ) {}
| delete i;
| }

This is as close at "finally" as possible: the try/catch is necessary
or some equivalent is necessary for the finally-clause, too. However,
deleting and rethrowing in the catch-clause would increase the code
even more (see tst3.cpp below).

| // file tst2.cpp
| extern void f(int* i);
| struct ptr {
| ptr(int* p): mp(p) {}
| ~ptr() { delete mp; }
| int* mp;
| };
| void g() {
| ptr p(new int(10));
| f(p.mp);
| }

It is admittedly more code but it can be placed into a header and
reused
in multiple places.

| // file tst3.cpp
| extern void f(int* i);
| void g() {
| int* i = new int(10);
| try { f(i); delete i; }
| catch ( ... ) { delete i; throw;}
| }

This is the manual approach without finally.

| // file tst4.cpp
| extern void f(int* i);
| template <typename T>
| struct ptr {
| ptr(T* p): mp(p) {}
| ~ptr() { delete mp; }
| T* get() { return mp; }
| private:
| ptr(ptr const&);
| void operator= (T const&);
| T* mp;
| };
| void g() {
| ptr p(new int(10));
| f(p.get());
| }

.... and I thought it is instructive to put this follow-blown version
using templates into it, too.

Compiled with "g++ -O4 -c tst*.cpp" I get the following:

| size *.o
| text data bss dec hex filename
| 172 16 0 188 bc tst1.o
| 180 6 0 186 ba tst2.o
| 228 16 0 244 f4 tst3.o
| 180 6 0 186 ba tst4.o

(I couldn't paste the results as they are on my PDA which is not
connected
to the machine I'm typing at).

Although the memory wasted by the non-automatic version is not too bad,
the automatic release is shorter, at minimum two bytes - not much but
you complained about use less code. Of course, if you want the correct
semantics, i.e. the exception shall not be swallowed, you have no
choice but go with automatic resource release if you don't want to
waste memory...
I would like to use less code. I also
think it is a workaround and not a structural solution.

I disagree: the idiom of handling resources is essential to effective
C++ programming.
The above would simply move the exception handling and my finally problem
to a different place but it would still be present.

Where does the resource management class need a "finally"? All
exception
handling code is entirely generated by the compiler which will probably
generate something akin to finally but there is no need to care at all.
Also, resource management is centralized in one place. There is no
chance
of forgetting to clean up allocated resource (well, you can have a
plain
new but you shouldn't) because the finally clause is omitted. The code
is
not littered with clean-up code which is irrelevant to the actual
business
logic.
I like it that you as
a user of the class don't have to deal with it anymore, but that is more
visual/convenience.

I disagree. Actually, if you are ever doing template programming you
will
appreciate that you don't have to know how to clean-up the objects you
are handling. You don't even need to care whether they need clean-up:
it
is the object's business, not yours. It is a logical step toward
encapsulation.
 
D

Dietmar Kuehl

Nicolas said:
void bar()
{
char * my_ptr;

Object should always be initialized, i.e. the above line shall be
replaced
by something like this if used at all:

| char * my_ptr = 0;
try
{
my_ptr = new char[987];
// do something
delete my_ptr

You allocated an array object you shall release an array object or
suffer
the effects of undefined behavior. The only correct way to delete an
array
object is using array delete:

| delete[] my_ptr;

This is explicit in 5.3.5 (expr.delete) paragraphs 2 and 3 of the
standard.
If you disagree with my statement, please provide a quote from the
standard
supporting your view.
Here it's necessary to duplicate code, another example would be file
handling.
Sometimes a finally - block can help.

Resource handling classes foster encapsulation and provide a better
approach
than a finally block could do. This was considered sufficient reason
for not
having finally blocks.
 
D

Dietmar Kuehl

Dietmar said:
I just tested the following two translation units...
| ^^^

As it turns out, there are three kinds of persons: those who can count
and those who can't... I wrote this sentence when I wanted to post
just two translation units but it turned out to be reasonable to have
a total of four.
 
R

red floyd

Rolf said:
That's what std::auto_ptr is for:

void bar()
{
try
{
SomeObject instace;
std::auto_ptr<char> my_ptr(new char[987]);
// do something
instace.doSomehting() // throws an exception let's say MyExec
}
catch(MyExec &exc)
{
// error handling
}
}

That's double-plus-ungood Rolf. As I understand it, std::auto_ptr<>'s
destructor calls delete, not delete[]. So you invoked UB.

I suspect that with an array of char, it's probably OK (but not
necessarily), but suppose you did that with some class that had a
nontrivial destructor?
 
S

Stefan Arentz

Jeff Flinn said:
probably

Is this because your compiler doesn't support STL/Boost? Or that you "think"
STL\Boost will require more memory?

It is because both memory and flash is sparse. And I know for a fact that it
does not fit.

S.
 
R

Richard Herring

Stefan Arentz said:
Well, it is code for firmware of a small device. Not very small, but small
enough that something like STL or Boost is not an option. Templates probably
are,

??? Both STL and Boost _are_ mostly templates.
but I've had no reason to use them yet and I would have to look into
object code size first.

Unlikely to be significantly more than whatever you code manually.
Btw, I am very happy with the choise of C++ for this project. It has made
the code more robust and organized.


So that would mean stack based objects and references? Get rid of all
pointers?

Not necessarily. Your stack based objects can still contain pointers,
but they take care of all the pointer management for you.
It would probably mean a complete redesign of some things,
but I am willing to look into it. Is this the RAII stuff other people
were talking about?
Just so.
 
S

Stefan Arentz

....
I do not understand what you're saying. STL - or templates - does not
necessarily use more ressources than handwritten code. In your case it
should be safe.

Templates, yes probably. But including a 700K library in the firmware is
simply not an option.

S.
 
R

Richard Herring

Stefan Arentz said:
...


I don't buy this for two reasons.

Wrapper classes introduce more code. I would like to use less code. I also
think it is a workaround and not a structural solution.

The above would simply move the exception handling and my finally problem
to a different place but it would still be present.

No, there _is_ no exception handling code. Your destructor just
contains the normal cleanup code that you'd need anywhere.
 
I

Ioannis Vranos

Dietmar said:
Object should always be initialized, i.e. the above line shall be
replaced
by something like this if used at all:

| char * my_ptr = 0;


Why? This is a personal style and not mandatory.
 
P

Peter Koch Larsen

Stefan Arentz said:
...


Templates, yes probably. But including a 700K library in the firmware is
simply not an option.

S.

700K? What do you mean? I really would like to know how you get these
numbers. The stuff that could take up some space is iostreams - if there is
a lot of locale support, but you could skip that or trim it. Apart from that
you should be in the clear.

/Peter
 
I

Ioannis Vranos

Stefan said:
Neh, we keep the beer in the fridge. The device is a MIPS based device
with not too much RAM/Flash. Think <= 8MB. which needs to be shared
with a kernel, libraries some tools.

It is not very special, you just can't use all nice tricks that are
obvious on a normal 1GB workstation with a standard 80GB drive :)


8MB? C++ programs can execute in DOS systems with <= 640 KB RAM. And
that is too much too.
 
J

Jeff Flinn

Ioannis Vranos said:
Why? This is a personal style and not mandatory.

It is "mandatory" if you want exception safe code. The above, without "=0"
is equivalent to:

char* my_ptr = rand();

What happens if/when new throws?

Jeff F
 
D

Dietmar Kuehl

Ioannis said:
Why? This is a personal style and not mandatory.

Correct, it is not mandatory. However, this "personal" style
reduces the potential for errors dramatically (not only in C++).
It also makes certain, IMO ill-advised, programming approachs,
like e.g. use of "try"-blocks, quite inattractive :)
 
S

Stefan Arentz

Ioannis Vranos said:
8MB? C++ programs can execute in DOS systems with <= 640 KB RAM. And
that is too much too.

Yes, you can run C++ programs on a AVR with 16K flash too, but that is
not the point.

Sorry but no more smart ass answers on this one please. I *know* what
kind of memory constraints I have in my project.

S.
 
I

Ioannis Vranos

Dietmar said:
Correct, it is not mandatory. However, this "personal" style
reduces the potential for errors dramatically (not only in C++).


As I have told many times in clc++, not really.


It also makes certain, IMO ill-advised, programming approachs,
like e.g. use of "try"-blocks, quite inattractive :)


May you expand on that?
 
P

Phlip

Niels said:
Cleanup code are not part of the program logic, so it is very nice to avoid
it in the middle of the main code by putting it away in a destructor.
In addition the destructor approach prevents programmers from forgetting
cleanup code which leads to more stable applications.
So it is not a workaround but a significant improvement.

Totally.

Designs should not duplicate behaviors. If two functions have the same lines
in their finally blocks, they duplicate those lines. Moving these to a
non-deterministic destructor simplifies the design.

Java's reason to exist is C++ memory leaks. Then they solved the wrong
problem. Non-deterministic destructors cause all kinds of trouble, leading
to klutzy work-arounds.
 
J

Jeff Flinn

Stefan Arentz said:
It is because both memory and flash is sparse. And I know for a fact that it
does not fit.

Please adequately define "it"?

In the context of using std facilities in lieu of "finally", you'd only use
boost::scoped_array_ptr, or just use std::vector. With an optimizing
compiler, the size differences should be insignificant. Or by chance are you
looking at debug mode builds?

Jeff F
 
T

Tom Widmer

So that would mean stack based objects and references? Get rid of all
pointers?

Rather, make sure any pointers are controlled by objects.

It would probably mean a complete redesign of some things,
but I am willing to look into it. Is this the RAII stuff other people
were talking about?

Yes. Basically, rather than:

Foo* f = 0;
try
{
f = new Foo();
f->bar();
}
catch(...)
{
delete f;
throw;
}
delete f;


You would do:
Foo f;
f.bar();

or, if necessary,

std::auto_ptr<Foo> f(new Foo);
f->bar();


Tom
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top