Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2002-04-19 18:04:18


"Andrei Alexandrescu" <andrewalex_at_[hidden]> wrote in message
news:a9q2jv$knn$1_at_main.gmane.org...
> I found many of Fernando's arguments insightful; as he said, they mostly
> relate to misusing policies as an interface device (rather than an
> implementation device). Please bear with me.
>
> > The first thing I noticed that would happened if I used a policy *as an
> > additional template parameter*, that is, an "intrusive policy", is that
> >
> > optional<A,Policy1> and optional<B,Policy2> become distinct types.
>
> Just a nit: optional<A> and optional<B> are distinct types from the
get-go,
> so that doesn't mark any difference.
>
Oops! I meant to write 'optional<A,Policy2>'

> > In the case of optional<>, it is clear to me that this is a mistake,
> because
> > these Policies describe an implementation detail of optional<> (whether
it
> > should /shouldn't bypass A's def ctor), so they souldn't be part of the
> > static-type.
>
> I agree with that. When the design decision is bound to the type, depends
> solely on it, and is unlikely to change, it is best that that decision is
> made through a trait.
>
OK.

> > In the case of smart_ptr<> however, it could be argued that a given
policy
> > must be accounted as part of the static-type of the smart pointer.
>
> My point exactly :o).
>
> > Scenario 1: I want to write a class which does something with a given
> > source object. This class needs to keep the object alive, but it is
quite
> > possible that the caller disposes the object even before this class
> > completes its task. This scenario calls for a common pattern:
> >
> > class Processor
> > {
> > Processor ( smart_ptr<A> const& obj ) : m_obj(obj) {}
> > ...
> > smart_ptr<A> m_obj ;
> > } ;
> >
> >
> > The important thing in this example is that all I really know of is A,
and
> > that I need it to be alive as long as Proccesor needs it.
> > If smart_ptr<> is intrusively-policy-based, I -the user of A- must make
> > decisions about which of those policies would fit here. Some of those
> > decisions might be hard to take on this side of the system, but even if
> can
> > take them, there is still this problem:
>
> I understand. So the problem has two aspects (1) you don't want Processor
to
> make policy decisions on how smart pointers to A should look like, (2) you
> don't want to add clutter (in form of template arguments) to Processor to
> have it support various policies that smart_ptr might have.
>
> This is the typical example where a template typedef would be extremely
> useful. Again, policies are only an implementation device: they help you
> with not having to write various flavors of smart pointers from scratch.
>
> Absent typedef templates, one can use (1) a straight typedef smart_ptrA
> that's used as an interface device for passing smart pointers to A's all
> around, or (2) the "nested typedef" trick:
>
> template <class T> struct SmartPtr
> {
> typedef Loki::SmartPtr<T, ...> Type;
> };
>
This looks pretty much the same to what I'm after...
See at the bottom...

> Then you implement the communication between various components by using
> SmartPtr<T>::Type.
>
> The alternate design you mention is to customize smart_ptr with traits.
This
> is quite an attractive design, especially given that you can define traits
> for a whole hierarchy (or sub-hierarchy) in a single shot - no
duplication!
> See http://www.moderncppdesign.com/publications/traits.html and
> http://www.moderncppdesign.com/publications/traits_on_steroids.html.
>
> There are things that work best with traits and things that work best with
> policies. Please allow me to do a short comparative analysis below.
>
> 1. A trait binds a type, or a (sub)hierarchy, to a capability. That
binding
> is not optional and irrefutable. You can provide a default capability for
> types you know nothing about.
>
Agreed.

> An important, well, trait of traits [:o)] is that you cannot define traits
> for types you know nothing about. Instead, you define an interface for the
> capability abstracted by the trait, provide maybe a default, and have
users
> to define their own traits if they need to.
>
Yes.

