Cross-Platform C++ code and single header - multiple implementations

前端 未结 5 567
离开以前
离开以前 2021-01-31 11:15

I have heard that a way to write Cross Platform c++ code is to define classes as follows (for example, a Window class):

window.h
window_win32.cpp
window_linux.cp         


        
5条回答
  •  失恋的感觉
    2021-01-31 11:44

    There is more ways to solve this problem - each has it's pros and cons.

    1.) Use macros #ifdef, #endif

    // Note: not sure if "WINDOWS" or "WIN32" or something else is defined on Windows
    #ifdef WINDOWS
        #include 
    #else
        // etc.
    #endif
    
    class MyClass
    {
    public:
    
        // Public interface...
    
    private:
    
    #ifdef WINDOWS
        HWND m_myHandle;
    #else
        // etc.
    #endif
    
    };
    

    Pros:

    • Maximal speed of program.

    Cons:

    • Worse readibility. With many platforms it can get really messy.
    • Including platform specific includes might break something. windows.h defines many macros with normal names.

    2.) As was there already written, you might use polymorphism:

    // IMyClass.h for user of your class:
    
    class IMyClass
    {
    public:
    
        virtual ~IMyClass() {}
        virtual void doSomething() = 0;
    
    };
    
    
    // MyClassWindows.h is implementation for one platform
    
    #include 
    #include "IMyClass.h"
    
    class MyClassWindows : public IMyClass
    {
    public:
    
        MyClassWindows();
        virtual void doSomething();
    
    private:
    
        HWND m_myHandle;
    
    };
    
    // MyClassWindows.cpp implements methods for MyClassWindows
    

    Pros:

    • Much, much more cleaner code.

    Cons:

    • User cannot create instances of your class directly (especially not on stack).
    • You must provide special function for creation: for example declare IMyClass* createMyClass(); and define it in MyClassWindows.cpp and other platform specific files. In that case (well, in fact in this whole polymorphism case) you should also define function which destroys the instances - in order to keep idiom "whoever created it should also destroy".
    • Little slowdown because of virtual methods (in these days practically completely insignificant except very, very special cases).
    • Note: the allocation can be problem on platforms with limited memory because of problems with RAM fragmentation. In that case, it can be solved by using some kind of memory pool for your objects.

    3.) PIMPL idiom.

    // MyClass.h
    
    class MyClass
    {
    public:
    
        MyClass();
        void doSomething();
    
    private:
    
        struct MyClassImplementation;
    
        MyClassImplementation *m_impl;
    
    }
    
    
    // MyClassWindows.h
    
    #include 
    #include "MyClass.h"
    
    struct MyClassImplementation
    {
        HWND m_myHandle;
    
        void doSomething();
    
    }
    

    In this case, MyClassImplementation keeps all needed (at least platform specific) data and implements what is needed (again, platform specific). In MyClass.cpp you include the platform specific implementation (methods can be inline), in constructor (or later if you want to - just be careful) you allocate the implementation and in destructor you will destroy it.

    Pros:

    • User can create instances of your class (including on stack) (no worrying about un/deleted poiners).
    • You do not need to include platform specific headers in MyClass.h.
    • You can simply add reference counter and implement data sharing and/or copy-on-write which can easily allow to use your class as return value even if it keeps big amount of data.

    Cons:

    • You must allocate implementation object. Object pool can help.
    • When calling a methods, two are called instead and one pointer dereferencing. Still, today shouldn't be any problem.

    4.) Define a neutral type, which is big enough to keep your data. For example long long int.

    // MyClass.h
    
    class MyClass
    {
    public:
    
        MyClass();
        void doSomething();
    
    private:
    
        typedef unsigned long long int MyData;
    
        MyData m_data;
    
    };
    

    In implementation (e.g. MyClassWindows.cpp) you always need to cast (reinterpret casting) between MyClass::MyData and actual data stored.

    Pros:

    • As fast as first way but you avoid macros.
    • Avoiding allocation if not needed.
    • Avoiding multiple method calls.
    • Avoiding including platform specific headers in MyClass.h.

    Cons:

    • You must be absolutely 110% sure that size of MyClass::MyData is always at least same as data stored.
    • If different platform stores differently sized data, you are waisting with space (if you use a few items, it's ok, but with millions of them...) unless you use macros. In this case, it won't be so messy.
    • It's low level work with data - unsafe, not OOP. But fast.

    So use the one which is best fitting to your problem... and your personality :3 because with today's power are all four more or less relatively equal in terms of speed and space.

提交回复
热议问题