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
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 (...
Answer
#3: Post edited
- 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
- 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
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.