Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C - emulate 'mutable' from C++

I have a C structure like this:

struct my_struct {
   int i;
   double d;
   struct expensive_type * t;
};

An instance of this structure is created and initialized as:

struct my_struct * my_new( int i , double d) 
{
    struct my_struct * s = malloc( sizeof * s);
    s->i = i;
    s->d = d;
    s->t = NULL;
    return s;
}   

Calculating the struct expensive_type * t member is quite expensive, and might not be needed - it is therefor just initialized to NULL - and later calculated on demand:

const struct expensive_type * my_get_expensive( const struct my_struct * s) 
{
    if (!s->t)
       s->t = my_expensive_alloc( s->i , s->d );
    return s->t;  
 }

In C++ I would have used mutable on the struct expensive_type *member, is it possible to achieve something similar in C, i.e. casting away the const locally:

{
    struct my_struct * mutable_s = (struct my_struct*) s;
    mutable_s->t = ...;

  }

Or is removing const in the signature my only standard-compliant alternative?

like image 318
user422005 Avatar asked May 09 '17 21:05

user422005


1 Answers

You could(1) restructure your code and add a layer of indirection:

struct expensive; // Forward declaration, ignore
// One could also use a struct expensive * (a pointer) instead
// of this structure. IMO giving it a name is the better option.
struct expensive_handle {
  struct expensive * target;
};

// Store the simple data members as usual, store a pointer to a
// handle (pointer) to the expensive ones
struct my_struct {
  int simple;
  struct expensive_handle * handle;
};

struct expensive {
  int content; // whatever
};

Creating a my_struct must create the additional pointer/handle used for the indirection:

struct my_struct * new() {
  struct my_struct * data = malloc(sizeof(*data));
  // Error handling please
  // Set simple data members
  data->handle = malloc(sizeof(*(data->handle)));
  // Error handling please
  data->handle->target = NULL;
  return data;
}

The target member (which will point to the expensive data once it is computed) is set to NULL initially.

Accessing (and thus possibly lazy computation of) the expensive data members is then possible even with a const qualified my_struct, because no data member of that my_struct is changed:

int get_expensive(struct my_struct const * ptr) {
  if (ptr->handle->target == NULL) {
    ptr->handle->target = malloc(sizeof(struct expensive));
    // Error handling please
    puts("A hell of a computation just happened!");
    ptr->handle->target->content = 42; // WOO
  }
  return ptr->handle->target->content;
}

The only thing that changes is the data member of *(ptr->handle), a struct expensive_handle. Which is not const qualified (only the pointer to it named handle is).

Test (Live on ideone):

int main(void) {
  struct my_struct * p = new();
  printf("%d\n", get_expensive(p));
  printf("%d\n", get_expensive(p));
}

(1) Whether this is reasonable or a complete waste of resources (both programmer and computation) cannot be decided from your dummy example, though.

like image 125
Daniel Jour Avatar answered Nov 10 '22 23:11

Daniel Jour