Menu
Coddy logo textTech

Defining Opaque Structs

Part of the Object Oriented Programming section of Coddy's C journey — lesson 18 of 61.

Now that you understand the concept of opaque pointers, let's see how to complete the pattern. The header declares that a type exists, but the source file is where you define what's actually inside.

Here's the complete setup. First, the header with only the forward declaration:

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // Incomplete type

Counter *create_counter(int start);
void free_counter(Counter *c);

#endif

Then, the source file contains the actual struct definition:

// counter.c
#include "counter.h"
#include <stdlib.h>

struct Counter {
    int value;      // Hidden from outside!
    int step;
};

Counter *create_counter(int start) {
    Counter *c = malloc(sizeof(Counter));
    c->value = start;
    c->step = 1;
    return c;
}

void free_counter(Counter *c) {
    free(c);
}

The key point: struct Counter { ... } appears only in the .c file. Any file that includes counter.h knows Counter exists but cannot see value or step. Attempting to write c->value in main.c would cause a compiler error—the members are truly invisible.

This separation is the foundation of encapsulation in C. The next step is providing controlled access through getter and setter functions.

challenge icon

Challenge

Easy

Let's build a Timer module that demonstrates the opaque pointer pattern. You'll practice hiding the struct definition inside the source file, making its internal members completely invisible to any code that uses your module.

You'll create three files:

  • timer.h: This is your public interface. Declare an opaque Timer type using only a forward declaration—no struct body here! Also declare two functions: create_timer (takes an integer for seconds and returns a Timer pointer) and free_timer (takes a Timer pointer and cleans up). Use include guards with the symbol TIMER_H.
  • timer.c: This is where the secret lives. Define the actual struct Timer with two hidden members: int seconds and int running (use 1 for running, 0 for stopped). Implement your constructor to allocate the struct, initialize seconds with the provided value, and set running to 1. Implement the destructor to free the memory.
  • main.c: Use your Timer module to create a timer and confirm it was created successfully. Since the struct members are hidden, you cannot access them directly—this is the whole point! Simply print a message confirming creation, then free the timer and print a cleanup message.

You will receive one input: the number of seconds (an integer) to initialize the timer with.

In your main file, create a timer with the provided seconds value. Print a creation message, then free the timer and print a confirmation.

Print the output in this format:

Timer created
Timer freed

The key insight here is that main.c can work with Timer pointers but has no way to access seconds or running directly. Attempting to write t->seconds in main.c would cause a compiler error because the struct body is only defined in timer.c. This is true encapsulation—the implementation details are completely hidden from users of your module.

Cheat sheet

The opaque pointer pattern separates the declaration of a type from its definition, hiding implementation details.

Header file contains only a forward declaration:

// counter.h
#ifndef COUNTER_H
#define COUNTER_H

typedef struct Counter Counter;  // Incomplete type

Counter *create_counter(int start);
void free_counter(Counter *c);

#endif

Source file contains the actual struct definition:

// counter.c
#include "counter.h"
#include <stdlib.h>

struct Counter {
    int value;      // Hidden from outside
    int step;
};

Counter *create_counter(int start) {
    Counter *c = malloc(sizeof(Counter));
    c->value = start;
    c->step = 1;
    return c;
}

void free_counter(Counter *c) {
    free(c);
}

The struct definition appears only in the .c file. Files that include the header know the type exists but cannot access its members directly. This provides true encapsulation in C.

Try it yourself

#include <stdio.h>
#include "timer.h"

int main() {
    int seconds;
    scanf("%d", &seconds);
    
    // TODO: Create a timer with the provided seconds value
    
    // TODO: Print "Timer created"
    
    // TODO: Free the timer
    
    // TODO: Print "Timer freed"
    
    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming