Skip to main content

ABI-Stable Interfaces

beginner12 min readLesson 191 of 204

pimpl, extern "C" factories, and function-pointer tables: three load-bearing patterns that keep binaries working while the implementation moves.

Pattern 1 โ€” pimpl (pointer to implementation)

// engine.hpp โ€” shipped, frozen
class Engine {
public:
    Engine();
    ~Engine();                       // must be out-of-line! (see below)
    void tick(int units);
private:
    struct Impl;                     // never defined in the header
    Impl* impl_;                     // or std::unique_ptr<Impl> with out-of-line dtor
};

All private state hides behind one pointer. Adding members, changing algorithms, even swapping data structures: the header never changes, the layout of Engine stays one-pointer-wide, old binaries keep working. The destructor must live in the .cpp โ€” the compiler generates the deleter there, where Impl is complete. (With unique_ptr<Impl> as a member, an in-header inline dtor would instantiate default_delete<Impl> on an incomplete type โ€” a compile error; with a raw pointer plus out-of-line dtor, it is just a delete you owe once.)

Pattern 2 โ€” extern "C" factory + opaque handle

extern "C" disables mangling: one fixed symbol name, callable from any language, immune to C++ signature drift. Combined with an opaque pointer it is the classic plugin boundary:

extern "C" Engine* engine_create();
extern "C" void    engine_tick(Engine*, int units);
extern "C" void    engine_destroy(Engine*);

Implementation can be rewritten completely; the three symbols are the contract.

Pattern 3 โ€” versioned function tables

New capability without breaking old callers: append function pointers at the end of a struct and bump a version field. Old callers read v1 fields and stop; new callers check version >= 2 before touching later entries. This is how OS and driver ABIs stay stable for decades.

What the fixed sandbox grades

Real .so linking is not gradeable in a single-TU harness, so the exercises grade the patterns' logic: pimpl lifecycle with the correct out-of-line destructor discipline, opaque-handle factory/destroy contracts, table versioning arithmetic, and ODR-safe constant scoping. Documented limitation, deliberately chosen.

Now practice

Practice: ABI-Stable PatternsPimpl lifecycle under a double-move battery, and an opaque factory whose destroy survives being called twice.2 challenges ยท ยท ~16 min