Pimpl Idiom VS Pure Virtual Class Interface

Pimpl idiom vs Pure virtual class interface

When writing a C++ class, it's appropriate to think about whether it's going to be

  1. A Value Type

    Copy by value, identity is never important. It's appropriate for it to be a key in a std::map. Example, a "string" class, or a "date" class, or a "complex number" class. To "copy" instances of such a class makes sense.

  2. An Entity type

    Identity is important. Always passed by reference, never by "value". Often, doesn't make sense to "copy" instances of the class at all. When it does make sense, a polymorphic "Clone" method is usually more appropriate. Examples: A Socket class, a Database class, a "policy" class, anything that would be a "closure" in a functional language.

Both pImpl and pure abstract base class are techniques to reduce compile time dependencies.

However, I only ever use pImpl to implement Value types (type 1), and only sometimes when I really want to minimize coupling and compile-time dependencies. Often, it's not worth the bother. As you rightly point out, there's more syntactic overhead because you have to write forwarding methods for all of the public methods. For type 2 classes, I always use a pure abstract base class with associated factory method(s).

C++ Pimpl vs Pure Virtual Interface Performance

The two options are not equivalent, they should not be compared on performance as the focus is different. Even if they were equivalent, the performance difference would be minimal to unimportant in most situations. If you are in the rare case that you know that dispatch is being an issue, then you have the tools to measure the difference yourself.

keeping private parts outside c++ headers: pure virtual base class vs pimpl

I can think of a few differences:

With the virtual base class you break some of the semantics people expect from well-behaved C++ classes:

I would expect (or require, even) the class to be instantiated on the stack, like this:

BananaTree myTree("somename");

otherwise, I lose RAII, and I have to manually start tracking allocations, which leads to a lot of headaches and memory leaks.

I also expect that to copy the class, I can simply do this

BananaTree tree2 = mytree;

unless of course, copying is disallowed by marking the copy constructor private, in which case that line won't even compile.

In the above cases, we obviously have the problem that your interface class doesn't really have meaningful constructors. But if I tried to use code such as the above examples, I'd also run afoul of a lot of slicing issues.
With polymorphic objects, you're generally required to hold pointers or references to the objects, to prevent slicing. As in my first point, this is generally not desirable, and makes memory management much harder.

Will a reader of your code understand that a BananaTree basically doesn't work, that he has to use BananaTree* or BananaTree& instead?

Basically, your interface just doesn't play that well with modern C++, where we prefer to

  • avoid pointers as much as possible, and
  • stack-allocate all objects to benefit form automatic lifetime management.

By the way, your virtual base class forgot the virtual destructor. That's a clear bug.

Finally, a simpler variant of pimpl that I sometimes use to cut down on the amount of boilerplate code is to give the "outer" object access to the data members of the inner object, so you avoid duplicating the interface. Either a function on the outer object just accesses the data it needs from the inner object directly, or it calls a helper function on the inner object, which has no equivalent on the outer object.

In your example, you could remove the function and Impl::getBanana, and instead implement BananaTree::getBanana like this:

Banana* BananaTree::getBanana(string const& name)
{
return pimpl_->findBanana(name);
}

then you only have to implement one getBanana function (in the BananaTree class), and one findBanana function (in the Impl class).



Related Topics



Leave a reply



Submit