> In the example of SmartPtr, one can argue that for a given type, only one
> ownership method will ever be used throughout the application. For
example,
> if you have COM types, you'll never use *anything* but COMRefCounting for
> those types. If you have CORBA types, you'll never use *anything* but
> CORBARefCounting for those types. If you have... you get my drift. This
> would suggest that ownership is best made a trait (likely a hierarchy-wide
> trait) and not a policy. However, imagine you have a Widget object. Some
> smart pointers to Widgets are copied between threads, some are not. It is
> reasonable to aim at a design that incurs the MT overhead only when
needed.
> So in this case, the traits-based approach falls short because it cannot
> define two traits for the same Widget type.
>
I agree.

> Continuing the SmartPtr study, let's move on to the checking policy. Is
that
> at best a trait, or not? Practical experience suggests it's not, this time
> for a slightly different reason.
>
> Many times, checking might be a system (or subsystem) wide decision. That
> is, you compile a whole module with "null dereference checking" on, and
you
> know that that subsystem will fail safely whenever that particular problem
> occurs. So in this case, what checking method to use transcends multiple
> types and hierarchies. Again, the traits-based approach falls short
because
> (1) different parts of the same application might want to use different
> checking methods for the same type;
> (2) you want to change that capability
> once in a shot, and not for every type. You can do the latter with traits,
> with enough awkwardness to strengthen the argument against its
> effectiveness.
>
I agree that traits only work if the checking policy is world-wide (entirely
one way or the other).

> On to the Storage policy. Is storage bound to type, or not? The primary
> purpose of Storage is to give the SmartPtr user a crack at defining
> alternate represenations, a custom default value, and on-the-fly
> initialization. It also allows the user to define proxies by having
> stored_type be itself a sort of smart pointer. I didn't really experiment
a
> lot with custom storages, but my intuition says that sometimes you don't
> want to bind all that to a specific type.
>
Of course a policy allows for a lot more choices than a trait.

What I think we need to do is to find a way to 'mesaure' the flexibility of
a type-specific strategy and of a wrapper-specific strategy. Then we can
balance both tradeoffs and see which one is better.

> 2. Policy-based design allows you to statically bind design decisions to
the
> designs that you make.
Ineed. The problem is that either the architect fix those desicions into a
very small set, or the users must deal with the combinatorial complexity in
client code.

> This approach is very natural for many designs. As I
> will show shortly, while you cannot simulate policies with traits, you can
> do with policies everything you do with traits, and much more.
>
Yes.

> Policies are best used instead of traits when you want to want to
implement
> the same capability of a design over a range of types,
>
You can do this with traits (unless I misunderstood the statement).

> or when you want to
> use different design capabilities of an artifact with the same type.
>
Yes.

> Let's get back to the SmartPtr multithreading analysis. In that case, it
is
> best to use a policy-based design and use SmartPtr<Widget, RefCounting>
and
> SmartPtr<Widget, MtRefCounting>. The fact that these are different types
is
> actually quintessential to the well-functioning of the design. It is under
> programmer's control /if/ and /how/ smart pointers with different
> capabilities are copied to each other.
>
Agreed.

> If you do want to bind a capability to a specific type or hierarchy, you
can
> do that with policy-based design. All you need to do is to define a policy
> and specialize it appropriately for the types or hierarchies that you
need.
> For example, consider you want to use COM-style refcounting for COM
objects
> and straight refcounting for all others.
>
> template <class T> struct AutoRefCounting
> {
> typedef Select<is_base_derived<IUnknown, T>::result,
> COMRefCounting,
> RefCounting>::Result;
> };
>
> Then you can specialize AutoRefCounting to define specific reference
> counting capabilities for specific types or hierarchies. I believe this is
a
> good approach, and typedef templates will only make it much more
> comfortable.
>
Yes. But without typedef templates, such policies can be left external since
they don't need to be specified if a fixed template-id is used.

> > Scenario 2:
> >
> > It is quite common to specialize certain things -such as predicates- on
a
> > smart_ptr<>. This is in order to allow certain expressions to be applied
> > indistinctely to bare and smart pointers.
> > For example, the boost shared-ptr declares:
> >
> > // get_pointer() enables boost::mem_fn to recognize shared_ptr
> > template<typename T> inline T * get_pointer(shared_ptr<T> const & p) ;
> >
> > This is used -as the comment says- inside mem_fn which allows a very
> > powerful usage of mem_fn.
> >
> > If shared_ptr were intrusively policy-based, this wouldn't be feasible
> > becuase mem_fn couldn't know about the smart pointer policies.
>
> Because the example lacks enough context, I cannot comment on it. Maybe in
> light of the comparison above, a solution will naturally pop up.
>
OK.
Just think about how you, as a user of some object A, which you assume in
advance could be used via a smart pointer, will get ahead and provide
specializations of some key things (say, std::less) if you have to deal with
a template class with a lot more than a single parameter.
You can do it, but you'll need a lot of complex metaprogramming machinery.

> > Just as is the case with optional<>, a smart pointer is a library fature
> so
> > general that the ability to express a smart pointer as a
single-parameter
> > template class: smart_ptr<T> is so powerful that directly competes with
> the
> > power of the flexibility given by a policy-based class.
>
> I disagree with that.
>
> > Dietmar already shown -and I totally agree- that most of the smart ptr
> > policies are usually type dependent, so they perfectly call for external
> > policies. (BTW, the sketch made by Doug seems very reasonable).
>
> As shown above with examples, I believe the precise contrary. The examples
I
> gave clearly show that all policies might need to be decoupled from types.
>
> > The problematic case is the threading policy which would certainly loose
> too
> > much of its flexibility if it is specified externally in a global basis,
> as
> > Andrei warned.
>
> The problematic case is when you misuse policies.
>
> > IMHO, Dietmar has raised very strong points.
> >
> > I believe that most of the important policy choices could be made
> > externally, based only on the type. This will cut-down a lot of the
> > combinatorial possibilities, but, IMO, will still give a significant
> degree
> > of flexibility while allowing the system to retain the smart_ptr<T>
simple
> > signature which I've shown (I hope) to be fundamental.
>
> IMHO, the points raised are invalid. The discussion starts from an example
> of misuse of policies, and furthermore, does not propose a better design,
as
> I tried to show above.
>
> There is nothing fundamental about smart_ptr's simple signature (whatever
> that means). Template typedefs are not a fundamentally new addition to the
> languages, and even their simulation in today's C++ renders the points
moot.
>
>
I see.
This last statement seems to be the central point of our disagreement: we
really want to retain a simple: smart_ptr<T> interface even at the expense
of fixing a lot of the design strategies.
The statement that policies 'might need' to be decoupled from types is
true/false depending precisely on the needs of a general user of a general
smart pointer facility.

I personally believe that most of the really needed policies *can* be bound
to the type, in spite of the irrefutable fact that this looses flexibility,
in order to retain a simple smart_ptr<T> interface on client code.

Getting back to the generator example:

> template <class T> struct SmartPtr
> {
> typedef Loki::SmartPtr<T, ...> Type;
> };

Imagine that there is also: MtSmartPtr<T>, CheckingSmartPtr<T>, etc..

Who is supposed to write this?
If your answer is the system architect, which means that nearly all the code
in the system will use SmartPtr<T> and the others, then we are actually
saying just the same. Because all the arguments against additional template
parameters as intrusive policies were targeted to the template class used by
the ordinary user in every-day code.

As Dietmar said, it is not only correct, but actually the most logical
choice, to use Loki::SmartPtr to implement the fixed small set of choices
that are available for general usage.

But in this case, SmartPtr<T>, MtSmartPtr<T>,CheckingPtr<T> doen't really
need to be generators, because they are fixing policies to a unique type;
they can be single parameter template classes (conventional smart pointers)
which happen to use traits classes and Loki::SmartPtr to assemble their
implementations based on a fixed mapping between the type and some policies.
If they are generators, they can bring all the power of wrapper-specific
policy selection, but then they wouldn't in general have fixed template-ids.

We actually have just one disagreement: I think that having a bounded set of
single-parameter template classes with fixed template-ids is the right
choice as a general purpose smart pointer facility because it sets a
*uniform* idiom for dealing with the general problem of keeping objects
alive as needed.
All my arguments are based on this statement. Were this be wrong, then IMO,
you would be entirely right.

Eventually, generators like the one above could be used to achieve something
similar to the uniformity of idioms I'm looking for.

--
Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk