3

I created two classes "DEVICE_s" and "DEVICE_SET_s" as following:

Device_Manager.h

typedef struct DEVICE_s DEVICE_s;
typedef struct DEVICE_SET_s DEVICE_SET_s;

Device_Manager.c

struct DEVICE_s
{
    uint32_t IP;
    TYPE_e Type;
    METHOD_e Method;
    GROUP_RULE_e GroupRule;
    char Name[NAME_SIZE];
};
struct DEVICE_SET_s
{
    uint8_t     Total;
    uint8_t     Used;
    uint8_t     Available;
    DEVICE_s    Set[SET_SIZE];
};
DEVICE_s Instance;
DEVICE_SET_s Objects;

Because I put these two classes within a same file, all functions that manipulate variables "Instance" and "Objects" are put together inside this file.

I think this way is bad considering to modularity, so I want to create another source file to separately manage class "DEVICE_SET_s", just like:

DeviceSet_Manager.h

typedef struct DEVICE_SET_s DEVICE_SET_s;

DeviceSet_Manager.c

#include "Device_Manager.h"
#include "DeviceSet_Manager.h"
struct DEVICE_SET_s
{
    uint8_t     Total;
    uint8_t     Used;
    uint8_t     Available;
    DEVICE_s    Set[SET_SIZE];    //Oops! Incomplete Type Is Not Allowed
};

However, In terms of DeviceSet_Manager.c, class "DEVICE_s" is not visible(not a complete type).

How do I fix this? Thanks

alk
  • 69,737
  • 10
  • 105
  • 255
Andy Lin
  • 397
  • 1
  • 2
  • 21
  • While modularity is extremely important, it's not always the most important. It's OK to have 2 classes in one fileset if they are closely related. – user694733 Jan 28 '19 at 10:12
  • Also, while encapsulation is *nice*, hiding the structure definition away from headers limits the memory allocation options for the user. This is huge downside for relatively minor advantages of encapsulation. – user694733 Jan 28 '19 at 10:16
  • What do you mean by "memory allocation options"? – Andy Lin Jan 28 '19 at 10:17
  • If I wanted to create instance of your class and your public type is incomplete, then I cannot create it myself. Only options are that instance is static in your module and pointer is given to me, or your module has `new` function which creates object for me using dynamic memory allocation. (If I am on embedded system malloc is often not available, leaving only static and automatic allocation options. So that is not possible) – user694733 Jan 28 '19 at 10:21
  • I do have a new function which passes the address of the static variable to users – Andy Lin Jan 28 '19 at 10:25
  • I intended to limit number of variable to 1, because only one instance is used by far. – Andy Lin Jan 28 '19 at 10:26
  • That is fine, if that fits your program design. But be aware that it comes with loss of flexibility. – user694733 Jan 28 '19 at 10:29
  • Yes. I did think of this way as not flexible, but I'm now have no time to figure out more flexible way... – Andy Lin Jan 28 '19 at 10:33
  • I might create a pool(array) of DEVICE_s for every user to get it, and they can manipulate their each instance via functions. – Andy Lin Jan 28 '19 at 10:36
  • And I'll again create a pool for DEVICE_SET_s too, so that user can specify which set is gonna be filled with their instance. – Andy Lin Jan 28 '19 at 10:39
  • "*How do I fix this?*" how to fix what please? The error the compiler stumbles over ("*not a complete type*"), or the unmet requirement your question's title mentions "*achieving information hiding?*"? – alk Jan 28 '19 at 15:21
  • @alk Hi, English is not my mother tongue. I have no idea about what you say. Can you please say it again in other words? – Andy Lin Jan 29 '19 at 01:22

2 Answers2

2

What you want are opaque types for

  • DEVICE
  • DEVICE_SET

This is straight forward the same way for both:

  • header, defining

    • incomplete type for object structure. A pointer to it is the opaque type to handle an object's instance and to be passed to its interfacing functions
    • interfacing functions' prototypes
  • implementation of

    • complete type
    • interfacing functions

headers

device.h

#ifndef DEVICE_H
#define DEVICE_H

struct device;

struct device * device_new(void);
void device_delete(struct device *);

#endif

device_set.h:

#ifndef DEVICE_H
#define DEVICE_H

#include "device.h"

struct device_set;

struct device_set * device_set_new(size_t);
void device_set_delete(struct device_set *);

int device_set_set_device(struct device_set *, size_t, struct device *);
struct device * device_set_get_device(struct device_set *, size_t); 


#endif

implementations

device.c

#include "device.h"

struct device {
  ...
};

struct device * device_new(void)
{
  struct device * pd = malloc(sizeof * pd);
  if (NULL != pd)
  {
    /* Init members here. */
  }

  return pd;
}

void device_delete(struct device * pd)
{
  if (pd)
  {
    /* de-init (free?) members here. */
  }

  free(pd);
}

device_set.c:

#include "device_set.h"

struct device_set
{
  size_t total;
  size_t used;
  size_t available; /* what is this for? isn't it just total - used? */
  struct device ** pd;
}

struct device_set * device_set_new(size_t nb)
{
  struct device_set pds = malloc(sizeof *pds);
  if (NULL != pds)
  {
    pds->pd = malloc(nb * sizeof *pds->pd);
    if (NULL == pds->pd)
    {
      free(pds);
      pds = NULL;
    }
    else
    {
      for (size_t d = 0; d < nb; ++d)
      {
        pds->pd[d] = NULL;
      }

      pds->total = nb;
      pds->used = 0;
      pds->available = 0;
    }
  }

  return pds;
}

void device_set_delete(struct device_set * pds)
{
  if (pds)
  {
    free(pds->pd);
    free(pds)
  }

  return;
}

int device_set_set_device(struct device_set * pds, size_t d, struct device * pd)
{
  int result = 0;

  if (pds->total <= d)      
  {
    result = ERANGE;
  }
  else
  {
    pds->pd[d] = pd;
  }

  return;
}    

struct device * device_set_get_device(struct device_set * pds, size_t d); 
  int result = 0;
  struct device * pd = NULL;

  if (pds->total <= d)      
  {
    result = ERANGE;
  }
  else
  {
    pd = pds->pd[d];
  }

  return pd;
}
alk
  • 69,737
  • 10
  • 105
  • 255
1

Here's what I usually do:

device.h

// insert header guards here

typedef struct DEVICE_s DEVICE_s;
struct DEVICE_s
{
   ...
};

// method declarations here
DEVICE_Init(DEVICE_s * this, ...);
DEVICE_Foo(DEVICE_s * this, ...);

device.c

#include "device.h"
// method implementations here

deviceset.h

//hguards...

#include "device.h"

typedef struct DEVICE_SET_s DEVICE_SET_s;
struct DEVICE_SET_s
{
    uint8_t     Total;
    uint8_t     Used;
    uint8_t     Available;
    DEVICE_s    Set[SET_SIZE];
};

// method declarations here
DEVICE_SET_Init(DEVICE_SET_s * this, ...);
DEVICE_SET_Foo(DEVICE_SET_s * this, ...);

deviceset.c

#include "deviceset.h"
// method implementations here

usercode.c

DEVICE_SET_s myDevices;
void func(void) {
     DEVICE_SET_Init(&myDevices, a, b, c);         
        ...
}

With this approach it's users responsibility to allocate the memory and call init function (=constructor) to initialize object before use.

It does not give really give you encapsulation, but gives maximum freedom for allocation. For encapsualtion to work well it needs support from the language. Because C is so limited language to begin with, I don't recommend adding more limitations just to satisfy programming paradigm.

user694733
  • 15,208
  • 2
  • 42
  • 68
  • "*It does not give really give you encapsulation*" Hm, wasn't this exactly, what the OP asked for? At least the structures can be hidden. – alk Jan 28 '19 at 14:40
  • @alk Yes, but my answer aims to solve the problem without, because I believe that gives better benefits in the long run. Even if the user of the module isn't supposed to mess with the internals of the structure, he should still be able to allocate it as needed, and see what it costs to do so. – user694733 Jan 28 '19 at 15:12