C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Design decisions
Goto page 1, 2, 3  Next
 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C Language
View previous topic :: View next topic  
Author Message
jacob navia
Guest





PostPosted: Thu Jun 21, 2012 6:19 pm    Post subject: Design decisions Reply with quote



In the new "templated" form of the containers library I have now that
all data retrieving functions return the data directly:

int (*GetElement)(intList *l,size_t idx);

The problem is that it is not possible to return an error
with this configuration: Since the return value is an
int, any error code could be actually some data!!

The solution is to return a pointer to the data and NULL
if there is an error, as the other functions of the library do.

But this doesn't look great with the syntax viewed from the
user's side:

Instead of:
int m = iintList.GetElement(mylist,23);
you should write:
int m = *iintList.GetElement(myList,23);

risking a core dump if there is a NULL return.

Obviously if the list has only 20 elements, the core dump
is actually a good thing since it will show you the error immediately.

Before returning, the "GetElement" API calls the error function
and will return ONLY IF the error function returns.

I was relying in that as error handling, and just returning zero...

After some reflection I am convinced now that this would be a bad design
decision.

What do you think?

jacob
Back to top
Andrew Cooper
Guest





PostPosted: Thu Jun 21, 2012 6:43 pm    Post subject: Re: Design decisions Reply with quote



On 21/06/2012 21:19, jacob navia wrote:
Quote:
In the new "templated" form of the containers library I have now that
all data retrieving functions return the data directly:

int (*GetElement)(intList *l,size_t idx);

The problem is that it is not possible to return an error
with this configuration: Since the return value is an
int, any error code could be actually some data!!

The solution is to return a pointer to the data and NULL
if there is an error, as the other functions of the library do.

But this doesn't look great with the syntax viewed from the
user's side:

Instead of:
int m = iintList.GetElement(mylist,23);
you should write:
int m = *iintList.GetElement(myList,23);

risking a core dump if there is a NULL return.

Obviously if the list has only 20 elements, the core dump
is actually a good thing since it will show you the error immediately.

Before returning, the "GetElement" API calls the error function
and will return ONLY IF the error function returns.

I was relying in that as error handling, and just returning zero...

After some reflection I am convinced now that this would be a bad design
decision.

What do you think?

jacob

(Other than the fact you appear to be trying to get C++ style code into C)

One design you could use is:

bool_t (*GetElement)(intList *l, size_t idx, int * dst);

so your code looks like:
int m;
if ( iintList.GetElement(myList, 23, &m) == FALSE )
// Some error
else
// All Good

~Andrew
Back to top
BartC
Guest





PostPosted: Thu Jun 21, 2012 7:02 pm    Post subject: Re: Design decisions Reply with quote



"jacob navia" <jacob (AT) spamsink (DOT) net> wrote in message
news:jrvvlb$8k1$1 (AT) speranza (DOT) aioe.org...
Quote:
In the new "templated" form of the containers library I have now that all
data retrieving functions return the data directly:

int (*GetElement)(intList *l,size_t idx);

The problem is that it is not possible to return an error
with this configuration: Since the return value is an
int, any error code could be actually some data!!

The solution is to return a pointer to the data and NULL
if there is an error, as the other functions of the library do.
....
What do you think?

How does C++ deal with it?

My feeling is that GetElement should be as simple and convenient to use as
possible, especially for the 99.999% of calls (and usually 100%) where there
is no error. Perhaps have two versions, one where any errors are handled
internally (either aborting, returning an agreed default value, or
whatever), and another which has full error returns for people who insist on
checking every single list access themselves.

--
bartc
Back to top
Gareth Owen
Guest





PostPosted: Thu Jun 21, 2012 8:23 pm    Post subject: Re: Design decisions Reply with quote

"BartC" <bc (AT) freeuk (DOT) com> writes:

Quote:
How does C++ deal with it?

Most (all?) C++ STL containers give a bounds-checked accessor that
returns a reference on success, but throws an exception on out-of-bounds
access (I assume that's the main failure condition Jacob has in mind for
GetElement also).

Exceptions give you a return path which, when combined with well
encapsulated objects, automatically cleans up any resources. Of course,
writing such exception-safe objects is a non-trivial undertaking.

