Follow-up Pimpl question

R

Rupert Swarbrick

I'm coming back to writing some C++ after a few years in Lisp-land, and
was wondering about the "pimpl idiom". I understand how to write and use
it, and have done in the past. However, I don't really understand what
it gains you over having an abstract base class in the header, along
with a factory function.

Presumably there's a significant difference, since people put up with
the reams of boilerplate required for passing functions through to the
implementation object. Can anyone explain to me what it is?


Rupert


PS: I'm not sure whether there are strong feelings about whether to use
this idiom or not. To be clear, I'm not trying to hear them! I can
see obvious downsides to keeping a pointer to an implementation
object (verbosity; have to be careful about destructor +
exceptions...) and I'm interested to know what the upsides are.
 
K

K. Frank

Hello Rupert!

I'm coming back to writing some C++ after a few years in Lisp-land, and
was wondering about the "pimpl idiom". I understand how to write and use
it, and have done in the past. However, I don't really understand what
it gains you over having an abstract base class in the header, along
with a factory function.

Presumably there's a significant difference, since people put up with
the reams of boilerplate required for passing functions through to the
implementation object. Can anyone explain to me what it is?

Well, first off, as you recognize, there is cost to
using the pimpl idiom, and I certainly won't argue
that it is always desirable of preferable.

However, one benefit is that for some use cases
constructors and destructors are very useful tools.

In particular they let you have local instances of
classes on the stack and those instances get cleaned
up automatically -- their destructors called -- when
they go out scope, even if scope is exited because
an exception was thrown. This is the main reason
that RAII plus executions / stack unwinding is so
powerful.

Of course there are other ways of achieving this.
For example you could use a smart pointer and a
factory function (but one might argue that doing
so is just reimplementing the pimpl idiom by another
name).
Rupert
...


Good luck.


K. Frank
 
Ö

Öö Tiib

I'm coming back to writing some C++ after a few years in Lisp-land, and
was wondering about the "pimpl idiom". I understand how to write and use
it, and have done in the past. However, I don't really understand what
it gains you over having an abstract base class in the header, along
with a factory function.

We call it "pimpl" since we like Orcish language perhaps, rest call it
"Cheshire Cat", "Compiler firewall" or "Bridge pattern".

There is better separation of concerns. Abstraction implements external
interface. Implementor implements internal implementation.

There are more dynamics. For example when object behind pointer to
interface is made then it can not change its type anymore in C++.
The implementor that is behind abstraction of pimpl however may be
is dynamically replaced, shared, cached, reused or copied-on-write etc.
by abstraction. It is not business of user of abstraction but
externally it may feel like type has changed entirely during life-time
of abstraction.

Slight performance advantage of pimpl is that the virtual functions
are not needed. There may be virtual functions as implementor may be
polymorphic ... but those are not mandatory. So virtual functions
may be used when those improve performance, not when they describe
common interface.
Presumably there's a significant difference, since people put up with
the reams of boilerplate required for passing functions through to the
implementation object. Can anyone explain to me what it is?

It is never clear if any of named advantages is substantial enough for
you.
PS: I'm not sure whether there are strong feelings about whether to use
this idiom or not. To be clear, I'm not trying to hear them! I can
see obvious downsides to keeping a pointer to an implementation
object (verbosity; have to be careful about destructor +
exceptions...) and I'm interested to know what the upsides are.

I must say that pimpl has its downsides too. If the problem has simple
solution then it is easy to make it more complex by adding unneeded
pimpl. We should aim to keep things as simple as possible (just not
simpler than possible). So pimpl is good for complex enough objects.
 
T

Tobias Müller

Rupert Swarbrick said:
I'm coming back to writing some C++ after a few years in Lisp-land, and
was wondering about the "pimpl idiom". I understand how to write and use
it, and have done in the past. However, I don't really understand what
it gains you over having an abstract base class in the header, along
with a factory function.

Presumably there's a significant difference, since people put up with
the reams of boilerplate required for passing functions through to the
implementation object. Can anyone explain to me what it is?


Rupert


PS: I'm not sure whether there are strong feelings about whether to use
this idiom or not. To be clear, I'm not trying to hear them! I can
see obvious downsides to keeping a pointer to an implementation
object (verbosity; have to be careful about destructor +
exceptions...) and I'm interested to know what the upsides are.

IMO the abstract base class pattern is not the right solution for this
problem. It may work as long as your objects are only given out, and never
taken back.
As soon as you have a method taking the abstract base class as parameter it
is not safe anymore.
While the method is formally taking an abstract base class, it actually
expects your concrete subclass instead (and must resort to casting).
I know no way for restricting a class to only be derived once. There is
nothing (except documentation) that prevents the client code from deriving
from your abstract base class and pass such an object to your method.

It is just an incomplete solution to the problem. OTOH, pimpl objects
behave like normal C++ objects in every single aspect. As long as you
restrict yourself to the public interface you could use the pimpl class as
a drop-in replacement of the actual implementation.

Tobi
 
J

James Kanze

I'm coming back to writing some C++ after a few years in Lisp-land, and
was wondering about the "pimpl idiom". I understand how to write and use
it, and have done in the past. However, I don't really understand what
it gains you over having an abstract base class in the header, along
with a factory function.
Presumably there's a significant difference, since people put up with
the reams of boilerplate required for passing functions through to the
implementation object. Can anyone explain to me what it is?

It allows value semantics, which the abstract base class
doesn't. Typical C++ makes extensive use of value semantics.

Note that you would never use the compilation firewall idiom
except for a class which had value semantics (and thus isn't
meant to be derived from).
 
R

Rupert Swarbrick

K. Frank said:
However, one benefit is that for some use cases
constructors and destructors are very useful tools.

In particular they let you have local instances of
classes on the stack and those instances get cleaned
up automatically -- their destructors called -- when
they go out scope, even if scope is exited because
an exception was thrown. This is the main reason
that RAII plus executions / stack unwinding is so
powerful.

Of course there are other ways of achieving this.
For example you could use a smart pointer and a
factory function (but one might argue that doing
so is just reimplementing the pimpl idiom by another
name).

Thank you, that makes a lot of sense. I guess std::unique_ptr makes this
considerably less painful, but the user of the class still has extra
hoops to jump through with the factory function.

Rupert
 
R

Rupert Swarbrick

Tobias Müller said:
IMO the abstract base class pattern is not the right solution for this
problem. It may work as long as your objects are only given out, and never
taken back.
As soon as you have a method taking the abstract base class as parameter it
is not safe anymore.
While the method is formally taking an abstract base class, it actually
expects your concrete subclass instead (and must resort to casting).
I know no way for restricting a class to only be derived once. There is
nothing (except documentation) that prevents the client code from deriving
from your abstract base class and pass such an object to your method.

Ahah! I hadn't thought of that!

But thinking further, I'm a bit confused about how important it
is. Functions that "take the object as a this pointer" work for free:
you add a pure virtual function to the abstract base class, have the
subclass implement it, then rely on virtual functions doing the right
thing.

So I suppose the problem is when I have this in foo.h:

class foo {
void something () = 0;
};

void take_a_foo (foo& x);


Is that what you mean? I've never used the abstract base class idiom
except when writing applications. Then, of course, you can just say
"don't derive from foo unless you are foo_impl" and declare the problem
solved.

I guess that in a library context, this is a bit harder. Presumably you
could use RTTI to throw an exception in the body of take_a_foo if x
isn't actually a foo_impl, but that's brittle and has an additional
performance penalty. (Interestingly, the take_a_foo example is the only
way of specialising methods in Lisp, but there you put up with slower
method dispatch in exchange for much more flexibility)
It is just an incomplete solution to the problem. OTOH, pimpl objects
behave like normal C++ objects in every single aspect. As long as you
restrict yourself to the public interface you could use the pimpl class as
a drop-in replacement of the actual implementation.

Hmm, I'm not convinced that this argument doesn't apply equally well to
an implementation of an abstract base class. That's what the "is-a"
relationship means, right?

Rupert
 
R

Rupert Swarbrick

James Kanze said:
It allows value semantics, which the abstract base class
doesn't. Typical C++ makes extensive use of value semantics.

Ah, because without being able to call the constructor of my
implementation class "by name", the client code will never be able to
get an actual object rather than some pointer? Thanks, that makes sense.
Note that you would never use the compilation firewall idiom
except for a class which had value semantics (and thus isn't
meant to be derived from).

Yep, that bit makes sense to me.


Rupert
 
R

Rupert Swarbrick

Öö Tiib said:
We call it "pimpl" since we like Orcish language perhaps, rest call it
"Cheshire Cat", "Compiler firewall" or "Bridge pattern".

Well, I could have been even more horrible and called it pImpl. Or maybe
p_impl? The trick is the glottal stop after the p to emphasise the
camel-case (and the recovering Java programmer?)
There is better separation of concerns. Abstraction implements
external interface. Implementor implements internal implementation.

There are more dynamics. For example when object behind pointer to
interface is made then it can not change its type anymore in C++.
The implementor that is behind abstraction of pimpl however may be
is dynamically replaced, shared, cached, reused or copied-on-write etc.
by abstraction. It is not business of user of abstraction but
externally it may feel like type has changed entirely during life-time
of abstraction.

Ah, I hadn't thought about the copy-on-write use case. But how is that
different to doing it on one or more members of an abstract base class's
implementation? Presumably more data members actually means that you
have more fine-grained control!
Slight performance advantage of pimpl is that the virtual functions
are not needed. There may be virtual functions as implementor may be
polymorphic ... but those are not mandatory. So virtual functions
may be used when those improve performance, not when they describe
common interface.

That doesn't make much sense to me. Surely every public function in your
class has to look something like

void interface::function (int x)
{
pimpl->function (x);
}

which... is a virtual function table, just manually written out.


Rupert
 
Ö

Öö Tiib

Ah, I hadn't thought about the copy-on-write use case. But how is that
different to doing it on one or more members of an abstract base class's
implementation? Presumably more data members actually means that you
have more fine-grained control!

Derived classes have one or more polymorphic members accessible from base
abstract interface? It is even more complex.
That doesn't make much sense to me. Surely every public function in your
class has to look something like

void interface::function (int x)
{
pimpl->function (x);
}

Pimpl typically does not have exact same names in its interface:

void const* interface::address() const
{
return &pimpl_->data();
}
which... is a virtual function table, just manually written out.

If something does not make sense to you then measure. ;)
Compilers are quite happily removing such thin one liner forwarding
functions by inlining. Nothing like that is done with vtable.
 
R

Rupert Swarbrick

Paavo Helde said:
No, if the function is not virtual, then this translates approx. to:

PimplClass::function(pimpl, x);

The address of the function to call is fixed at compile/link time, no
lookup needed from anywhere. Thus optimizers can also inline this call
(even if it is in another translation unit, though it is harder then).

Ahah. I hadn't thought of that. Well, I guess that it will always be in
another translation unit (since if it wasn't, you wouldn't get the
compile time separation that's the whole point of the idiom). But I see
that a sufficiently clever compiler could inline it. Do existing
compilers deal with that though? It doesn't seem like something a linker
could/should do, and doesn't a compiler normally operate one translation
unit at once?

Rupert
 
K

K. Frank

Hello Paavo (and Rupert)!

This is called whole program optimization or link time optimization and
requires cooperation between compiler and linker. Basically compiler emits
(parts of) compiled code in some special or intermediate representation,
and the linker generates actual code. Both current MSVC and gcc support
this, don't know about others. The drawback is of course that the linking
step takes enormous time (compared to ordinary linking) and cannot be
(currently?) parallelized.

Of course, now if you change your pimpl implementation, you
have to go back and re-link-time-optimize the client code
that calls it, thus defeating one of the main benefits of the
pimpl (compiler firewall) idioim. Sure, technically, LTO is
not compilation, but it's a kissing cousin.

Well, you can't win for losing ...
Cheers
Paavo


Cherrio!


K. Frank
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top