Making all members of pimpl classes public

  • Thread starter Asfand Yar Qazi
  • Start date
A

Alf P. Steinbach

* Phlip:
DPs are logical design. Pimpl is physical design. That means you could take
the pimpl out, and all the client classes would have exactly the same design
and contents.
Nope.


Except they'd take longer to compile, and would be exposed to
all the stray identifiers in the excess headers, that could change their own
identifier's meanings in subtle ways,

And not so subtle ways, like, not compiling at all.

if your C++ codebase was already circling the drain anyway.

Derogatory language isn't conducive to civilized discussion, Phlip.

Anyway, you've missed the point.

Else why put it in?

See above. Or google. ;-) Or, forget Ruby for a while, and try to
wrap some old C graphics library in C++, for example.
 
P

Phlip

Alf said:

Please pick none or more:

[ ] Pimples are good and should be designed-for
[ ] the Introduce Pimple Refactor changes client classes
[ ] the Introduce Pimple Refactor introduces opportunities for
clients to simplify

Please back up your choice, and please assume the extra .h files that Pimpl
hides were themselves well-factored and insulated from the clients that
didn't use them directly and wants to become ignorant of them.
 
P

Phlip

Aleksander said:
...I believe that pImpl is a specific case of another DP called Facade.

"Intent: Provide a unified interface to a set of interfaces in a subsystem.
Facade defines a higher-level interface that makes the subsystem easier to
use."

They are indeed very close. But Pimpl provides only one interface for only
one class. Not a set of interfaces and not a subsystem. And Pimpl does not
make the subsystem easier to use, because all the clients call all the same
methods on the wrapper class; not reduced or simplified methods.

Yes yes yes it makes it easier to compile and easier to insulate. /DP/ uses
"easier" in the logical design sense, not the physical design sense.

If, however, you then procede to simplify the methods in your wrapper class,
or pull more implementation classes under its umbrella, then you indeed have
Facade.
 
A

Alf P. Steinbach

* Phlip:
Alf said:

Please pick none or more:

[ ] Pimples are good and should be designed-for
[ ] the Introduce Pimple Refactor changes client classes
[ ] the Introduce Pimple Refactor introduces opportunities for
clients to simplify

Please back up your choice, and please assume the extra .h files that Pimpl
hides were themselves well-factored and insulated from the clients that
didn't use them directly and wants to become ignorant of them.

I don't see any point; this is rubbish.
 
A

Alf P. Steinbach

* Phlip:
And "Derogatory language isn't conducive to civilized discussion". ;-)

Right.

There's a difference between (1) trying to associate something with
something bad, and (2) saying right out that rubbish is rubbish. The
first is derogatory (see the nearest dictionary definition); it appeals
to emotion, which is a well-known fallacy. The second is /opposite/.
 
W

werasm

Yep, I see your problem. You still need need to know your type at
runtime in order to instantiate it. You need to separate your type
information from your creation.
Yes.


}
base* create(std::string id) {
return factory_[id]();
}

What about totally unrelated types. Types who do not derive from base?
Yes, I noticed Alexandrescu's solution addresses this with scattered
hierarchies. Yours don't. Derived's type is derived from base, which is
exactly what MakeX would give you in:

struct X{...};
struct XD : X{...};

struct Factory
{
X* makeX() const;
}
struct DFactory: Factory
{
XD* makeX() const;
}

Here XD represents your derived. I see no added advantange when
weighing your example against the original. The other thing you did not
address is constructors with many arguments. I know prototyping is one
way to address this. Unfortunately it is not good enough as not all
aggregates of the same type requires the same prototype.
int main() {
Factory factory;
factory.Register(std::string("base"), &createBase);
factory.Register(std::string("derived"), &createBase);

base* b = factory.create(std::string("base"));
return 0;
}

Less than ideal example...

How about:

Client::Client( const Factory& f)
{
//I'm still exposed to the interface of the entire factory... no
gains!
//What if I were required to make a type not derived from base?
}

What I'm getting at, is that each factories interface must be
ultra-concise (narrow). It should only represent one type. Andrei does
achieve this, yes - by means of the leaves of the scattered hierarchy.
The Clients can now state:

Client::Client( const AbstractFactoryUnit<MyServer>& f)
{
//Now I'm only exposed to the interface of the generic factory that
never changes,
// as well as that of MyServer, which is ok as I'll be using it. Of
course, if I'm only passing
// it along this is not necessary (Forward declare is sufficient).
}

