A pimpl list is short for pointer implementation list. Effectively it's what you call an interface in OOP land. But not quite.
First you declare some functions (what your application will be calling) and a struct of function pointers. As an example, let's say we're doing animals. We have an "animal.h" that has this definition.
Code:
void move_animal(void);
void sound_animal(void);
typedef struct animal_impl_tag
{
void (*move)(void);
void (*make_sound)(void);
}animal_impl;
Then in our "animal.c"
Code:
#include "animal.h"
static animal_impl = pimpl;
void move_animal(void)
{
pimpl->move();
}
void sound_animal(void)
{
pimpl->make_sound();
}
move_animal and sound_animal are what get called in the application. When you call them, they call another function in the struct defined earlier. The catch is that these functions must be defined somewhere else and properly pointed to, otherwise you'll be following a null pointer. More on how to properly set this up later...
Then we have a "dog.c"
Code:
#include "animals.h"
void walk(void)
{
//Dog walks
}
void bark(void)
{
//Dog barks
}
const struct animal_impl dog_impl
{
.move = walk,
.make_sound = bark
};
In the bottom, you're mapping out which function in "dog.c" goes to the animal_impl struct. So let's say we also have a "dolphin.c"
Code:
#include "animals.h"
void swim(void)
{
//Dolphin swims
}
void squeak(void)
{
//Dolphin make squeaking sounds
}
const struct animal_impl dolphin_impl
{
.move = swim,
.make_sound = squeak
};
However, both "dog_impl" and "dolphin_impl" need to be global. This changes "animal.h" to...
Code:
void set_pimpl(int Animal_Type);
void move_animal(void);
void sound_animal(void);
typedef struct animal_impl_tag
{
void (*move)(void);
void (*make_sound)(void);
}animal_impl;
extern const struct animal_impl dog_impl;
extern const struct animal_impl dolphin_impl;
And you need to add another function in "animal.c" to set up which function ultimately gets called, hence the "set_pimpl" function... So we have:
Code:
#include "animal.h"
static animal_impl = pimpl;
void set_pimpl(int Animal_Type)
{
switch(Animal_Type)
{
case DOG:
pimpl = dog_impl;
break;
case DOLPHIN:
pimpl = dolphin_impl;
break;
default:
//Some default condition
break;
}
}
void move_animal(void)
{
pimpl->move();
}
void sound_animal(void)
{
pimpl->make_sound();
}
So if you called move_animal when pimpl = dog_impl, you would call walk() in dog.c.
It's a pain in the ass to debug when you aren't aware of pimpl lists, because it's not intuitive where any of this goes. But it's a handy tool. In this case, the mainline application code doesn't need to know if it's a dog or a dolphin. It just calls generic functions that mean move the animal or have the animal make a sound.
And heck, you probably don't need a set_pimpl function. You could have as many animal_impl variables as needed.