Asked  7 Months ago    Answers:  5   Viewed   33 times

Backgrounder:

The PIMPL Idiom (Pointer to IMPLementation) is a technique for implementation hiding in which a public class wraps a structure or class that cannot be seen outside the library the public class is part of.

This hides internal implementation details and data from the user of the library.

When implementing this idiom why would you place the public methods on the pimpl class and not the public class since the public classes method implementations would be compiled into the library and the user only has the header file?

To illustrate, this code puts the Purr() implementation on the impl class and wraps it as well.

Why not implement Purr directly on the public class?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}

 Answers

35
  • Because you want Purr() to be able to use private members of CatImpl. Cat::Purr() would not be allowed such an access without a friend declaration.
  • Because you then don't mix responsibilities: one class implements, one class forwards.
Tuesday, June 1, 2021
 
aWebDeveloper
answered 7 Months ago
30

You define and document for your types what a 'valid' state is and what operation can be performed on moved-from objects of your types.

Moving an object of a standard library type puts the object into an unspecified state, which can be queried as normal to determine valid operations.

17.6.5.15 Moved-from state of library types                                         [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

The object being in a 'valid' state means that all the requirements the standard specifies for the type still hold true. That means you can use any operation on a moved-from, standard library type for which the preconditions hold true.

Normally the state of an object is known so you don't have to check if it meets the preconditions for each operation you want to perform. The only difference with moved-from objects is that you don't know the state, so you do have to check. For example, you should not pop_back() on a moved-from string until you have queried the state of the string to determine that the preconditions of pop_back() are met.

std::string s = "foo";
std::string t(std::move(s));
if (!s.empty()) // empty has no preconditions, so it's safe to call on moved-from objects
    s.pop_back(); // after verifying that the preconditions are met, pop_back is safe to call on moved-from objects

The state is probably unspecified because it would be onerous to create a single useful set of requirements for all different implementations of the standard library.


Since you are responsible not only for the specification but also the implementation of your types, you can simply specify the state and obviate the need for querying. For example it would be perfectly reasonable to specify that moving from your pimpl type object causes do_stuff to become an invalid operation with undefined behavior (via dereferencing a null pointer). The language is designed such that moving only occurs either when it's not possible to do anything to the moved-from object, or when the user has very obviously and very explicitly indicated a move operation, so a user should never be surprised by a moved-from object.


Also note that the 'concepts' defined by the standard library do not make any allowances for moved-from objects. That means that in order to meet the requirements for any of the concepts defined by the standard library, moved-from objects of your types must still fulfill the concept requirements. This means that if objects of your type don't remain in a valid state (as defined by the relevant concept) then you cannot use it with the standard library (or the result is undefined behavior).

Saturday, June 5, 2021
 
Zeth
answered 6 Months ago
87

I'd say that whether you do it per-class or on an all-or-nothing basis depends on why you go for the pimpl idiom in the first place. My reasons, when building a library, have been one of the following:

  • Wanted to hide implementation in order to avoid disclosing information (yes, it was not a FOSS project :)
  • Wanted to hide implementation in order to make client code less dependent. If you build a shared library (DLL), you can change your pimpl class without even recompiling the application.
  • Wanted to reduce the time it takes to compile the classes using the library.
  • Wanted to fix a namespace clash (or similar).

None of these reasons prompts for the all-or-nothing approach. In the first one, you only pimplize what you want to hide, whereas in the second case it's probably enough to do so for classes which you expect to change. Also for the third and fourth reason there's only benefit from hiding non-trivial members that in turn require extra headers (e.g., of a third-party library, or even STL).

In any case, my point is that I wouldn't typically find something like this too useful:

class Point {
  public:      
    Point(double x, double y);
    Point(const Point& src);
    ~Point();
    Point& operator= (const Point& rhs);

    void setX(double x);
    void setY(double y);
    double getX() const;
    double getY() const;

  private:
    class PointImpl;
    PointImpl* pimpl;
}

In this kind of a case, the tradeoff starts to hit you because the pointer needs to be dereferenced, and the methods cannot be inlined. However, if you do it only for non-trivial classes then the slight overhead can typically be tolerated without any problems.

Saturday, June 12, 2021
 
uiroshan
answered 6 Months ago
27

PIMPL is a way of hiding the implementation, primarily to break compilation dependencies.

The Bridge pattern, on the other hand, is a way of supporting multiple implementations.

swap is a standard C++ function for exchanging the values of two objects. If you swap the pointer to the implementation for a different implementation, you are essentially changing the mechanism of the class at runtime.

But in its basic and common form, a class using PIMPL points to a single implementation, so there is no abstract class with distinct subclasses — just one class, forward declared, and compiled elsewhere. Changing the implementation class does not require any recompilation of sources that include the main header.

For example, say you have a lot of private member functions, private enums, and private data. And these private "bits" change fairly frequently as the class is developed and maintained. If the #include dependencies are such that touching this header file causes a large number of sources to be recompiled, you have a good candidate for PIMPL.

So the Bridge pattern is about object-oriented design, while the PIMPL idiom is about physical design of files.

(For more on physical design, I recommend the book Large-Scale C++ Software Design by John Lakos.)

Saturday, July 31, 2021
 
sunshinejr
answered 4 Months ago
98

The idea behind pimpl is to not so much to hide implementation details from classes, (private members already do that) but to move implementation details out of the header. The problem is that in C++'s model of includes, changing the private methods/variables will force any file including this file to be recompiled. That is a pain, and that's why pimpl seeks to eliminate. It doesn't help with preventing dependencies on external libraries. Other techniques do that.

Your unit tests shouldn't depend on the implementation of the class. They should verify that you class actually acts as it should. The only thing that really matter is how the object interacts with the outside world. Any behavior which your tests cannot detect must be internal to the object and thus irrelevant.

Having said that, if you find too much complexity inside the internal implementation of a class, you may want to break out that logic into a separate object or function. Essentially, if your internal behavior is too complex to test indirectly, make it the external behavior of another object and test that.

For example, suppose that I have a class which takes a string as a parameter to its constructor. The string is actual a little mini-language that specifies some of the behavior the object. (The string probably comes from a configuration file or something). In theory, I should be able to test the parsing of that string by constructing different objects and checking behavior. But if the mini-language is complex enough this will be hard. So, I define another function that takes the string and returns a representation of the context (like an associative array or something). Then I can test that parsing function separately from the main object.

Sunday, September 5, 2021
 
mahclark
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share