Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs

Dashboard
Notifications
Mark all as read
Q&A

How to do private encapsulation in C?

+3
−0

I'm using an object-oriented design for my C project and trying to implement classes with private encapsulation. How do I do this? Some things I've tried that are problematic:

  • Using a struct for my class and placing the struct definition in the header file, then designing the API around this and telling the user "please don't access the struct members" through comments/documentation. Clearly this doesn't actually give private encapsulation since the caller can access all struct members freely.

    And users using private members intentionally isn't as much of a problem as them doing so unintentionally (for example by getting suggestions from "code completion" IDEs).

  • Using static file scope variables for private members inside the .c file of the class. This does fix the private encapsulation, but now I can't declare multiple instances of the class, it's essentially "singleton" since there is just one instance of the static file scope variables. And it isn't thread-safe either.

Are there other alternatives?

Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+4
−0

The concept you are looking for is referred to as opaque type or opaque pointers. This is the proper method to achieve private encapsulation in C and can also be used for inheritance/polymorphism (which I won't address in this answer). It allows multiple object instances of the same class.

Opaque type is achieved by only placing a forward declaration of the struct inside the public header. This makes the struct an incomplete type which will have to be completed later in order to be used. In formal C terms, it has to be completed within the same translation unit as it is used inside. A translation unit being one .c file and every .h file it includes.

In this case, the class interface in the .h file and the class implementation in the corresponding .c file forms one translation unit. If we place the struct definition in the .c file, then it only becomes accessible there. Since the caller's .c file and the class .h file forms a different translation unit.

This allows the caller to only declare pointers to that type. They can't access the members or allocating actual instances of it. Allocation/deallocation is handled by the class.

A practical example with some snippets taken from a string class I once made (irrelevant parts of the code commented out):

cstring.h

#ifndef CSTRING_H
#define CSTRING_H

// forward declaration, struct of incomplete type:
typedef struct cstring cstring; 

cstring* cstring_create (const char* str_opt); // constructor
void cstring_delete (cstring* cstr); // destructor

void cstring_assign (cstring* cstr, const char* str); // member function
...

#endif

cstring.c

#include "cstring.h"

struct cstring
{ // all of these are private member variables:
  char*  str;
  size_t length;
  size_t alloc_size;
};

cstring* cstring_create (const char* str_opt)
{
  cstring* result;

  /* code checking how much to allocate */

  result = cstring_alloc(size); // dynamic memory allocation
  if(result == NULL)
  {
    return NULL;
  }

  /* code assigning all member variables */
  
  return result;
}

void cstring_delete (cstring* cstr)
{
  free(cstr->str);
  free(cstr);
}

void cstring_assign (cstring* cstr, const char* str)
{
  size_t length = strlen(str);

  /* code checking if more memory is needed, then allocating */

  memcpy(cstr->str, str, length+1);
}

caller.c

#include "cstring.h"

cstring* cstr1 = cstring_create(0);
cstring_assign(cstr1, "hello world");
puts(cstring_cstr(cstr1));
cstring_delete (cstr1);

Regarding member functions:

Note that in this example, member functions are declared in the header rather than as function pointers in a public part of the struct. Some like to use function pointer to emulate C++ like member syntax like obj.member() (see for example Schreiner - Object-oriented Programming in ANSI-C, a book which I don't really recommend), but I think that's a rather pointless feature.

In C, there are no automatic constructor/destructor calls, no RAII, no this pointers. Meaning you have to pass along a reference to the object anyway.

So rather than writing foo.bar(&foo, stuff) you might as well write bar(&foo, stuff). But name bar something meaningful so it becomes clear which class it belongs to. In my example above, everything belonging to the cstring class is prefixed with cstring.

Also, declaring function pointers in the struct itself means that those are allocated for every instance of the class. That's potentially wasting a lot of memory.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »