7

If I want to present to the user of my code an interface, that is unlikely to change, I can write only the public part of the classes in public headers and have a single private pointer to another object, that holds the private declarations. I believe this is known as The pImpl idiom.

Now imagine another approach. The public part of the class is placed into an IClass or ClassBase or whatever class, placed in the public headers. Then, in a private header (== not exposed to the user), this base class is inherited and it's methods implemented.

To me it seems that the second approach achieves the same goals as the first one. But are there any drawbacks? When to prefer which?

1
  • 2
    I don't do much C++, but the alternative approach results in slicing if you ever pass the object by value.
    – Doval
    Mar 13, 2015 at 17:24

2 Answers 2

6

Let's consider as close to an apples to apples comparison as we can, meaning you are using this base class only to derive it one time in a class that's not publicly visible and discounting the extensibility of this solution. The sole purpose of using an abstraction here is to hide one concrete implementation, nothing more (as with the pimpl case).

So we have something like this (abstract interface):

// In some header file:
class IFoo
{
public:
    virtual ~IFoo() {}
    virtual void do_something() = 0;
};
std::unique_ptr<IFoo> create_foo();

// Inside source file (hidden from the outside world):
class Foo: public IFoo
{
    ...
};

std::unique_ptr<IFoo> create_foo()
{
    return std::unique_ptr<IFoo>(new Foo);
}

vs. Pimpl:

// In some header file:
class Foo
{
public:
    Foo();
    Foo(const Foo& other);
    Foo& operator=(const Foo& other);
    ~Foo();

    void do_something();

private:
    struct Impl;
    std::unique_ptr<Impl> priv;
};

// Inside source file (hidden from the outside world):
struct Foo::Impl
{
    ...
};

Foo::Foo(): priv(new Impl)
{
}
...

In Common

What these both have in common is that they serve the same fundamental premise: they hide the implementation details from the outside world, they reduce compile-time dependencies.

The abstract interface arguably does this ever so slightly more, not even exposing a Pimpl at all, though typically this is getting into really moot pedantic territory. It might be a favorable aesthetic to some, making only the public interface/documentation visible in the header (if header aesthetics are valued).

Both suffer from the need for dynamic allocation, an extra pointer, and disjointed memory representation from pointer to pointee as well as a potential loss of spatial locality from one pointee to the next. The memory inefficiency can be mitigated considerably for both cases by a fixed allocator to pool chunks of memory from a contiguous pool for pointees to use.

Different

Some things different between these two:

  • The abstract interface version requires a dynamic (aka virtual) dispatch overhead per member function call. This may be moot for deeper pipelines where a reasonable amount of work is done in a member function. It may turn into a minor hotspot for teeny member functions called in huge numbers (ex: millions and millions of times) in tight loops for some simple accessor function (both due to shallow pipeline and the optimization barrier which prevents inlining). I generally wouldn't worry about this too much pre-measurements, since you always have the option of turning IFoo from a pure interface into an abstract base class and inlining some critical functions if absolutely necessary without redesigning the whole thing.
  • The abstract version requires a vptr overhead per instance and a vtable to be generated per class.
  • The pimpl version can provide value/copy/move semantics directly without the need for yet another wrapper (the abstract version would require it, along with either virtual methods like clone or external functions like clone that are implemented inside the source file where Foo is visible).
  • The abstract version is arguably a little more straight-forward to implement (and arguably also read), as it lets you avoid dealing with managing this separate pimpl storing the implementation details and just lets you define a new class and override some virtual functions all in one go.
  • The abstract version is a little more flexible (ex: we can have two or more separate concrete implementations of IFoo living in the same codebase). This is getting away from striving for the apples to apples comparison, however, and noting more that this is apples to oranges.
  • The abstract version has a more fragile ABI, as adding any new virtual function anywhere will break the vtable ABI. This is only important in contexts like software development kits used by plugin developers where the binaries aren't all being built/distributed together.

I think that covers the main differences. Note that object slicing isn't a concern here provided that IFoo cannot be instantiated on its own.

4

With Pimpl, other code is free to create instances and use the class as normal. From the outside it looks exactly like a normal class. This is pretty convenient for a user but there is an efficiency penalty.

With the entire implementation of the class being private, your code is going to have to provide some way for the user to create instances - a factory method or something similar. Also, all your method calls will need to be virtual and the client code will need to use it by reference. Without knowing the actual type and size (etc) of the variable it can't be allocated on the stack or passed by value.

Both approaches protect client code from changes in the implementation but I don't think you can say they're exactly equivalent. Which one you use depends on the expected usage of the class and personally I've found uses for both patterns within the same project.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.