Is the pImpl idiom really used in practice?

后端 未结 11 1307
渐次进展
渐次进展 2020-11-22 17:21

I am reading the book \"Exceptional C++\" by Herb Sutter, and in that book I have learned about the pImpl idiom. Basically, the idea is to create a structure for the p

相关标签:
11条回答
  • 2020-11-22 17:26

    One benefit I can see is that it allows the programmer to implement certain operations in a fairly fast manner:

    X( X && move_semantics_are_cool ) : pImpl(NULL) {
        this->swap(move_semantics_are_cool);
    }
    X& swap( X& rhs ) {
        std::swap( pImpl, rhs.pImpl );
        return *this;
    }
    X& operator=( X && move_semantics_are_cool ) {
        return this->swap(move_semantics_are_cool);
    }
    X& operator=( const X& rhs ) {
        X temporary_copy(rhs);
        return this->swap(temporary_copy);
    }
    

    PS: I hope I'm not misunderstanding move semantics.

    0 讨论(0)
  • 2020-11-22 17:31

    It seems that a lot of libraries out there use it to stay stable in their API, at least for some versions.

    But as for all things, you should never use anything everywhere without caution. Always think before using it. Evaluate what advantages it gives you, and if they are worth the price you pay.

    The advantages it may give you are:

    • helps in keeping binary compatibility of shared libraries
    • hiding certain internal details
    • decreasing recompilation cycles

    Those may or may not be real advantages to you. Like for me, I don't care about a few minutes recompilation time. End users usually also don't, as they always compile it once and from the beginning.

    Possible disadvantages are (also here, depending on the implementation and whether they are real disadvantages for you):

    • Increase in memory usage due to more allocations than with the naïve variant
    • increased maintenance effort (you have to write at least the forwarding functions)
    • performance loss (the compiler may not be able to inline stuff as it is with a naïve implementation of your class)

    So carefully give everything a value, and evaluate it for yourself. For me, it almost always turns out that using the pimpl idiom is not worth the effort. There is only one case where I personally use it (or at least something similar):

    My C++ wrapper for the linux stat call. Here the struct from the C header may be different, depending on what #defines are set. And since my wrapper header can't control all of them, I only #include <sys/stat.h> in my .cxx file and avoid these problems.

    0 讨论(0)
  • 2020-11-22 17:33

    Other people have already provided the technical up/downsides, but I think the following is worth noting:

    First and foremost, don't be dogmatic. If pImpl works for your situation, use it - don't use it just because "it's better OO since it really hides implementation" etc. Quoting the C++ FAQ:

    encapsulation is for code, not people (source)

    Just to give you an example of open source software where it is used and why: OpenThreads, the threading library used by the OpenSceneGraph. The main idea is to remove from the header (e.g. <Thread.h>) all platform-specific code, because internal state variables (e.g. thread handles) differ from platform to platform. This way one can compile code against your library without any knowledge of the other platforms' idiosyncrasies, because everything is hidden.

    0 讨论(0)
  • 2020-11-22 17:36

    I would mainly consider PIMPL for classes exposed to be used as an API by other modules. This has many benefits, as it makes recompilation of the changes made in the PIMPL implementation does not affect the rest of the project. Also, for API classes they promote a binary compatibility (changes in a module implementation do not affect clients of those modules, they don't have to be recompiled as the new implementation has the same binary interface - the interface exposed by the PIMPL).

    As for using PIMPL for every class, I would consider caution because all those benefits come at a cost: an extra level of indirection is required in order to access the implementation methods.

    0 讨论(0)
  • 2020-11-22 17:38

    So, I am wondering it this technique is really used in practice? Should I use it everywhere, or with caution?

    Of course it is used. I use it in my project, in almost every class.


    Reasons for using the PIMPL idiom:

    Binary compatibility

    When you're developing a library, you can add/modify fields to XImpl without breaking the binary compatibility with your client (which would mean crashes!). Since the binary layout of X class doesn't change when you add new fields to Ximpl class, it is safe to add new functionality to the library in minor versions updates.

    Of course, you can also add new public/private non-virtual methods to X/XImpl without breaking the binary compatibility, but that's on par with the standard header/implementation technique.

    Data hiding

    If you're developing a library, especially a proprietary one, it might be desirable not to disclose what other libraries / implementation techniques were used to implement the public interface of your library. Either because of Intellectual Property issues, or because you believe that users might be tempted to take dangerous assumptions about the implementation or just break the encapsulation by using terrible casting tricks. PIMPL solves/mitigates that.

    Compilation time

    Compilation time is decreased, since only the source (implementation) file of X needs to be rebuilt when you add/remove fields and/or methods to the XImpl class (which maps to adding private fields/methods in the standard technique). In practice, it's a common operation.

    With the standard header/implementation technique (without PIMPL), when you add a new field to X, every client that ever allocates X (either on stack, or on heap) needs to be recompiled, because it must adjust the size of the allocation. Well, every client that doesn't ever allocate X also need to be recompiled, but it's just overhead (the resulting code on the client side will be the same).

    What is more, with the standard header/implementation separation XClient1.cpp needs to be recompiled even when a private method X::foo() was added to X and X.h changed, even though XClient1.cpp can't possibly call this method for encapsulation reasons! Like above, it's pure overhead and is related with how real-life C++ build systems work.

    Of course, recompilation is not needed when you just modify the implementation of the methods (because you don't touch the header), but that's on par with the standard header/implementation technique.


    Is this technique recommended to be used in embedded systems (where the performance is very important)?

    That depends on how powerful your target is. However the only answer to this question is: measure and evaluate what you gain and lose. Also, take into consideration that if you're not publishing a library meant to be used in embedded systems by your clients, only the compilation time advantage applies!

    0 讨论(0)
  • 2020-11-22 17:38

    I used to use this technique a lot in the past but then found myself moving away from it.

    Of course it is a good idea to hide the implementation detail away from the users of your class. However you can also do that by getting users of the class to use an abstract interface and for the implementation detail to be the concrete class.

    The advantages of pImpl are:

    1. Assuming there is just one implementation of this interface, it is clearer by not using abstract class / concrete implementation

    2. If you have a suite of classes (a module) such that several classes access the same "impl" but users of the module will only use the "exposed" classes.

    3. No v-table if this is assumed to be a bad thing.

    The disadvantages I found of pImpl (where abstract interface works better)

    1. Whilst you may have only one "production" implementation, by using an abstract interface you can also create a "mock" inmplementation that works in unit testing.

    2. (The biggest issue). Before the days of unique_ptr and moving you had restricted choices as to how to store the pImpl. A raw pointer and you had issues about your class being non-copyable. An old auto_ptr wouldn't work with forwardly declared class (not on all compilers anyway). So people started using shared_ptr which was nice in making your class copyable but of course both copies had the same underlying shared_ptr which you might not expect (modify one and both are modified). So the solution was often to use raw pointer for the inner one and make the class non-copyable and return a shared_ptr to that instead. So two calls to new. (Actually 3 given old shared_ptr gave you a second one).

    3. Technically not really const-correct as the constness isn't propagated through to a member pointer.

    In general I have therefore moved away in the years from pImpl and into abstract interface usage instead (and factory methods to create instances).

    0 讨论(0)
提交回复
热议问题