This does not entirely solve the many arguments to a constructor
problem. I have scanned chapter 9 in Modern CPP briefly, but did not
see this being addressed (but I may have been to brief). I read the
part about using prototype. Yes, one only has one prototype per type,
and that prototype may not be the one that the client is requiring.
Sometimes the client may have data that may influence the prototype
(i.e. the prototype is constructed differently due to requirements by
client), right? This is rare though, and the fact that the interface of
the factory is narrowed down to one type helps allot. Agreed.

Now, my point is - making the client dependent on a non-abstract class
(and using an abstract pimpl as bridge), may solve some problems not
solved by Factories and Prototypes (like providing arguments to
constructors). Other solutions are what I call <delayed construction>
idiom. This is similar, actually, to the solution in MCPP design.
Simply:
template <class T>
struct CreationAbstractor
{
virtual T* create() const = 0;
}

Client::Client( const CreationAbstractor<MyType>& myTypeCreator )
: myType( myTypeCreator.create() )
{
}

Now the Client can dicate quite easily what type he requires. Whoever's
responsible for instantiating Client, provides the necessary
construction parameters:

struct MyTypeAbstractor : public CreationAbstractor<MyType>
{
MyTypeAbstractor( int x, int y, int z);
virtual MyType* create() const{ return new DerivedFromMyType( x,y,z
); }

private:
int x_;
int y_;
int z_;
};

This does assume that the one creating client, has enough info to
provide the construction parameters. Note that the one creating client
does not need to be exposed to the interface of MyType, though.

Kind regards,

Werner
 
P

Phlip

werasm said:
What about totally unrelated types. Types who do not derive from base?

If they don't, then clients of the factory cannot use them interchangably,
so abstracting their construction has no point.

Look up "Liskov Substitution Principle".
 
W

werasm

Phlip said:
If they don't, then clients of the factory cannot use them interchangably,
so abstracting their construction has no point.

Look up "Liskov Substitution Principle".

LSP - I have looked this up years ago (Robert Martin -
objectmentor.com). Why not just give me the URL :).

And I've also read the other object mentor related things. You're
missing the point. AbstractFactory according to GOF is used for a whole
range of products. MCPP Design is iaw. this. The factory caters for
products with unrelated bases - read it. The range of products don't
require a common base - actually, I think it's detrimental to have a
common base.

My point is, that if one uses concrete interfaces, factories aren't
required (at least not by the client). The alternative has pros and
cons, but consider it as alternative to what your propose, and realise
that pimpl has more uses than you've advocated. OOP Library components
can be concrete in their entirety. All the application needs to do, is
to instantiate the correct instance of the lib (the one iaw. his
platform needs). This then in turn creates a library factory, who's
interface is hidden from clients, and that is only used by library
components (bridge pattern). The client then depends on consistent
concrete interfaces, and create these calling normal constructors. They
in turn use the factory to create underlying products, oblivious to the
client. Obviously, above mentioned method is not applicable to generic
libraries such as STL and boost.

My other point is, that if you use AbstractFactories - be sure that the
interface that the client requires to create her aggregate is very
narrow. Alexandrescu also emphasizes this in MCPP Design (scattered
hierarchies). This can also be achieved by the CreationAbstractor<T>
mentioned above.

Regards,

W
 
P

Phlip

werasm said:
LSP - I have looked this up years ago (Robert Martin -
objectmentor.com). Why not just give me the URL :).

RCM didn't invented it. But I would have to google too, so you may as well
take the shot. You will find many, many different explanations and
tutorials about LSP. Some consider it the core topic of all of Object
Oriented Programming.
And I've also read the other object mentor related things. You're
missing the point. AbstractFactory according to GOF is used for a whole
range of products. MCPP Design is iaw. this. The factory caters for
products with unrelated bases - read it. The range of products don't
require a common base - actually, I think it's detrimental to have a
common base.

Until now I have avoided discussing the two /DP/ factories, because most
programs need only Prototype, or less.

Abstract factory returns common base types for each element it creates.
There's no common base of all elements; there is at least one for each
type. The example distinguishes Window with PMWindow and MotifWindow, then
ScrollBar with PMScrollBar and MotifScrollBar. Of course Window and
ScrollBar have no common base class. Clients of AbstractFactory are still
insulated from PM and Motif.

FactoryMethod encapsulates creation within a derived method. So (in C++) the
returned type must be a member of a type hierarchy.
My point is, that if one uses concrete interfaces, factories aren't
required (at least not by the client). The alternative has pros and
cons, but consider it as alternative to what your propose, and realise
that pimpl has more uses than you've advocated.

Can you please rename the thing you so eagerly defend to something other
than Pimpl? I fully support defending that thing!

And (per /DP/) there's no such thing as a "concrete interface", and designs
should program "to the interface".
OOP Library components
can be concrete in their entirety. All the application needs to do, is
to instantiate the correct instance of the lib (the one iaw. his
platform needs).