jacob navia <jacob (AT) spamsink (DOT) net> writes:
Quote:
Before returning, the "GetElement" API calls the error function and
will return ONLY IF the error function returns.

What do you have in mind for a well designed, non-returning error
function. Obviously one that calls abort()/exit() would work but what
else?

Could the error function provide a default value (e.g. out-of-bounds
access always returns a 0 value, or attempts to expand the vector and
pad with zero)?

Or were you thinking in terms of longjmp()ing to some prior state?
Back to top
James Kuyper
Guest





PostPosted: Thu Jun 21, 2012 8:43 pm    Post subject: Re: Design decisions Reply with quote

On 06/21/2012 05:02 PM, BartC wrote:
Quote:
"jacob navia" <jacob (AT) spamsink (DOT) net> wrote in message
news:jrvvlb$8k1$1 (AT) speranza (DOT) aioe.org...
In the new "templated" form of the containers library I have now that all
data retrieving functions return the data directly:

int (*GetElement)(intList *l,size_t idx);

The problem is that it is not possible to return an error
with this configuration: Since the return value is an
int, any error code could be actually some data!!

The solution is to return a pointer to the data and NULL
if there is an error, as the other functions of the library do.
...
What do you think?

How does C++ deal with it?

Of the C++ standard containers, only basic_string, array, deque, and
vector containers have a close equivalent of GetElement. The other
container types do not provide int-indexed access. The ones that do,
each have two GetElement equivalents. If Container is one of those
templates, then given the declaration:

Container<T> container;

the expressions container[idx] and container.at(idx) both have the
semantics of *(container.begin()+n). If idx is out of range,
container[idx] has undefined behavior; container.at(idx) throws
std::out_of_range.
Back to top
io_x
Guest





PostPosted: Fri Jun 22, 2012 4:35 am    Post subject: Re: Design decisions Reply with quote

"jacob navia" <jacob (AT) spamsink (DOT) net> ha scritto nel messaggio
news:jrvvlb$8k1$1 (AT) speranza (DOT) aioe.org...
Quote:
In the new "templated" form of the containers library I have now that all data
retrieving functions return the data directly:

int (*GetElement)(intList *l,size_t idx);

The problem is that it is not possible to return an error
with this configuration: Since the return value is an
int, any error code could be actually some data!!

in how i see it: -1 in return for one function means error
if -1 is a sensible data for return to caller; use:
u32 (*GetElement)(i32* result, intList *l,size_t idx);

i32 result;
if(getelemte(&result etc)==-1) error()

separate error code from data...

it will good every world function follow these laws
but even my not follow them... now too, i don't know why...

desclaimer: there are many experts here, i'm just a beginner
i don't know what error follow from my vision
of software...
Back to top
jacob navia
Guest





PostPosted: Fri Jun 22, 2012 5:12 am    Post subject: Re: Design decisions Reply with quote

Le 22/06/12 00:23, Gareth Owen a écrit :
Quote:

What do you have in mind for a well designed, non-returning error
function. Obviously one that calls abort()/exit() would work but what
else?


Obviously a longjmp combined with a prior setjmp can establish a
recovery point where all errors in a program (or in a part of the
program) would end.

If you use pooled memory access you can release the memory pool
associated with the current error, and you have the exact mechanism
of the C++ exceptions combined with cleanup by stack unwinding in
a much cheaper and efficient way.

Quote:
Could the error function provide a default value (e.g. out-of-bounds
access always returns a 0 value, or attempts to expand the vector and
pad with zero)?


I had this strategy but it is unworkable since you can't know in
advance if a zero will appear in the data!

Attempting to expand the vector is also a bad decision because:

1) In case the error is an index error you are just masking the error
making it more difficult to find. GetElement retrieves the element's
data but you have no data for an inexistent element so that you fake
it. But then the rest of the software that expects that data point
will give WRONG RESULTS!

Debugging a wrong result bug is orders of magnitude more difficult
than debugging a simple crash.

2) If you get a huge index (say, 75876) you will start trying to
allocate 75876-20 elements and you could get a memory allocation
failure, making a double fault!

3) If you expand the vector you are probably overstretching the used
memory, specially if the wrong index is too big, provoking a memory
failure later in the code. Then, you will have to start figuring
out why you are using so much memory, and it could take a LONG while
until you figure out that a wrong index forced you to allocate
several megabytes of vector space...
Back to top
Ian Collins
Guest





PostPosted: Fri Jun 22, 2012 5:44 am    Post subject: Re: Design decisions Reply with quote

On 06/22/12 07:12 PM, jacob navia wrote:
Quote:
Le 22/06/12 00:23, Gareth Owen a écrit :

What do you have in mind for a well designed, non-returning error
function. Obviously one that calls abort()/exit() would work but what
else?


Obviously a longjmp combined with a prior setjmp can establish a
recovery point where all errors in a program (or in a part of the
program) would end.

If you use pooled memory access you can release the memory pool
associated with the current error, and you have the exact mechanism
of the C++ exceptions combined with cleanup by stack unwinding in
a much cheaper and efficient way.

Er, no you don't. Allocated memory isn't the only thing you may want to
clean up.

This situation is much the same as the early return cases discussed at
great length on the recent goto thread.

--
Ian Collins
Back to top
jacob navia
Guest





PostPosted: Fri Jun 22, 2012 6:08 am    Post subject: Re: Design decisions Reply with quote

Le 22/06/12 10:00, gwowen a écrit :
Quote:
On Jun 22, 8:12 am, jacob navia <ja...@spamsink.net> wrote:
If you use pooled memory access you can release the memory pool
associated with the current error, and you have the exact mechanism
of the C++ exceptions combined with cleanup by stack unwinding in
a much cheaper and efficient way.

Really? Do you really believe that? What about resources that are not
memory - mutex/critical sections, network connections, open ports,
file descriptors.

How would I use your error handler to do this:


[snip code]


Very easy. Just use a pushdown stack. Each time you acquire a
mutex lock you push it in the stack, each time you release it you pop it.

If an exception occurs, you pop the whole mutex stack. This would
require just a few lines more of code.
Back to top
jacob navia
Guest





PostPosted: Fri Jun 22, 2012 6:42 am    Post subject: Re: Design decisions Reply with quote

Le 22/06/12 10:00, gwowen a écrit :
Quote:
I had this strategy but it is unworkable since you can't know in
advance if a zero will appear in the data!
But it might be desirable for a user to be able to specify such
behaviour in his error handler. For example, in signal processing its
often useful to assume that the data-array is zero-padded-to-infinity,
so I may use a C++ object like this:

class zero_padded_array
{
private:
public std::vector<double> v_;

public:
const double& at(size_t idx) const {
if(idx >= v_.size()){
return 0.0;
} else {
return v_[idx];
}
}
// Rest of interface.
}

Can I use an error handler to handle out-of-bounds access in you
library to get that behaviour?


Yes.

Method 1: Modify a vector's VTable (long)

You write a new "GetElement" function:


typedef void *(*fnPtr)(Vector *,size_t);

fnPtr oldGetElementFunction;

static double error_return = 0.0;

static double *MyGetElement(doubleVector *v,size_t idx)
{
if (idx >= iVector.Size(v))
return &error_return;
// Call the original function;
return oldGetElementFunction(v,idx);
}

Now, when you want to create an infinite vector you write:

Vector *CreateExtensibleVector(size_t realSize)
{
VectorInterface *intf;
Vector *result = idoubleVector.Create(20); // 20 real elements
// Save the original function to be used later
oldGetElementFunction = result->VTable->GetElement;
intf = malloc(sizeof(VectorInterface));
memcpy(intf,result->VTable,sizeof(VectorInterface));
result->VTable = intf;
// Replace the GetElement in THIS vector
result->VTable->GetElement = MyGetElement;
result->Vtable->Finalize = MyFinalize;
return result;
}

Instead of writing iVector.GetElement you use the macro:

#define iVector_GetElement(vector,idx) \
vector->VTable->GetElement(vector,idx);

That will work with normal vectors AND with extensible ones.
Note that the trivial new Finalize procedure is skipped. It just frees
the copied VTable allocated with malloc.

