Fundamentals 16 min read

Eliminating Linux Platform‑Specific Code in C++ with Design‑Pattern Solutions

This article examines the challenges of Linux platform‑specific code in C++ projects, critiques simple preprocessor‑macro approaches, and presents three progressively refined design‑pattern solutions—including interface abstraction, binary layering, and a combination of Proxy, Bridge, and Singleton—culminating in extensible, maintainable implementations.

ITPUB
ITPUB
ITPUB
Eliminating Linux Platform‑Specific Code in C++ with Design‑Pattern Solutions

Background and Problem Statement

Various Linux distributions (RedHat, Ubuntu, Suse, etc.) lead developers to embed extensive platform‑specific code using preprocessor macros. This practice harms readability, maintainability, and violates the principle of platform‑independent interfaces, resulting in code duplication and structural chaos.

Simple Preprocessor‑Macro Approach

Setting compile‑time macros for each platform can isolate platform code, but quickly becomes unwieldy as the number of supported distributions and OS bitness grows.

// Procedure.cpp
void SomeFunction()
{
    // Common code for all Linux
    ...
    #ifdef RHEL
    SpecialCaseForRHEL();
    #endif
    #ifdef SUSE
    SpecialCaseForSUSE();
    #endif
    #ifdef UBUNTU
    SpecialCaseForUBUNTU();
    #endif
    // Common code for all Linux
    ...
    #ifdef RHEL
    SpecialCase2ForRHEL();
    #endif
    #ifdef SUSE
    SpecialCase2ForSUSE();
    #endif
    #ifdef UBUNTU
    SpecialCase2ForUBUNTU();
    #endif
    // Common code for all Linux
    ...
}

While functional, this method leads to:

Redundant and tangled function bodies.

Explosion of macros when adding new platforms or features.

Exposure of platform‑specific implementations to callers, breaking interface abstraction.

Solution 1 – Interface‑Based Refactoring

Encapsulate all platform‑specific implementations behind a single interface, exposing only one public function to callers.

// Procedure.cpp
void SomeFunction()
{
    // Common code for all Linux
    ...
    SpecialCase();
    // Common code for all Linux
    ...
    SpecialCase2();
    // Common code for all Linux
    ...
}

void SpecialCase()
{
    // Common code for all Linux
    ...
    #ifdef RHEL
    SpecialCaseForRHEL();
    #endif
    #ifdef SUSE
    SpecialCaseForSUSE();
    #endif
    #ifdef UBUNTU
    SpecialCaseForUBUNTU();
    #endif
    // Common code for all Linux
    ...
}

void SpecialCase2()
{
    // Common code for all Linux
    ...
    #ifdef RHEL
    SpecialCase2ForRHEL();
    #endif
    #ifdef SUSE
    SpecialCase2ForSUSE();
    #endif
    #ifdef UBUNTU
    SpecialCase2ForUBUNTU();
    #endif
    // Common code for all Linux
    ...
}

Advantages: a single interface per functionality, better encapsulation.

Drawbacks: macro proliferation remains unresolved.

Solution 2 – Binary Layering

Separate each platform’s implementation into its own shared library (e.g., Rhel.so, Suse.so, Ubuntu.so) and keep a thin caller library ( Results.so) that delegates to the appropriate implementation at runtime.

Advantages: eliminates preprocessor macros entirely and preserves interface independence.

Drawbacks: packaging complexity increases because each release must ship multiple binaries.

Solution 3 – Combined Proxy, Bridge, and Singleton Pattern

Leverage C++ polymorphism and three design patterns to achieve both low macro usage and strict interface abstraction within a single binary.

Key Classes

// Host.h
class IHost {
public:
    virtual void SpecialCase1() = 0;
    virtual void SpecialCase2() = 0;
};

class Host : public IHost {
public:
    virtual ~Host() {}
    void setHost(IHost* pHost) { m_pImp->setHost(pHost); }
    virtual void SpecialCase1() { m_pImp->SpecialCase1(); }
    virtual void SpecialCase2() { m_pImp->SpecialCase2(); }
protected:
    Host(HostImp* pImp);
private:
    HostImp* m_pImp;
    friend class HostImp;
};

class RhelHost : public Host { public: static RhelHost* instance(); private: RhelHost(HostImp* pImp); };

class RhelOS : public IHost {
public:
    static void init() { static RhelOS me; RhelHost::instance()->setHost(&me); }
    static void term() { RhelHost::instance()->setHost(nullptr); }
private:
    virtual void SpecialCase1() { /* Real operation */ }
    virtual void SpecialCase2() { /* Real operation */ }
};

// HostImp.h
class HostImp : public IHost {
public:
    HostImp();
    virtual ~HostImp() {}
    void setHost(IHost* pHost) { m_pHost = pHost; }
    virtual void SpecialCase1() { if (m_pHost) m_pHost->SpecialCase1(); }
    virtual void SpecialCase2() { if (m_pHost) m_pHost->SpecialCase2(); }
private:
    IHost* m_pHost;
};

Caller code becomes concise:

// Procedure.cpp
void SomeFunction()
{
    // Common code for all Linux
    ...
    XXHost::instance()->SpecialCase1();
    // Common code for all Linux
    ...
    XXHost::instance()->SpecialCase2();
    // Common code for all Linux
    ...
}

Only the initialization routine still needs a single macro per platform:

void Init()
{
    #ifdef RHEL
    RhelOS::init();
    #endif
    #ifdef SUSE
    SuseOS::init();
    #endif
    #ifdef UBUNTU
    UbuntuOS::init();
    #endif
}

Advantages:

Strict adherence to platform‑independent interface.

Only one macro location required.

Clear separation of caller and implementation layers.

Extensible via additional host/implementation classes.

Further Extensions

Extension 1 – Multi‑style Support on a Single OS – By creating multiple host/implementation pairs (e.g., Style1HostStyle1Dialog), the pattern supports many‑to‑many relationships, allowing several UI styles to coexist on the same operating system.

Extension 2 – Cross‑OS Support – The same architecture can be applied to Windows, macOS, and other platforms, each with its own host/implementation pair, handling GUI‑library differences while keeping the caller code unchanged.

Conclusion

The article identified two core problems caused by Linux platform‑specific code: excessive macro usage and violation of interface independence. After evaluating simple macro‑based methods, it introduced three increasingly sophisticated solutions, culminating in a design‑pattern‑driven framework that minimizes macros, enforces abstraction, and remains highly extensible.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsSoftware ArchitectureLinuxC++Platform-specific Code
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.