Right - polymorphism strikes again. The interface to the lib is still
abstract, still virtual. It just might not use the virtual keyword.
This then in turn creates a library factory, who's
interface is hidden from clients, and that is only used by library
components (bridge pattern). The client then depends on consistent
concrete interfaces, and create these calling normal constructors. They
in turn use the factory to create underlying products, oblivious to the
client.

Uh, sure, if you actually need all that...
Obviously, above mentioned method is not applicable to generic
libraries such as STL and boost.

Whyyyyyyyyy not?
My other point is, that if you use AbstractFactories - be sure that the
interface that the client requires to create her aggregate is very
narrow. Alexandrescu also emphasizes this in MCPP Design (scattered
hierarchies). This can also be achieved by the CreationAbstractor<T>
mentioned above.

All interfaces should be narrow. Per RCM, there should be one, different,
interface for each category of client to an object.
 
W

werasm

Phlip said:
werasm wrote:

RCM didn't invented it.

No, Barbara Liskov did. I think references can be found in Herb Sutters
EC++.
Until now I have avoided discussing the two /DP/ factories, because most
programs need only Prototype, or less.

Yes, I realised we weren't talking about the same thing. You were
talking about Factory Method, I was talking about Abstract Factories.
The latter is more common for creating families of products.
Abstract factory returns common base types for each element it creates.

Thank you for that bit of insight :).
There's no common base of all elements; there is at least one for each
type. The example distinguishes Window with PMWindow and MotifWindow, then
ScrollBar with PMScrollBar and MotifScrollBar. Of course Window and
ScrollBar have no common base class. Clients of AbstractFactory are still
insulated from PM and Motif.

Yes, I read GOF years ago. I see you use their example well.
FactoryMethod encapsulates creation within a derived method. So (in C++) the
returned type must be a member of a type hierarchy.

Yes, we weren't talking about the same thing. I was giving AFactory as
example from the start of my argument
Can you please rename the thing you so eagerly defend to something other
than Pimpl? I fully support defending that thing!

"The Bridge Pattern" - which makes use of the "Pimpl idiom". Yes, we
need to discern between Architectural Patterns, Design Patterns and
Idioms. Pimpl is the latter, that is used in a Design Pattern (bridge).
And (per /DP/) there's no such thing as a "concrete interface", and designs
should program "to the interface".

Ohhh, but you do get something with a consistent interface. Does that
require to be abstract as well? Actually, most things have consistent
interfaces. Its the implementation that varies, mostly. Read about
"Bridge Patten" - RCM tells you all about that too. I got most of my
OOP ideas from him.
Right - polymorphism strikes again. The interface to the lib is still
abstract, still virtual. It just might not use the virtual keyword.

Not true. Certainly not for STL and BOOST (but they not applicable in
this discussion as they are not pure OOP libs. The paradigm "generic
programming" comes into play).
Uh, sure, if you actually need all that...

Oh, makes it alot easier for the client.
Whyyyyyyyyy not?

Because of the paradigm shift, see.
All interfaces should be narrow. Per RCM, there should be one, different,
interface for each category of client to an object.

Yes, I've emphasized that already. This is a good rule of thumb.
Exceptions are always a consideration, though. As long as one knows the
pros and cons.

W
 
W

werasm

Not true. Certainly not for STL and BOOST (but they not applicable in
this discussion as they are not pure OOP libs. The paradigm "generic
programming" comes into play).

Sorry, I misread what you were saying. You right - this is yet another
form of polymorphism - without the virtual keyword (at least from
clients perspective). The interface to the library is abstract in the
sense that the true implementation is only decided at runtime by the
Abstract Factory, however, as far as physical structure is concerned
(in coding terms), it is non-abstract i.e. the client instantiates a
non-abstract class. If you reason that a class is abstract just because
it aggregates abstract classes, well then yes - in that case all
classes are always abstract, because most class interface to abstract
classes.

Regards,

W
 
P

Phlip

werasm said:
"The Bridge Pattern" - which makes use of the "Pimpl idiom". Yes, we
need to discern between Architectural Patterns, Design Patterns and
Idioms. Pimpl is the latter, that is used in a Design Pattern (bridge).

Suppose we write a UML diagram on a whiteboard. Now you point to one box,
and say "oh, make that one a Pimpl".

I can do that by drawing a jagged line across the box and writing <<pimpl>>
on it. Expressing the trivial extra design in UML is possible, but it's
still implementation-level, below the level of the abstract design.

I can't simplify any other class's design. (Gee, of course I can simplify
their headers, and that might take pressure off them. I can't simplify them
in terms of what methods they call on our Pimpl-ed class.)

Now suppose you point to one box and say, "Escallate that into interfaces,
one for each category of clients". I shouldn't write that as a <<tag>>. I
should write some base classes, and should point the clients at them. This
provides an opportunity to simplify those classes. And, because C++ is
wonderful^W pragmatic^W vaguely utilitarian, a client that depends on an
abstract base class doesn't need to compile all the headers that its
derived class implementation needs.

You guys have gotten hung up on Pimpl as if it were a high-level design
pattern, or a good goal. It's just a technique. Clean code with a mature
design doesn't need it.
 
W

werasm

Phlip said:
You guys have gotten hung up on Pimpl as if it were a high-level design
pattern, or a good goal. It's just a technique. Clean code with a mature
design doesn't need it.

Well, seems you know UML too :->. Congats.
Clearly, You did not read what I wrote, else your response would have
been either naught, or different. I stated that the pimpl is and idiom
e.g. a implementation technique - a use to a means. The Design Pattern
is called "The Bridge Pattern". The "C++" idiom "Pimpl" is used in C++
to implement the Bridge Pattern. What you stating is in actual fact the
exact opposite. We only use pimpl to implement a high level design
pattern (like a list is used to implement the Subject-Observer Pattern
and the SO Pattern is used to implement the MVC Architectural Pattern.
I say again and again and again. It's only an idiom. Sometimes it is
used to, as you mentioned - fix the rotten smell. Other times, it has
roles to play in good design. If you can't see this, well - so sad,
your loss.

Kind regards,

Werner
 
W

werasm

Suppose we write a UML diagram on a whiteboard. Now you point to one box,
and say "oh, make that one a Pimpl".

No, I would not say that. I would expect you to use the best
implementation technique for the circumstance. I'm also, like you not
interested in the details. This is however not a design group and the
details are relevant.
I can do that by drawing a jagged line across the box and writing <<pimpl>>
on it. Expressing the trivial extra design in UML is possible, but it's
still implementation-level, below the level of the abstract design.

So? What has this got to do with my previous post.
I can't simplify any other class's design. (Gee, of course I can simplify
their headers, and that might take pressure off them. I can't simplify them
in terms of what methods they call on our Pimpl-ed class.)

We just realise the interfaces (or the requirement), whether
implemented as abstract or not is not relevant during conceptual
design.
Now suppose you point to one box and say, "Escallate that into interfaces,
one for each category of clients". I shouldn't write that as a <<tag>>. I
should write some base classes, and should point the clients at them. This
provides an opportunity to simplify those classes. And, because C++ is
wonderful^W pragmatic^W vaguely utilitarian, a client that depends on an
abstract base class doesn't need to compile all the headers that its
derived class implementation needs.

Yes, I agree with this. You mention the one case here. I will go into
my way of realising interfaces. Usually, I start with an entity (Bob).
Bob provides some services, and it requires some services. Services are
required from other entities. This realises the relationships that Bob
need to have. Some of these relationships are really vague. In some
cases I'm also not sure whether services required from particular
entities may vary at a later stage. Other entities (like STL and BOOST
lib entities) are crystal clear and I know that they wont change.
Therefore no abstraction is required when using these. A platform
peripheral like a mutex is an example of this. It's interface is known
to the extent where it has become mature enough to not change, but it
may have many implementations, which it gathers as new platforms (or
changes to platforms) comes along. In this case, the service provider
existed before the entity required it. If new implementation comes
along, Bob does not want to know about it (him wanting to be
cross-platform).

So, I have ask myself long ago - when shall I use the pimpl idiom, and
when not? I came to this conclusion.

1) When I write a class that provides a concise service but whose
implementation may vary, and this class is realised prior to his
clients requiring his realisation, to protect them from variance in
implementation, pimpl will do.

2) If I am a client (Bob) and I require services and I do not want to
be dependent on my service providers interfaces, as the likelyhood of
their interfaces varying at a later stage are great, then I will make
myself depended on interfaces defined in the same package (or
namespace, or logical organisational unit) that I'm in.

3) Another reason for pimpl - come to think of it! If I require to
execute within my own context (not within the context of the caller),
then I make use of a concrete interface whos member functions are
called synchronously. They in turn use some mechanism to call the pimpl
methods asynchronously.

.... and many more.
You guys have gotten hung up on Pimpl as if it were a high-level design
pattern, or a good goal. It's just a technique. Clean code with a mature
design doesn't need it.

Hmmm, who ever said anything about high level design pattern. We
actually said the exact opposite. Do you even read what we write, or
are you to hung up about what you know?

Regards, of the kind sort.

W
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top