I apologize if this is an obvious question, but in C, are Opaque Pointers similar to Java Interfaces? As I’m trying to understand them, I’m interpreting them similarly to Java interfaces in the sense that they establish a mandatory way to interact with the method/function? So if I wanted to use an Opaque Pointer, I would need to define it in a .h file while I would call it in a .c file? Opaque pointers just define the signature of the method/function (in the .h file) while the the .c file takes those functions with those signatures and implements them.
3
No. An opaque pointer is a design where the implementation of a type (usually struct
; optionally typedef
‘ed) is hidden (encapsulated) from the client. This module may instantiate the type however it sees fit; often dynamically allocated (and deallocated). Clients have to use functions provided by the module to operate on the opaque pointer.
You will have something like this in your header file (interface):
#ifndef FOO_H
#define FOO_H
typedef struct foo foo; // forward declaration
foo *foo_create();
void foo_destroy(foo *f);
#endif
and then the module implement is:
#include <stdlib.h>
#include "foo.h"
struct foo {
int data;
};
foo *foo_create() {
return calloc(1, sizeof(foo));
}
void foo_destroy(foo *f) {
free(f);
}
5
I’m interpreting them similarly to Java interfaces in the sense that they establish a mandatory way to interact with the method/function
Your interpretation is in the right path, but it is missing a key point: The reason we use opaque pointers is so that we can keep the details of the structure’s implementation hidden, so that we can modify this structure in a future version of our software without breaking existing code that uses it.
Essentially, the analogy with Java is as follows:
-
The declaration of the opaque pointer, and of each function that accepts (or returns) such an opaque pointer, corresponds to an
interface
in Java. (Note: the declarations only. Not their actual definitions.) -
The structure pointed by the opaque pointer, and the set of function definitions, are more like a concrete Java class which
implements
the interface, containing nothing but private member fields, so that nobody can access them.
Another concept very similar to opaque pointers is handles. The open()
function returns an int
which is a file handle, the read()
, write()
, and close()
methods accept such a handle as a parameter. This number could be anything, you are not supposed to interpret it in any way or expect its value to be within any particular range. In theory, on a system where an int
is large enough to hold a pointer to structure, a file handle could in fact be a struct pointer that has been cast to int
. In practice, it is an index into some table of FILE
(or similar) which is maintained entirely by the standard library (or the operating system) so you don’t get to see it and therefore cannot foul it up.
These are all very primitive mechanisms for achieving encapsulation that people were using before Object-Oriented Programming was invented. And since there were no guiding principles and no standard way of doing things, people were doing hacks here and there, which were violating the encapsulation in various ways.
-
For example, the C standard library could be treating
FILE
as an opaque pointer, but for some reason most implementations
choose not to, because they define thestruct
forFILE
in a
header file that you can actually#include
. (See Stack Overflow:
Where is FILE defined in Unix) -
As another example, you are not supposed to interpret the value of
a file handle, but the file handles for the standard streams
(input, output, error) do have very publicly known values, which
are, essentially, nothing but magic numbers.
1
As an addendum to Allan Wind’s answer, opaque pointers rely on a very simple fact: struct pointers are all the same size1, while the size of the things they point to may differ wildly in size.
In C, functions must know the sizes of their arguments, local variables, and return values. Thus nowhere in the header file can we pass or return an abstract data type by value.
Because the actual data type is defined in the implementation file, and thus that translation unit is “aware” of the size of the data, we can work on values of the abstract data type directly there.
This permits the data type to be defined in any way we wish so long as the header file remains consistent. Improvements can be made in the implementation without having to adjust how the data type is actually used by client programs.
1 The size of a pointer is hardware and software platform dependent.
2
I apologize if this is an obvious question, but in C, are Opaque Pointers similar to Java Interfaces?
Nope, it is a completely unrelated thing.
Opaque pointers are pointers to incompletely specified (externally) struct
s, used to hide the internal representation of the structure. Better said, they are pointers to incompletely defined types (an unspecified field structure, or an unspecified length array) This way, the pointer can be used, but not the data pointed to, as the structure contents is unknown to the compiler.
typedef struct hash_table *hash_table_p;
In the code above, we have defined type hash_table_p
as a pointer to an undefined structure, this pointer is usable by hash table routines as an opaque data that is given to the user, just to pass it back to the routines that handle the internals of the structure as externally unaccessible fields.
As pointer to types are not interchangeable, pointer references cannot be intermixed, making the pointers to struct hash_table
to have always to be initialized with an authentic hash table reference (something that, if you are carefull, will always be warranted by the compiler) A pointer to a different type will never be compatible (except if you cast it) with a pointer to a different type. The only exception is pointer to void
which is compatible with any other type of pointer.
You can leave externally the declaration as such, and in the implementation code, provide a complete definition of struct hash_table
. The compiler will allow you to declare as many pointers of the referenced type, but the internals of the data are hidden to external code. The struct hash_table
structure is defined normally in a hash_tableP.h
include file (which also includes the external one) and so C can emulate the DEFINITION MODULE
and IMPLEMENTATION MODULE
compilation units of Modula-2 language (or the package
and package body
of Ada)
Java interfaces, on other side, are sets of method declarations that allow object oriented classes to implement
that, so called, interface, and so be treated as objects of a class that implements all those methods. This somehow defines a class like object, but without deriving it from another class.
C cannot associate a set of procedure declarations into a type, to make only instances implementing those procedures to be considered belonging to a single type (if a type can be compared to a class) so there’s no similar figure in C to the Java interface model.