Cross platform C++ code architecture

前端 未结 8 598
广开言路
广开言路 2021-01-31 00:18

I\'m having a bit of a go at developing a platform abstraction library for an application I\'m writing, and struggling to come up with a neat way of separating my platform indep

相关标签:
8条回答
  • 2021-01-31 00:37

    I'm using platform neutral header files, keeping any platform specific code in the source files (using the PIMPL idiom where neccessary). Each platform neutral header has one platform specific source file per platform, with extensions such as *.win32.cpp, *.posix.cpp. The platform specific ones are only compiled on the relevent platforms.

    I also use boost libraries (filesystem, threads) to reduce the amount of platform specific code I have to maintain.

    It's platform independent classes declarations with platform specific definitions.

    Pros: Works fairly well, doesn't rely on the preprocessor - no #ifdef MyPlatform, keeps platform specific code readily identifiable, allows compiler specific features to be used in platform specific source files, doesn't pollute the global namespace by #including platform headers.

    Cons: It's difficult to use inheritance with pimpled classes, sometimes the PIMPL structs need their own headers so they can be referenced from other platform specific source files.

    0 讨论(0)
  • 2021-01-31 00:38

    Have a look at ACE. It has a pretty good abstraction using templates and inheritance.

    0 讨论(0)
  • 2021-01-31 00:42

    So... you don't want to simply use Qt? For real work using C++, I'd very highly recommend it. It's an absolutely excellent cross-platform toolkit. I just wrote a few plugins to get it working on the Kindle, and now the Palm Pre. Qt makes everything easy and fun. Downright rejuvenating, even. Well, until your first encounter with QModelIndex, but they've supposedly realized they over-engineered it and they're replacing it ;)

    As an academic exercise though, this is an interesting problem. As a wheel re-inventor myself, I've even done it a few times now. :)

    Short answer: I'd go with PIMPL. (Qt sources have examples a-plenty)

    I've used base classes and platform specific derived classes in the past, but it usually ends up a bit messier than I had in mind. I've also done part of an implementation using some degree of function pointers for platform specific bits, and I was even less happy with that.

    Both times I ended up with a very strong feeling that I was over-architecting and had lost my way.

    I found using private implementation classes (PIMPL) with different platforms specific bits in different files easiest to write AND debug. However... don't be too afraid of an #ifdef or two, if it's just a few lines and very clear what's going on. I hate cluttered or nested #ifdef logic, but one or two here and there can really help avoid code duplication.

    With PIMPL, you're not constantly refactoring your design as you discover new bits that require different implementations between platforms. That way be dragons.

    At the implementation level, hidden from the application... there's nothing wrong with a few platform specific derived classes either. If two platform implementations are fairly well defined and share almost no data members, they'd be a good candidate for that. Just do it after realizing that, not before out of some idea that everything needs to fit your selected pattern.

    If anything, the biggest gripe I have about coding today is how easily people seem to get lost in idealism. PIMPL is a pattern, having platform specific derived classes is another pattern. Using function pointers is a pattern. There's nothing that says they're mutually exclusive.

    However, as a general guideline... start with PIMPL.

    0 讨论(0)
  • 2021-01-31 00:44

    There're also the big boys, such as Qt4 (complete framework + GUI),GTK+ (gui-only afaik), and Boost (framework only, no GUI), all 3 support most platforms, GTK+ is C, Qt4/Boost are C++ and for the most part template based.

    0 讨论(0)
  • 2021-01-31 00:58

    Another way is to have platform independent conventions, but substitute platform specific source code at compile time.

    That is to say that if you imagine a component, Foo, that has to be platform specific (like sockets or GUI elements), but has these public members:

    class Foo {
    public:
      void write(const char* str);
      void close();
    };
    

    Every module that has to use a Foo, obviously has #include "Foo.h", but in a platform specific make file you might have -IWin32, which means that the compiler looks in .\Win32 and finds a Windows specific Foo.h which contains the class, with the same interface, but maybe Windows specific private members etc.

    So there is never any file which contains Foo as written above, but only sets of platform specific files which are only used when selected by a platform specific make file.

    0 讨论(0)
  • 2021-01-31 00:59

    I might go for a policy-type thing:

    template<typename Platform>
    struct PlatDetails : private Platform {
        std::string getDetails() const {
            return std::string("MyAbstraction v1.0; ") + getName();
        }
    };
    
    // For any serious compatibility functions, these would
    // of course have to be in different headers, and the implementations
    // would call some platform-specific functions to get precise
    // version numbers. Using PImpl would be a smart idea for these 
    // classes if they need any platform-specific members, since as 
    // Joe Gauterin says, you want to avoid your application code indirectly
    // including POSIX or Windows system headers, containing useless definitions.
    struct Windows {
        std::string getName() const { return "Windows"; }
    };
    
    struct Linux {
        std::string getName() const { return "Linux"; }
    };
    
    #ifdef WIN32
        typedef PlatDetails<Windows> PlatformDetails;
    #else
        typedef PlatDetails<Linux> PlatformDetails;
    #endif
    
    int main() {
        std::cout << PlatformDetails().getName() << "\n";
    }
    

    There's not a whole lot to choose though between doing this, and doing regular simulated dynamic binding with CRTP, so that the generic thing is the base and the specific thing the derived class:

    template<typename Platform>
    struct PlatDetails {
        std::string getDetails() const {
            return std::string("MyAbstraction v1.0; ") + 
                static_cast<Platform*>(this)->getName();
        }
    };
    
    struct Windows : PlatDetails<Windows> {
        std::string getName() const { return "Windows"; }
    };
    
    struct Linux : PlatDetails<Linux> {
        std::string getName() const { return "Linux"; }
    };
    
    #ifdef WIN32
        typedef Windows PlatformDetails;
    #else
        typedef Linux PlatformDetails;
    #endif
    
    int main() {
        std::cout << PlatformDetails().getName() << "\n";
    }
    

    Basically in the latter version, getName must be public (although I think you can use friend) and so must be the inheritance, whereas in the former, the inheritance can be private and/or the interface functions can be protected, if desired. So the adaptor can be a firewall between the interface the platform has to implement, and the interface your application code uses. Furthermore you can have multiple policies in the former (i.e. multiple platform-dependent facets used by the same platform-independent class), but not for the latter.

    The advantage of either of them over versions with delegates or non-template-using inheritance, is that you don't need any virtual functions. Arguably this isn't a whole lot of advantage, considering how scary both policy-based design and CRTP are at first contact.

    In practice, though, I agree with quamrana that normally you can just have different implementations of the same thing on different platforms:

    // Or just set the include path with -I or whatever
    #ifdef WIN32
        #include "windows/platform.h"
    #else
        #include "linux/platform.h"
    #endif
    
    struct PlatformDetails {
        std::string getDetails() const {
            return std::string("MyAbstraction v1.0; ") + 
                porting::getName();
        }
    };
    
    // windows/platform.h
    namespace porting {
        std::string getName() { return "Windows"; }
    }
    
    // linux/platform.h
    namespace porting {
        std::string getName() { return "Linux"; }
    }
    
    0 讨论(0)
提交回复
热议问题