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
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

90%
+16 −0
Q&A How to do private encapsulation in C?

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 (...

posted 2y ago by Lundin‭  ·  edited 1y ago by Lundin‭

Answer
#3: Post edited by user avatar Lundin‭ · 2022-08-19T14:19:52Z (over 1 year ago)
  • 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
  • ```c
  • #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
  • ```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
  • ```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.
  • 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
  • ```c
  • #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
  • ```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
  • ```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 pointers 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.
#2: Post edited by user avatar Lundin‭ · 2021-09-01T09:39:29Z (over 2 years ago)
  • 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
  • ```c
  • #ifndef CSTRING_H
  • #define CSTRING_H
  • typedef struct cstring cstring; // forward declaration, struct of incomplete type
  • 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
  • ```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
  • ```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.
  • 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
  • ```c
  • #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
  • ```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
  • ```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.
#1: Initial revision by user avatar Lundin‭ · 2021-09-01T09:38:55Z (over 2 years ago)
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

```c
#ifndef CSTRING_H
#define CSTRING_H

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

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

```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

```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.