How to call c-style cleaner functions implicitly?

137 views Asked by At

I am working on some c Apis and I always have to check some variables are initialized and then clear/destroy/free them using special functions. such as allocation :

ogg_stream_state os;
ogg_stream_init(&os,ogg_page_serialno(&og));

and destroying:

ogg_stream_clear(&os);

I want to call the cleaner function automatically and not explicitly.

3

There are 3 answers

3
Behrouz.M On

Using C++ Templates you can do it easily:

template<typename ARG, typename RET>
class Destroyer
{
public:
    typedef RET (*DestoyerFn)(ARG*);
    Destroyer(DestoyerFn destroyer_fn, ARG* object_ptr) { objectPointer = object_ptr; destroyerFn = destroyer_fn;}
    ~Destroyer()
    {
        if(destroyerFn && objectPointer)
            destroyerFn(objectPointer);
    }
private:
    DestoyerFn destroyerFn;
    ARG* objectPointer;
};

ARG is the argument of your cleaner function, and RET is the return type of that (RET needed to avoid compiler warning.)

example call:

Destroyer<ogg_stream_state, int> des_ogg_stream(ogg_stream_clear, &os);

now every where you like, just return from your function, it will call your cleaner function.

0
Michael Anderson On

I'd consider wrapping ogg_stream_state with a shared_ptr with custom destructor.

class OggStreamState {
public:
  shared_ptr<ogg_stream_state> state;
  OggStreamState() : 
    state(new ogg_stream_state, &ogg_stream_clear)
    {}
};

Your code would now look like this:

OggStreamState os;
ogg_stream_init(os.state.get(),ogg_page_serialno(&og));

Which is a little ugly, but this technique gives a logical place to start moving to an object oriented interface rather than a C function based one.

For example you could then move ogg_stream_init into OggStreamState so that it would become

OggStreamState os;
os.init(ogg_page_seialno(&og));

Take it one step further and repeat for the ogg_page, and you'd get

OggPage og = ...;
OggStreamState os;
os.stream_init(og.serialno());

You could even pull the init all the way into the constructor

OggStreamState os(og.serialno());

or at the extreme...

OggStreamState os(og);

Another advantage of this over a pure sentry RAII (like the solution from Lundin) is that you can pass the OggStreamState in and out of functions with out trouble. The compiler will determine when your last reference is destroyed and call the clear function for you. i.e. you can safely have a

OggStreamState oss = function_that_returns_a_stream_state(...);

Of course this technique does introduce other overheads, but usually they are minimal - also it does blur the ownership of the ogg stream slightly, which many or may not be a good thing...

0
Lundin On

In a real-world scenario you most likely want some kind of custom wrapper around the C functions, to encapsulate them and to dodge C like behavior and oddities such as calling convention.

In the real world, I don't believe you can treat any C code as "a generic C API" and design some template class which can handle all possible C APIs. There are far too many things to consider to make such a generic class feasible.

For example, given the following random C code:

//cfile.c
static int* something;

void cfunction_init (void)
{
  printf("C function init\n");
  something = (int*) malloc(sizeof(*something));
}

void cfunction_cleanup (void)
{
  printf("C function cleanup\n");
  free(something);
}

You can make a wrapper class like this:

class wrapper
{ 
  public:
    wrapper()  { cfunction_init(); } 
    ~wrapper() { cfunction_cleanup(); }
}; 

Then simply declare a wrapper class variable at the appropriate scope:

#include <iostream>

int main()
{
  wrapper w;

  std::cout << "C++ program executing" << std::endl;

  return 0;
}

Program output:

C function init
C++ program executing
C function cleanup