True, this approach is not as easy as the C++ one, but it has the
advantage of being much more flexible. You can subclass before
or after calling the original procedure, or you can bypass the
called procedure entirely.

Method 2: Shorter
Make ALL vectors extensible:
Vector *CreateExtensibleVector(size_t realSize)
{
Vector *result = idoubleVector.Create(20); // 20 real elements
// Save the original function to be used later
oldGetElementFunction = result->VTable->GetElement;
// Replace the GetElement in ALL vectors
iVector.GetElement = MyGetElement;
return result;
}
-----------------------------------------------------------------------------

Note that I haven't answered your original question:

Quote:
Can I use an error handler to handle out-of-bounds access in you
library to get that behaviour?

I think you have a point here. I will modify the behavior of the
GetElement function so that it RETURNS the value returned by the
error handling procedure (if that procedure returns of course).

The return value of the error handling function should be a pointer
to a replacement data, or NULL or whatever.

Thanks for your input. Really appreciated.

jacob
Back to top
gwowen
Guest





PostPosted: Fri Jun 22, 2012 8:00 am    Post subject: Re: Design decisions Reply with quote

On Jun 22, 8:12 am, jacob navia <ja...@spamsink.net> wrote:
Quote:
Le 22/06/12 00:23, Gareth Owen a écrit :
What do you have in mind for a well designed, non-returning error
function.  Obviously one that calls abort()/exit() would work but what
else?

Obviously a longjmp combined with a prior setjmp can establish a
recovery point where all errors in a program (or in a part of the
program) would end.

If you use pooled memory access you can release the memory pool
associated with the current error, and you have the exact mechanism
of the C++ exceptions combined with cleanup by stack unwinding in
a much cheaper and efficient way.

Really? Do you really believe that? What about resources that are not
memory - mutex/critical sections, network connections, open ports,
file descriptors.

How would I use your error handler to do this:

extern DAQ_source* daq;

int npieces = 100;

while(true){
try {
std::vector<std::vector<uint8_t> datapts;
for(int i=0; i<npieces; ++i){
std::vector<uint8_t> signal;
{
// Acquire the next signal from DAQ
Mutex DAQ_lock(daq); // mutex lock acquired
signal = daq->acquire(); // can generate errors
// mutex lock released
}
Filter multitap; // can throw
multitap.process(signal); // can generate errors
datapts.push_back(signal); // can generate errors
}

analyse(datapts);
}
catch(const std::runtime_error& e)
{
// all resources automatically cleaned up
log << e.what();
sleep(100); // retry on error
}
}

Quote:
Could the error function provide a default value (e.g. out-of-bounds
access always returns a 0 value, or attempts to expand the vector and
pad with zero)?

I had this strategy but it is unworkable since you can't know in
advance if a zero will appear in the data!

But it might be desirable for a user to be able to specify such
behaviour in his error handler. For example, in signal processing its
often useful to assume that the data-array is zero-padded-to-infinity,
so I may use a C++ object like this:

class zero_padded_array
{
private:
public std::vector<double> v_;

public:
const double& at(size_t idx) const {
if(idx >= v_.size()){
return 0.0;
} else {
return v_[idx];
}
}
// Rest of interface.
}

Can I use an error handler to handle out-of-bounds access in you
library to get that behaviour?

Quote:
Attempting to expand the vector is also a bad decision because:

1) In case the error is an index error you are just masking the error
   making it more difficult to find. GetElement retrieves the element's
   data but you have no data for an inexistent element so that you fake
   it. But then the rest of the software that expects that data point
   will give WRONG RESULTS!

You misunderstand me. My point is that insertion

e.g. In Matlab I can say:

vec = [1 2 4 8];
vec(6) = 9; % inserting out-of-bounds automatically expands the vector
% vec is now [1 2 4 8 0 9];
x = vec(9); % retrieving out-of-bounds is an error


class resizing_array
{
private:
public std::vector<double> v_;

public:
double& at(size_t idx) {
if(idx >= v_.size()){
v_.resize(idx+1);
}
return v_[idx];
}
// Rest of interface....
}

Can I use an error handler to get that behaviour? Exceptions make it
easy.

Quote:
2) If you get a huge index (say, 75876) you will start trying to
    allocate 75876-20 elements and you could get a memory allocation
    failure, making a double fault!

I regularly create C++ matrices with far in excess of 76000 elements -
occasionally even as temporaries - knowing that properly designed
exceptions make memory allocation failure easy to handle. And since
these are designed to run on 64-bit processors, I know that memory
failure is actually unlikely.

Quote:
3) If you expand the vector you are probably overstretching the used
    memory..

It works really well in matlab and C++. Exceptions make it easy.
Back to top
Neil Cerutti
Guest





PostPosted: Fri Jun 22, 2012 10:40 am    Post subject: Re: Design decisions Reply with quote

On 2012-06-21, jacob navia <jacob (AT) spamsink (DOT) net> wrote:
Quote:
In the new "templated" form of the containers library I have
now that all data retrieving functions return the data
directly:

int (*GetElement)(intList *l,size_t idx);
...
What do you think?

Taking a step back, why not just return garbage for bad indices?
That seems more like what a C programmer would expect.

--
Neil Cerutti
Back to top
Ben Bacarisse
Guest





PostPosted: Fri Jun 22, 2012 11:01 am    Post subject: Re: Design decisions Reply with quote

Neil Cerutti <neilc (AT) norwich (DOT) edu> writes:

Quote:
On 2012-06-21, jacob navia <jacob (AT) spamsink (DOT) net> wrote:
In the new "templated" form of the containers library I have
now that all data retrieving functions return the data
directly:

int (*GetElement)(intList *l,size_t idx);
...
What do you think?

Taking a step back, why not just return garbage for bad indices?
That seems more like what a C programmer would expect.

In some contexts, that's a good solution. I remember implementing a
simple scripting language that had typed containers. The 'get' and
'put' operations acted on pointers, but 'get' never returned a NULL
pointer. Every container always had a spare element, a pointer which
was returned by get for out-of-bounds indexes, and whose value would be
used for dereferenced out-of-bounds accesses.

The case is more murky for a C container API, but since Jacob also
provides the generic (untyped) interface, I think returning junk (zero?)
could be justified. Programs that need to know every error condition
can use the full interface.

Jacob might also want to consider having a dummy element. The most
efficient plan would probably be to have a single dummy element for each
size (properly aligned for all accesses of course). An erroneous lookup
would return this pointer rather than NULL. It could be tested for,
just like, NULL, but it would be valid to dereference it. Such a scheme
can simplify things a bit, but I'm not familiar enough with the existing
API to be sure.

--
Ben.
Back to top
Neil Cerutti
Guest





PostPosted: Fri Jun 22, 2012 12:26 pm    Post subject: Re: Design decisions Reply with quote

On 2012-06-22, gwowen <gwowen (AT) gmail (DOT) com> wrote:
Quote:
On Jun 22, 1:40?pm, Neil Cerutti <ne...@norwich.edu> wrote:
On 2012-06-21, jacob navia <ja...@spamsink.net> wrote:

...
What do you think?

Taking a step back, why not just return garbage for bad indices?
That seems more like what a C programmer would expect.

Certainly "undefined behaviour", rather than returning garbage
is the most C-like thing to do. It "works" for arrays.

Returning garbage invokes undefined behavior, surely.

--
Neil Cerutti
"This room is an illusion and is a trap devisut by Satan. Go
ahead and dauntlessly! Make rapid progres!"
--Ghosts 'n Goblins
Back to top
gwowen
Guest





PostPosted: Fri Jun 22, 2012 1:01 pm    Post subject: Re: Design decisions Reply with quote

On Jun 22, 1:40 pm, Neil Cerutti <ne...@norwich.edu> wrote:
Quote:
On 2012-06-21, jacob navia <ja...@spamsink.net> wrote:

...
What do you think?

Taking a step back, why not just return garbage for bad indices?
That seems more like what a C programmer would expect.

Certainly "undefined behaviour", rather than returning garbage is the
most C-like thing to do. It "works" for arrays.
Back to top
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C Language All times are GMT
Goto page 1, 2, 3  Next
Page 1 of 3

 
 


Powered by phpBB © 2001, 2006 phpBB Group