Topic: Design Mistake?


Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/06/15
Raw View
In article <394569C5.580509B3@tesco.net>, Jonathan de Boyne Pollard
<J.deBoynePollard@tesco.net> writes
>I disagree.  A public IS-A relationship without all of the baggage that comes
>with polymorphism can be quite useful at times.

If you mean what you wrote then you do not understand what an is-a
relationship is.

If a derived class cannot always substitute for the base class it is a
design fault to treat it as if it could be.  This despite many books to
the contrary.  OO in C++ is not OO in SmallTalk.


Francis Glassborow      Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Jonathan de Boyne Pollard <J.deBoynePollard@tesco.net>
Date: 2000/06/13
Raw View
FG> But public inheritance from a class with a non-virtual public
FG> dtor is, I believe, an error.

I disagree.  A public IS-A relationship without all of the baggage that comes
with polymorphism can be quite useful at times.

Not all class hierarchies involve polymorphism.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/06/02
Raw View
In article <8h195o$sdd$1@nnrp1.deja.com>, sirwillard@my-deja.com writes
>If a compiler would do this we'd have a lot of warnings, since there
>are several areas in which the standard classes do this.

But public inheritance from a class with a non-virtual public dtor is, I
believe, an error.  I think that such cases in the Standard C++ Library
should be reported as defects.


Francis Glassborow      Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/06/03
Raw View
In article <lx5MQlAV1SN5Ewy9@robinton.demon.co.uk>,
  Francis Glassborow <francisG@robinton.demon.co.uk> wrote:
> In article <8h195o$sdd$1@nnrp1.deja.com>, sirwillard@my-deja.com
writes
> >If a compiler would do this we'd have a lot of warnings, since there
> >are several areas in which the standard classes do this.
>
> But public inheritance from a class with a non-virtual public dtor
is, I
> believe, an error.  I think that such cases in the Standard C++
Library
> should be reported as defects.

*laughs*  Well, I called this a "design mistake" and you've just
elevated it to a "defect report".  Most people in this thread so far
don't believe it's even a "design mistake", so the likelyhood of
pushing this through as a "defect report" seems to be nil.  Remember,
there are several places in the standard that this is done.  I'll try
to highlight a few of them, but I'm likely to miss some.

unary_function -> unary_negate, binder1st, binder2nd,
pointer_to_unary_function, mem_fun_t, mem_fun_ref_t, const_mem_fun_t,
const_mem_fun_ref_t

binary_function -> binary_negate, pointer_to_binary_function,
mem_fun1_t, mem_fun1_ref_t, const_mem_fun1_t, const_mem_fun1_ref_t,
map::value_compare, multimap::value_compare

iterator -> raw_storage_iterator, reverse_iterator,
back_insert_iterator, front_insert_iterator, insert_iterator,
istream_iterator, ostream_iterator, istreambuf_iterator,
ostreambuf_iterator

input_iterator_tag -> forward_iterator_tag

forward_iterator_tag -> bidirectional_iterator_tag

bidirectional_iterator_tag -> random_access_iterator_tag

codecvt_base -> code_cvt

time_base -> time_get

money_base -> money_punct

message_base -> messages

The reasoning behind this makes some sense.  All of these "base" types
are used for type laundering.  They have no functionality themselves.
So the likelyhood that someone would attempt to use these
polymorphically is pretty slim.  In fact, any attempts to do so will
result in failure.  It's still a violation of the "is a" rules, but the
room for abuse is pretty slim.  I'm wondering if other designs could
have been used as effectively, to remove all room for abuse, but
because the room for abuse is so low I wouldn't necessarily be in favor
of a defect report at this time.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/06/03
Raw View
In article <Uz08gMAIpJN5EwEs@two-sdg.demon.co.uk>,
  Kevlin Henney <kevlin@curbralan.com> wrote:
> In article <3NWIvyBwy+L5EwiW@robinton.demon.co.uk>, Francis Glassborow
> <francis@robinton.demon.co.uk> writes
> >As of today we do not have an efficient way of preventing a class
from
> >being used as a base class, but we do have a way of expressing that
> >intent: we simply do not provide a virtual dtor.
>
> It requires more than that, given that template classes such as
> std::iterator and std::unary_function do not have virtual dtors.

This is circular logic.  If, in fact, std::iterator and
std::unary_function could have been designed or used in such a way as
to not require public inheritance with out a virtual dtor then what Mr.
Glassborow stated could be taken as the simple truth.  In fact, this
*IS* the case, since a virtual dtor could have been added to either of
these (yes, I know why we don't want to do this), or other ways of
using them could have been devised (such as the nested approach I gave).

> >By hindsight it might
> >have been nice if the language had forbidden inheritance form classes
> >whose dotrs were non-virtual, but it is too late now.
>
> Hmm, hindsight suggests that it was fortunate that this was not the
> case.

Hindsight doesn't suggest any such thing.  Again, circular thinking is
being applied.  If the design of the standard libraries could be
modified to eliminate this useage then we can't say if it was fortunate
or unfortunate.  I believe the design could be modified in this case.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/06/03
Raw View
In article <gdc0h8.i11.ln@lnwie.intra.doosys.com>,
  Wil Evers <bouncer@dev.null> wrote:
> In article <1eb9mli.1j27ns213qd8psN@slip139-92-12-
86.hm.de.prserv.net>,
> Joerg Barfurth wrote:
>
> > <sirwillard@my-deja.com> wrote:

> > But surely logical_not<T> *is a* unary function object taking a T
> > and returning a bool. It also has an argument type it accepts and
> > a result type it returns.
>
> Right.  The use of public inheritance here is correct.

Slightly debatable.  There's no reason why inheritance need be used at
all here.  No true reason why we need an *is a* relationship.  However,
after the insight you give later, I'd tend to agree that it probably
should be!

>  One could even
> imagine a few examples where being able to convert a pointer to a
> logical_not<> to a pointer to a unary_function<> would be useful, for
> example, when selecting a particular overload without actually using
the
> pointer's value.

Such as what's done with iterator tags.  This could still be
accomplished with nested type laundering, but again, with the insight
you give later I'd tend to agree with you again.

> > > [Willard's alternative (template instead of inheritance) snipped]

Slight nit... my alternative was based on "nesting instead of
inheritance", not "template instead of inheritance".

> > > What's the thoughts of others?  Did the standard make a mistake
> > > in the design here?
> >
> > Just my $0.02. IMHO no.
>
> Yes, there is a mistake: the base class's interface contains an
implicitly
> declared public operation (the destructor) that leads to undefined
> behaviour when invoked from a delete-expression.  Declaring the
> destructor to be virtual is intended to make that operation work as
> intended; if that is too expensive, it can simply be disabled by
> saying it is protected (and giving it an empty inline implementation
to
> elminate any overhead).

This is the insight I was talking about!  I should have thought of this
one myself.  Simply change unary_function to read like this:

template <class Arg, class Result>
struct unary_function {
   typedef Arg argument_type;
   typedef Result result_type;
protected:
   ~unary_function() { }
};

This will prevent *ALL* attempts to use a unary_function pointer
polymorphically, yet still allow us to use the class in public
inheritance for type laundering!  This is a *MUCH* better alternative
to the nested solution I posted.  In addition, it wouldn't effect any
code at all if the definition were to be changed to this.  For this
reason, I can even see this as a valid possibility to write up as a
defect report!  Any further thoughts by anyone?  Should we issue a
defect report on this one?

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/06/04
Raw View
In article <8h8h1k$ni1$1@nnrp1.deja.com>, sirwillard@my-deja.com writes
>The reasoning behind this makes some sense.  All of these "base" types
>are used for type laundering.  They have no functionality themselves.
>So the likelyhood that someone would attempt to use these
>polymorphically is pretty slim.

The question, IMHO, is whether you should be able to form references or
pointers to these 'special' classes. Or put it another way, can
freestanding instances of these classes have any use? What adverse
effects would there be by defining a protected dtor for each of them?


Francis Glassborow      Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Kevlin Henney <kevlin@curbralan.com>
Date: 2000/06/01
Raw View
In article <sj8kuiin5pj164@news.supernews.com>, Andrei Alexandrescu
<andrewalex@hotmail.com> writes
>Kevlin Henney <kevlin@curbralan.com> wrote in message
>news:11qUBnB3S$M5EwIl@two-sdg.demon.co.uk...
>> Well, the rule I have recommended for years (and have begun to see
>> elsewhere) is more specific: Make destructors virtual in classes
>with a
>> public destructor and having at least one virtual function.
>
>What about classes with a non-public operator delete? :oP

Depends how you plan to use it, but it doesn't change the rec.
____________________________________________________________

  Kevlin Henney                   phone:  +44 117 942 2990
  Curbralan Ltd                   mobile: +44 7801 073 508
  kevlin@curbralan.com            fax:    +44 870 052 2289
____________________________________________________________

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Kevlin Henney <kevlin@curbralan.com>
Date: 2000/06/01
Raw View
In article <3NWIvyBwy+L5EwiW@robinton.demon.co.uk>, Francis Glassborow
<francis@robinton.demon.co.uk> writes
>As of today we do not have an efficient way of preventing a class from
>being used as a base class, but we do have a way of expressing that
>intent: we simply do not provide a virtual dtor.

It requires more than that, given that template classes such as
std::iterator and std::unary_function do not have virtual dtors.

>By hindsight it might
>have been nice if the language had forbidden inheritance form classes
>whose dotrs were non-virtual, but it is too late now.

Hmm, hindsight suggests that it was fortunate that this was not the
case.

>However, it would
>be a useful debug facility if compilers issued a warning in such cases
>(as many do for other suspect cases such as assignments in
>conditionals.)

It would add one can already get from compiling third party libraries on
high warning levels :-(
____________________________________________________________

  Kevlin Henney                   phone:  +44 117 942 2990
  Curbralan Ltd                   mobile: +44 7801 073 508
  kevlin@curbralan.com            fax:    +44 870 052 2289
____________________________________________________________

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: smeyers@aristeia.com (Scott Meyers)
Date: 2000/06/01
Raw View
On Wed, 31 May 2000 09:58:26 CST, Andrei Alexandrescu wrote:
> What about classes with a non-public operator delete? :oP

In general, such classes may not be dynamically allocated at all, because
new expressions have to have access to operator delete.  (Similarly, new
expressions for arrays must have access to both base class destructors and
operator delete[].)

Somebody wrote and asked me to comment on this thread.  I think the issues
have been well hashed out, but my inclination is to agree with William
E. Kempf on pretty much every point.  I think his nested typedef approach
would have been a superior design, because it would avoid all the "what
does public inheritance really mean here?" questions.  At the same time,
the standard is the standard, and we're stuck with it.  As you'll see in
another thread I posted, I'm more interested in what to pass to
unary_ or binary_function than in whether they should be base classes...

Scott

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/06/02
Raw View
In article <MPG.139efb7695b268999896f6@news.supernews.com>,
  smeyers@aristeia.com (Scott Meyers) wrote:
> On Wed, 31 May 2000 09:58:26 CST, Andrei Alexandrescu wrote:
> > What about classes with a non-public operator delete? :oP
>
> In general, such classes may not be dynamically allocated at all,
because
> new expressions have to have access to operator delete.  (Similarly,
new
> expressions for arrays must have access to both base class
destructors and
> operator delete[].)
>
> Somebody wrote and asked me to comment on this thread.

I'll own up to that.  It was myself that asked.

>  I think the issues
> have been well hashed out, but my inclination is to agree with William
> E. Kempf on pretty much every point.  I think his nested typedef
approach
> would have been a superior design, because it would avoid all
the "what
> does public inheritance really mean here?" questions.  At the same
time,
> the standard is the standard, and we're stuck with it.

Yes and no.  I have no plan to propose this as a defect report on the
standard.  First of all, we seem to have too many people with
dissenting opinions on this one to make it likely to even be
considered.  Secondly, I'm really not to concerned with this particular
issue in the standard.  The predicate classes are a tad too limiting
for full use as generalized functors (unary and binary functors barely
scratch the surface of what's needed).  I asked this question for
different reasons, namely:

1.  If this wasn't even considered in the first go around with the
standard, I hoped that the discussion here might influence the next
revision of the standard.  I fully expect the next standard to address
generalized functors to a much higher degree than what we have now.

2.  I wanted to verify my own thoughts on this for a seperate library
of functors under development.  I didn't fully trust my own opinion on
this subject and wanted to here pros and cons from others.
(Unfortunately, I don't think I got very many responses on the cons
side that sound legitimate to me, and I got basically zero on the pro
side, so I'm still left with just my own gut instincts here.)

I was also wondering if there might not have been some other
alternative from the three mentioned for this type laundering (public
derivation, nested class, or simply hard coding the typedefs in each
class).

>  As you'll see in
> another thread I posted, I'm more interested in what to pass to
> unary_ or binary_function than in whether they should be base
classes...

I'll have to check out this other thread.  At the present I'm very
interested in any and all discussions about functors.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Kevlin Henney <kevlin@curbralan.com>
Date: 2000/05/31
Raw View
In article <1eb9mli.1j27ns213qd8psN@slip139-92-12-86.hm.de.prserv.net>,
Joerg Barfurth <joerg.barfurth@attglobal.net> writes
><sirwillard@my-deja.com> wrote:
>
>> Most of you are aware of "Item 14:  Make destructors virtual in base
>> classes" in Scott Meyers' book "Effective C++".  I'm even willing to
>> bet that most of you completely agree with this item, since I've seen
>> many people argue this point in the past.
>
>Well, I believe that one should "Make destructors virtual in classes
>having at least one virtual function".

Well, the rule I have recommended for years (and have begun to see
elsewhere) is more specific: Make destructors virtual in classes with a
public destructor and having at least one virtual function. There is an
implicit assumption that destructors must be public, which is not the
case for a number of classes, eg mix-in classes for which public
deletability is not one of the features on offer.
____________________________________________________________

  Kevlin Henney                   phone:  +44 117 942 2990
  Curbralan Ltd                   mobile: +44 7801 073 508
  kevlin@curbralan.com            fax:    +44 870 052 2289
____________________________________________________________

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Pete Becker <petebecker@acm.org>
Date: 2000/05/31
Raw View
sirwillard@my-deja.com wrote:
>
> Unfortunately, it seems that most people responding so far want to take
> offense at the generalized rule, providing alternative wordings and
> constraints, instead of taking the idea at face value and evaluating
> whether or not there may be the potential for abuse in this specific
> instance.

I haven't seen anyone take offense. What I have seen is people who
disagree with the premise. Given that I don not accept the premise, I
see no point in reasoning about its implications.

--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contibuting Editor, C/C++ Users Journal (http://www.cuj.com)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Wayne Witzke <wwitzke@immersa.uky.edu>
Date: 2000/05/31
Raw View
sirwillard@my-deja.com wrote:
> Consider:
>
> std::unary_function<char, bool>* uf = new std::logical_not<char>();
> delete uf;
>
> Admittedly this example is extremely contrived.  To be honest, I can't
> think of any legitimate use of this idiom.  Since there doesn't seem to
> be any legitimate use, maybe this case can be considered the "exception
> that proves the rule".

Actually, I can think of a number of circumstances when functors might be
allocated and deallocated dynamically.  Your particular example might be a bit
contrived, but there might be good reason to dynamically allocate functors.  For
instance, a series of binary functors might be allocated with their base
pointers stored in a list<> or vector<> to be applied sequentually (or whatever)
on a series of objects.  A scheme similar to this might be used in a parser
where you forsee dynamic changes to the actions, or to resequence a series of
genetic strands in a simulation where the resequencing functions performed
change depending on previous resequencing, or perhaps in an attempt to
aggressively optimize hash tables where the optimization requires a number of
complicated calculations on the objects being hashed.

In any event, the inability to imagine a usefulness for such a feature does not
mean that the feature is uesless.  I think your example serves its purpose quite
well in demonstrating the possible design flaw.

> However, before excepting that I felt that I
> must fully understand why public inheritance from a non-polymorphic
> base was being used here.  So, carefully reading through the standard,
> all I could come up with was this quote:
>
> "The following classes are provided to simplify the typedefs of the
> argument and result types"
>
> Well, using this idiom solely to "simplify" other class definitions is
> hardly an excuse for this practice.

I would tend to agree with you in principle here.  Also, it is quite conceivable
that derived functors might be very complex, and might require a large amount of
dynamic memory handling and a number of other unsavory processes.  Why provide
base classes, implying that their use is good programming style (perhaps through
code reuse), when those base classes are not specified in a way that allows them
to be useful in general?

On the other hand, providing these base class functors so that they really _are_
useful in general might create a good deal of overhead when they are used for
the simpler applications.  I mean, when they are used simply to save a few
keystrokes.

Of course, one could simply define new base class functors specifically suited
to the application, but how tempting it is to use these readily available and
specification approved base classes.


> Since std::unary_function has no
> functionality all that we get is a bit less typing in other classes to
> define a few types.  These base classes could have been eliminated, and
> the typedefs hand coded in the handful of function object definitions
> that exist in the standard and we would have had the same result.  So,
> at best, this use shows laziness instead of good design.  However, even
> this barely scratches the surface of what I consider to be a major
> mistake.  We could have retained the "lazy coding practice" while still
> avoiding this design mistake!  Consider:
>
> // Hypothetical alternative definition
>
> template <class T> struct logical_not {
> typedef unary_function<T,bool> function_type;
> bool operator()(const T& x) const;
> };
>
> This gives us our automatic typedefs which can be used in adapter's
> such as bind1st, while using a design that doesn't violate the "is-a"
> rules.

This does not give exactly the same result.  In order to pull the types out of
logical_not you end up with a few extra keystrokes (qualifying with
function_type:: every time).  It is an alternative, and it would seem to be a
better design, if safety is more important to you then usability.  I'm not sure
that most programmers would sacrifice the convenience for the safety that this
alternative provides, since that safety only comes into play when you try to use
these functors in a way in which they probably were not intended to be used.
I've also seen far too many examples where programmers have sacrificed the
beauty of proper design for efficiency and code reduction to believe that any
argument of proper design would count as justification in their minds for this
alternative.


> As another benefit, it would eliminate the potential for code
> bloat with compilers that don't handle the "empty base" optimization
> very well.

Isn't the code bloat issue something that should be handled by the supplier of
the compiler?  I am not sure that the standard template library was meant to
accommodate poorly designed compilers...  Or am I missing something?


> What's the thoughts of others?  Did the standard make a mistake in the
> design here?  Since I'm a very strong believer in this design rule I
> often find myself explaining to others why this is a mistake.
> Explaining it to them is going to be even harder to do if they can
> point out cases such as this one.  So I'd really like to hear the
> thoughts of others, especially any that may have been involved in the
> design or acceptance of this portion of the standard.

I think it was fairly ugly to use public inheritance of a non-polymorphic
(non-virtual) base class to simplify basic functor member type definitions.  It
is a mechanism that seems to me to imply several capabilities that simply are
not true (this is clear as soon as you examine the base class definition).
However much I might dislike it, however, I don't really think that this is a
design flaw.  The functor hierarchy is simply non-polymorphic and has no support
for dynamic allocation and deallocation, and as long as programmers recognize
this there should not be a problem.  I think that any programmer who is ready to
define his or her own functor templates or who is struggling with a problem that
can only be solved using polymorphic functors probably already understands this
limitation.  If they need to have functors that have polymorphic behavior, or
that must be allocated and deallocated dynamically, they will probably simply
provide their own versions of the base classes that support polymorphic
behavior.

I seem to recall that there are several other places in the standard template
library where a similar mechanism was used to simplify type definitions in
similar classes.  The iterator base class is the first example to pop into my
head.  Do you believe that these present the same design problem as the functor
base classes?

Wayne

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Pete Becker <petebecker@acm.org>
Date: 2000/05/31
Raw View
sirwillard@my-deja.com wrote:
>
> In article <392EAD3E.131794E9@acm.org>,
>   Pete Becker <petebecker@acm.org> wrote:
> > sirwillard@my-deja.com wrote:
> > >
> > > Most of you are aware of "Item 14:  Make destructors virtual in base
> > > classes" in Scott Meyers' book "Effective C++".  I'm even willing to
> > > bet that most of you completely agree with this item, since I've
> seen
> > > many people argue this point in the past.
> >
> > I've said many times that this statement is too strong. The true rule
> is
> > to make destructors virtual in classes that are intended for use as
> > polymorphic bases.
>
> Public derivation implies polymorphism.  There's no way to use public
> derivation and yet prevent the use of polymorphic behavior, since
> anyone can delete a base pointer to your derived object.

Sure there is. The documentation can say "don't do that." You don't have
to wrap your code in a suit of armor to make it useful. In fact, armor
often gets in the way, because of its bulk and weight. The philosophy of
C and C++ has always been to allow more flexibility if the loss of
safety isn't too great. I have never yet seen a complaint based on
actual code failing to run correctly because of the non-polymorphic
behavior of unary_function, so there doesn't seem to be a significant
problem here.

> > Yes, we get less typing of duplicate information. Having to type the
> > same thing multiple times leads to errors. So, while it's literally "a
> > bit less typing," it is also a technique for reducing error rates.
>
> This still doesn't hold water.  First of all, I showed a method that
> produces the same result, uses the same amount of typing (give or take
> a few keystrokes), yet doesn't violate the "is-a" rules of public
> derivation.

Agreed: your approach satisfies the common guideline that public
inheritance represents "is-a," and the intended usage of unary_function
does not. That's why it's a guideline and not a rule and not a part of
the language definition.

> So even attempting to claim that you've saved us from
> errors is a red-herring argument.

No, it's a statement of the reason that that class exists. Your approach
is more verbose:

// standard approach:
logical_not::Arg

// alternative approach:
logical_not::function_type::Arg

This alternative approach also constrains users to this exact approach.
Providing a base class, on the other hand, is a convenience, but does
not impose any requirement on users. The requirement is only that the
class provide nested type names Arg and Result. If a better way exists
for providing Arg and Result in the code that you're writing you are
free to use it.

> Even if my solution weren't
> available, though, the potential for errors could still have been
> eliminated through the use of a macro generator, such as an M4 macro
> file.

I don't see how. What can the standard say about this macro file, so
that users can rely on its presence and utility?

> In any event, this is in code that would be (presumably)
> thoroughly tested through a validation suite (such as the ones you
> sell) to insure a typing error didn't occur.

It's not just for use in the standard library. User-defined unary
predicates can also use this class, and a validation suite won't help
there.

--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contibuting Editor, C/C++ Users Journal (http://www.cuj.com)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/05/31
Raw View
In article <8gml1p$m47$1@nnrp1.deja.com>, sirwillard@my-deja.com writes
>Public derivation implies polymorphism.  There's no way to use public
>derivation and yet prevent the use of polymorphic behavior, since
>anyone can delete a base pointer to your derived object.  In a closed
>implementation I *might* be persuaded that such uses can be made
>safely, and if doing so would simplify the design, then I'd agree to
>its use.  Neither of these criteria are met in this case, though, since
>the design isn't a closed implementation, being the foundation of all
>future C++ work, and it doesn't simplify the design any more than the
>alternatives.

As of today we do not have an efficient way of preventing a class from
being used as a base class, but we do have a way of expressing that
intent: we simply do not provide a virtual dtor. By hindsight it might
have been nice if the language had forbidden inheritance form classes
whose dotrs were non-virtual, but it is too late now.  However, it would
be a useful debug facility if compilers issued a warning in such cases
(as many do for other suspect cases such as assignments in
conditionals.)


Providing virtual dtors is not entirely cost free, indeed in classes
with small amounts of data it can be very expensive.

Francis Glassborow      Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Gillmer J. Derge" <gderge1@nycap.rr.com>
Date: 2000/05/31
Raw View
sirwillard@my-deja.com wrote:

> This still doesn't hold water.  First of all, I showed a method that
> produces the same result, uses the same amount of typing (give or take
> a few keystrokes), yet doesn't violate the "is-a" rules of public
> derivation.  So even attempting to claim that you've saved us from

What exactly is this "is-a" rule that's being violated?  My understanding was
that public inheritance of class B from class A should imply that a B "is a"
A (ex. a Circle "is a" Shape).  Are you saying that logical not is /not/ a
unary function?  I would argue that it is.

-----
Gillmer J. Derge

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/31
Raw View
In article <392FE724.A13591E0@acm.org>,
  Pete Becker <petebecker@acm.org> wrote:
> sirwillard@my-deja.com wrote:
> >
> > In article <392EAD3E.131794E9@acm.org>,
> >   Pete Becker <petebecker@acm.org> wrote:
> > > sirwillard@my-deja.com wrote:
> > > >
> > > > Most of you are aware of "Item 14:  Make destructors virtual in
base
> > > > classes" in Scott Meyers' book "Effective C++".  I'm even
willing to
> > > > bet that most of you completely agree with this item, since I've
> > seen
> > > > many people argue this point in the past.
> > >
> > > I've said many times that this statement is too strong. The true
rule
> > is
> > > to make destructors virtual in classes that are intended for use
as
> > > polymorphic bases.
> >
> > Public derivation implies polymorphism.  There's no way to use
public
> > derivation and yet prevent the use of polymorphic behavior, since
> > anyone can delete a base pointer to your derived object.
>
> Sure there is. The documentation can say "don't do that."

I certainly hope you were being facetious here.

> You don't have
> to wrap your code in a suit of armor to make it useful. In fact, armor
> often gets in the way, because of its bulk and weight. The philosophy
of
> C and C++ has always been to allow more flexibility if the loss of
> safety isn't too great.

I'm fully aware of this.  However, you make it sound as if the
alternative approach is so burdensome as to be deemed "bad".  In truth,
it adds very little in the way of "loss of flexibility" and is hardly
bulky.

> I have never yet seen a complaint based on
> actual code failing to run correctly because of the non-polymorphic
> behavior of unary_function, so there doesn't seem to be a significant
> problem here.

Hardly logical thinking.

> > > Yes, we get less typing of duplicate information. Having to type
the
> > > same thing multiple times leads to errors. So, while it's
literally "a
> > > bit less typing," it is also a technique for reducing error rates.
> >
> > This still doesn't hold water.  First of all, I showed a method that
> > produces the same result, uses the same amount of typing (give or
take
> > a few keystrokes), yet doesn't violate the "is-a" rules of public
> > derivation.
>
> Agreed: your approach satisfies the common guideline that public
> inheritance represents "is-a," and the intended usage of
unary_function
> does not. That's why it's a guideline and not a rule and not a part of
> the language definition.

Uhmm... I do know what you're trying to say here, but taken at face
value what you just said is a circular and pointless argument.  The use
of public inheritance that violates "is-a" requirements can hardly be
offered as proof that this is a "guideline" and not a "rule", nor offer
any evidence as to why it's not part of the language definition.

> > So even attempting to claim that you've saved us from
> > errors is a red-herring argument.
>
> No, it's a statement of the reason that that class exists.

Hardly.  What you say next is likely why the design was made the way it
was, but this doesn't follow from what you've already said.

> Your approach
> is more verbose:
>
> // standard approach:
> logical_not::Arg
>
> // alternative approach:
> logical_not::function_type::Arg

Verbosity hardly seems to have been considered in other cases in the
standard.  It may well be a consideration here, but was this really
even considered?  Does the verbosity really mean much in the long run,
if it tightens up the design?

> This alternative approach also constrains users to this exact
approach.
> Providing a base class, on the other hand, is a convenience, but does
> not impose any requirement on users. The requirement is only that the
> class provide nested type names Arg and Result. If a better way exists
> for providing Arg and Result in the code that you're writing you are
> free to use it.

The alternative approach has the exact same constraints, with the minor
addition that the types be nested.  How other classes define this
interface is still left wide open.  Does the extra level actually
hinder the programmer in any way?

> > Even if my solution weren't
> > available, though, the potential for errors could still have been
> > eliminated through the use of a macro generator, such as an M4 macro
> > file.
>
> I don't see how. What can the standard say about this macro file, so
> that users can rely on its presence and utility?

I think you missed the point here.  I never said the macro should be
released as a part of the library, nor should the standard address any
such aid in developing the library.  I assume you made this mistake
because of your next statement about usre defined unary predicates.

> > In any event, this is in code that would be (presumably)
> > thoroughly tested through a validation suite (such as the ones you
> > sell) to insure a typing error didn't occur.
>
> It's not just for use in the standard library. User-defined unary
> predicates can also use this class, and a validation suite won't help
> there.

True enough.  This would be an argument in favor of using the nested
class I proposed.  However, in practice I don't see this is a large
issue either.  This is really a convenience class, not one required by
any portion of the library.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/31
Raw View
In article <392FF323.3BB9F5@nycap.rr.com>,
  "Gillmer J. Derge" <gderge1@nycap.rr.com> wrote:
> sirwillard@my-deja.com wrote:
>
> > This still doesn't hold water.  First of all, I showed a method that
> > produces the same result, uses the same amount of typing (give or
take
> > a few keystrokes), yet doesn't violate the "is-a" rules of public
> > derivation.  So even attempting to claim that you've saved us from
>
> What exactly is this "is-a" rule that's being violated?  My
understanding was
> that public inheritance of class B from class A should imply that a
B "is a"
> A (ex. a Circle "is a" Shape).

That's an simplistic understanding.  The "is-a" rule actually states
that a derived class pointer can be used any where that a base class
pointer can be.  In the absence of a virtual dtor, this is simply not
true.

>  Are you saying that logical not is /not/ a
> unary function?  I would argue that it is.

Conceptually, yes, it is.  But programmatically, with out the virtual
dtor, it is not.  Add the virtual dtor and then yes, I'd agree, it
meets the "is-a" requirements.  However, I don't think anyone wants a
virtual dtor added to here.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: 2000/05/31
Raw View
Kevlin Henney <kevlin@curbralan.com> wrote in message
news:11qUBnB3S$M5EwIl@two-sdg.demon.co.uk...
> Well, the rule I have recommended for years (and have begun to see
> elsewhere) is more specific: Make destructors virtual in classes
with a
> public destructor and having at least one virtual function.

What about classes with a non-public operator delete? :oP


Andrei


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: 2000/05/31
Raw View
<sirwillard@my-deja.com> wrote in message
news:8h1b2r$tsj$1@nnrp1.deja.com...
>   "Gillmer J. Derge" <gderge1@nycap.rr.com> wrote:
> >  Are you saying that logical not is /not/ a
> > unary function?  I would argue that it is.
>
> Conceptually, yes, it is.  But programmatically, with out the
virtual
> dtor, it is not.  Add the virtual dtor and then yes, I'd agree, it
> meets the "is-a" requirements.  However, I don't think anyone wants
a
> virtual dtor added to here.

I'll bite: I think this has to do with a "bug" in C++. Public
inheritance is overloaded. It means two things at the same time:
runtime "is-a" and static "is-a". There are interesting uses with both
cases. Unfortunately, uses for the latter are herretical with regard
to the former.


Andrei


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/31
Raw View
In article <39341C03.2655F477@immersa.uky.edu>,
  wwitzke@immersa.uky.edu wrote:
> sirwillard@my-deja.com wrote:
> > Consider:
> >
> > std::unary_function<char, bool>* uf = new std::logical_not<char>();
> > delete uf;
> >
> > Admittedly this example is extremely contrived.  To be honest, I
can't
> > think of any legitimate use of this idiom.  Since there doesn't
seem to
> > be any legitimate use, maybe this case can be considered
the "exception
> > that proves the rule".
>
> Actually, I can think of a number of circumstances when functors
might be
> allocated and deallocated dynamically.  Your particular example might
be a bit
> contrived, but there might be good reason to dynamically allocate
functors.  For
> instance, a series of binary functors might be allocated with their
base
> pointers stored in a list<> or vector<> to be applied sequentually
(or whatever)
> on a series of objects.  A scheme similar to this might be used in a
parser
> where you forsee dynamic changes to the actions, or to resequence a
series of
> genetic strands in a simulation where the resequencing functions
performed
> change depending on previous resequencing, or perhaps in an attempt to
> aggressively optimize hash tables where the optimization requires a
number of
> complicated calculations on the objects being hashed.

This isn't really possible.  std::unary_function provides no
functionality at all, let alone any polymorphic functionality you could
call through a base pointer.  You can't even "cheat" by using RTTI to
down-cast, since RTTI will only return the static type info with out a
virtual function present.  This is why I said it was contrived and not
likely to be used in real code.  However, the fact that you jumped to
this idea shows why I think we still have a problem with the public
derivation design here.

> In any event, the inability to imagine a usefulness for such a
feature does not
> mean that the feature is uesless.  I think your example serves its
purpose quite
> well in demonstrating the possible design flaw.

However, the lack of any real world example limits its usefullness as a
gauge of how serious the flaw is.

> > However, before excepting that I felt that I
> > must fully understand why public inheritance from a non-polymorphic
> > base was being used here.  So, carefully reading through the
standard,
> > all I could come up with was this quote:
> >
> > "The following classes are provided to simplify the typedefs of the
> > argument and result types"
> >
> > Well, using this idiom solely to "simplify" other class definitions
is
> > hardly an excuse for this practice.
>
> I would tend to agree with you in principle here.  Also, it is quite
conceivable
> that derived functors might be very complex, and might require a
large amount of
> dynamic memory handling and a number of other unsavory processes.
Why provide
> base classes, implying that their use is good programming style
(perhaps through
> code reuse), when those base classes are not specified in a way that
allows them
> to be useful in general?

This is very much in question in my mind as well.  Mr. Pecker points
out that the use is in preventing possible errors when duplicating this
code in new functor types.  This has some validity, but I still think
my approach solves the same issue with out the possibly bad side
effects.

> On the other hand, providing these base class functors so that they
really _are_
> useful in general might create a good deal of overhead when they are
used for
> the simpler applications.  I mean, when they are used simply to save
a few
> keystrokes.
>
> Of course, one could simply define new base class functors
specifically suited
> to the application, but how tempting it is to use these readily
available and
> specification approved base classes.
>
> > Since std::unary_function has no
> > functionality all that we get is a bit less typing in other classes
to
> > define a few types.  These base classes could have been eliminated,
and
> > the typedefs hand coded in the handful of function object
definitions
> > that exist in the standard and we would have had the same result.
So,
> > at best, this use shows laziness instead of good design.  However,
even
> > this barely scratches the surface of what I consider to be a major
> > mistake.  We could have retained the "lazy coding practice" while
still
> > avoiding this design mistake!  Consider:
> >
> > // Hypothetical alternative definition
> >
> > template <class T> struct logical_not {
> > typedef unary_function<T,bool> function_type;
> > bool operator()(const T& x) const;
> > };
> >
> > This gives us our automatic typedefs which can be used in adapter's
> > such as bind1st, while using a design that doesn't violate the "is-
a"
> > rules.
>
> This does not give exactly the same result.  In order to pull the
types out of
> logical_not you end up with a few extra keystrokes (qualifying with
> function_type:: every time).  It is an alternative, and it would seem
to be a
> better design, if safety is more important to you then usability.
I'm not sure
> that most programmers would sacrifice the convenience for the safety
that this
> alternative provides, since that safety only comes into play when you
try to use
> these functors in a way in which they probably were not intended to
be used.
> I've also seen far too many examples where programmers have
sacrificed the
> beauty of proper design for efficiency and code reduction to believe
that any
> argument of proper design would count as justification in their minds
for this
> alternative.

I don't see the extra level here as being much of a burden on
programmers.  Unless they are writting new functors they probably won't
use any of this to begin with.  Even when they do need to use the
types, the few extra keystrokes are not that big of a deal.

> > As another benefit, it would eliminate the potential for code
> > bloat with compilers that don't handle the "empty base" optimization
> > very well.
>
> Isn't the code bloat issue something that should be handled by the
supplier of
> the compiler?  I am not sure that the standard template library was
meant to
> accommodate poorly designed compilers...  Or am I missing something?

The empty base optimization isn't one that can always be applied in the
first place.  True, my motivation here is to help reduce bloat on
compilers that don't handle this well if at all any way, but that's
just a side benefit.  I see nothing wrong with paying proper attention
to this detail in the design even if the standard allows for the empty
base optimization.

> > What's the thoughts of others?  Did the standard make a mistake in
the
> > design here?  Since I'm a very strong believer in this design rule I
> > often find myself explaining to others why this is a mistake.
> > Explaining it to them is going to be even harder to do if they can
> > point out cases such as this one.  So I'd really like to hear the
> > thoughts of others, especially any that may have been involved in
the
> > design or acceptance of this portion of the standard.
>
> I think it was fairly ugly to use public inheritance of a non-
polymorphic
> (non-virtual) base class to simplify basic functor member type
definitions.  It
> is a mechanism that seems to me to imply several capabilities that
simply are
> not true (this is clear as soon as you examine the base class
definition).
> However much I might dislike it, however, I don't really think that
this is a
> design flaw.  The functor hierarchy is simply non-polymorphic and has
no support
> for dynamic allocation and deallocation, and as long as programmers
recognize
> this there should not be a problem.  I think that any programmer who
is ready to
> define his or her own functor templates or who is struggling with a
problem that
> can only be solved using polymorphic functors probably already
understands this
> limitation.  If they need to have functors that have polymorphic
behavior, or
> that must be allocated and deallocated dynamically, they will
probably simply
> provide their own versions of the base classes that support
polymorphic
> behavior.
>
> I seem to recall that there are several other places in the standard
template
> library where a similar mechanism was used to simplify type
definitions in
> similar classes.  The iterator base class is the first example to pop
into my
> head.  Do you believe that these present the same design problem as
the functor
> base classes?

IMO, yes, unless you can convince me that there is not suitable
alternative.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Wil Evers <bouncer@dev.null>
Date: 2000/05/31
Raw View
In article <1eb9mli.1j27ns213qd8psN@slip139-92-12-86.hm.de.prserv.net>,
Joerg Barfurth wrote:

> <sirwillard@my-deja.com> wrote:
>
> > Most of you are aware of "Item 14:  Make destructors virtual in
> > base classes" in Scott Meyers' book "Effective C++".  I'm even
> > willing to bet that most of you completely agree with this item,
> > since I've seen many people argue this point in the past.
>
> Well, I believe that one should "Make destructors virtual in
> classes having at least one virtual function". IOW: Do this in
> classes  intended to be used as base classes with dynamic
> polymorphism.

I'm not sure I agree.  We all know the reason for Scott's rule: if the
base class's destructor is non-virtual, deleting a derived object identified
by a pointer to its base class results in undefined behaviour.

Frankly, I don't see what the presence of other virtual functions in the
base class has to do with that; I've seen quite a few cases where an
intended base class has no virtual functions (other than the destructor).

[snip]

> > Consider:
> >
> > std::unary_function<char, bool>* uf =
> >   new std::logical_not<char>();
> > delete uf;
>
> [snip]
>
> But surely logical_not<T> *is a* unary function object taking a T
> and returning a bool. It also has an argument type it accepts and
> a result type it returns.

Right.  The use of public inheritance here is correct.  One could even
imagine a few examples where being able to convert a pointer to a
logical_not<> to a pointer to a unary_function<> would be useful, for
example, when selecting a particular overload without actually using the
pointer's value.

> > [Willard's alternative (template instead of inheritance) snipped]
> >
> > What's the thoughts of others?  Did the standard make a mistake
> > in the design here?
>
> Just my $0.02. IMHO no.

Yes, there is a mistake: the base class's interface contains an implicitly
declared public operation (the destructor) that leads to undefined
behaviour when invoked from a delete-expression.  Declaring the
destructor to be virtual is intended to make that operation work as
intended; if that is too expensive, it can simply be disabled by
saying it is protected (and giving it an empty inline implementation to
elminate any overhead).

- Wil

--
Wil Evers, DOOSYS IT Consultants, Maarssen, Holland
[Wil underscore Evers at doosys dot com]

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/31
Raw View
In article <3NWIvyBwy+L5EwiW@robinton.demon.co.uk>,
  Francis Glassborow <francisG@robinton.demon.co.uk> wrote:
> In article <8gml1p$m47$1@nnrp1.deja.com>, sirwillard@my-deja.com
writes
> >Public derivation implies polymorphism.  There's no way to use public
> >derivation and yet prevent the use of polymorphic behavior, since
> >anyone can delete a base pointer to your derived object.  In a closed
> >implementation I *might* be persuaded that such uses can be made
> >safely, and if doing so would simplify the design, then I'd agree to
> >its use.  Neither of these criteria are met in this case, though,
since
> >the design isn't a closed implementation, being the foundation of all
> >future C++ work, and it doesn't simplify the design any more than the
> >alternatives.
>
> As of today we do not have an efficient way of preventing a class from
> being used as a base class, but we do have a way of expressing that
> intent: we simply do not provide a virtual dtor.

I'm well aware of this.  This is precisely why I started this thread.
By your own statement the std::unary_function class indicates that it
is not intended to be used as a base class since it does not provide a
virtual dtor... yet it's intent most certainly was to be a base class.
I'm not proposing that we add a virtual dtor to std::unary_function
because of the overhead this would create.  What I am doing is asking
if it was a mistake to have used public derivation to achieve what we
do with std::unary_function when there's a safer design we could have
used instead.

> By hindsight it might
> have been nice if the language had forbidden inheritance form classes
> whose dotrs were non-virtual, but it is too late now.  However, it
would
> be a useful debug facility if compilers issued a warning in such cases
> (as many do for other suspect cases such as assignments in
> conditionals.)

If a compiler would do this we'd have a lot of warnings, since there
are several areas in which the standard classes do this.

> Providing virtual dtors is not entirely cost free, indeed in classes
> with small amounts of data it can be very expensive.

Very true.  Again, I did not propose that we add a virtual dtor in this
case!

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/28
Raw View
In article <392EAD3E.131794E9@acm.org>,
  Pete Becker <petebecker@acm.org> wrote:
> sirwillard@my-deja.com wrote:
> >
> > Most of you are aware of "Item 14:  Make destructors virtual in base
> > classes" in Scott Meyers' book "Effective C++".  I'm even willing to
> > bet that most of you completely agree with this item, since I've
seen
> > many people argue this point in the past.
>
> I've said many times that this statement is too strong. The true rule
is
> to make destructors virtual in classes that are intended for use as
> polymorphic bases.

Public derivation implies polymorphism.  There's no way to use public
derivation and yet prevent the use of polymorphic behavior, since
anyone can delete a base pointer to your derived object.  In a closed
implementation I *might* be persuaded that such uses can be made
safely, and if doing so would simplify the design, then I'd agree to
its use.  Neither of these criteria are met in this case, though, since
the design isn't a closed implementation, being the foundation of all
future C++ work, and it doesn't simplify the design any more than the
alternatives.

> > Well, using this idiom solely to "simplify" other class definitions
is
> > hardly an excuse for this practice.  Since std::unary_function has
no
> > functionality all that we get is a bit less typing in other classes
to
> > define a few types.
>
> Yes, we get less typing of duplicate information. Having to type the
> same thing multiple times leads to errors. So, while it's literally "a
> bit less typing," it is also a technique for reducing error rates.

This still doesn't hold water.  First of all, I showed a method that
produces the same result, uses the same amount of typing (give or take
a few keystrokes), yet doesn't violate the "is-a" rules of public
derivation.  So even attempting to claim that you've saved us from
errors is a red-herring argument.  Even if my solution weren't
available, though, the potential for errors could still have been
eliminated through the use of a macro generator, such as an M4 macro
file.  In any event, this is in code that would be (presumably)
thoroughly tested through a validation suite (such as the ones you
sell) to insure a typing error didn't occur.

The base class simply isn't needed in this case.  I can see no
justification for using one here.  Even if you believe that there must
be an exception to the rule, I don't see how this should be considered
one.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: kanze@gabi-soft.de
Date: 2000/05/28
Raw View
"Bill Wade" <bill.wade@stoner.com> writes:

|>  Auto and static variables are dangerous with delete.
|>  Class data members are dangerous with delete.
|>  New[] and placement new are dangerous with delete.
|>  Literals are dangerous with delete.

|>  So, Bill's corollary to item 14:

|>  Don't use auto or static variables, classes with data members, new[],
|>  placement new or literals.

Sounds like Java:-).

--=20
James Kanze                               mailto:kanze@gabi-soft.de
Conseils en informatique orient=E9e objet/
                   Beratung in objektorientierter Datenverarbeitung
Ziegelh=FCttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: joerg.barfurth@attglobal.net (Joerg Barfurth)
Date: 2000/05/29
Raw View
<sirwillard@my-deja.com> wrote:

> Most of you are aware of "Item 14:  Make destructors virtual in base
> classes" in Scott Meyers' book "Effective C++".  I'm even willing to
> bet that most of you completely agree with this item, since I've seen
> many people argue this point in the past. =20

Well, I believe that one should "Make destructors virtual in classes
having at least one virtual function". IOW: Do this in classes intended
to be used as base classes with dynamic polymorphism. That latter form
is even more general, as it also includes classes to be used with RTTI
(see the 'Acyclic Visitor' pattern).

OTOH for some other situations that does not hold. In particular a
virtual destructor for classes intented to be used as base classes
(only) for use with compile-time polymorphism (i.e. as template
parameters)

> Consider:
>=20
> std::unary_function<char, bool>* uf =3D new std::logical_not<char>();
> delete uf;
>=20
> Admittedly this example is extremely contrived.  To be honest, I can't
> think of any legitimate use of this idiom.  Since there doesn't seem to
> be any legitimate use, maybe this case can be considered the "exception
> that proves the rule".  However, before excepting that I felt that I
> must fully understand why public inheritance from a non-polymorphic
> base was being used here.  So, carefully reading through the standard,
> all I could come up with was this quote:
>=20
> "The following classes are provided to simplify the typedefs of the
> argument and result types"

That is a valid argument.=20
In particular there is a valid abstraction 'unary_function taking a T
and returning a U'. It is much easier to read (and less error-prone to
write), if the declaration of a functor derives from a
std::unary_function<T,U> than to check manually whether the typedefs are
present, complete and correct.

As this class is only really useful as a base class of a template
parameter, my earlier condition holds true as well. You simply cannot
use a unary_function with dynamic polymorphism, as you can't
meaningfully use one by pointer or reference. This is because there are
no non-type members (except for the trivial compiler-generated ones).


> [...] However, even
> this barely scratches the surface of what I consider to be a major
> mistake.  We could have retained the "lazy coding practice" while still
> avoiding this design mistake!  Consider:
>=20
> // Hypothetical alternative definition
>=20
> template <class T> struct logical_not {
> typedef unary_function<T,bool> function_type;
> bool operator()(const T& x) const;
> };
>=20
> This gives us our automatic typedefs which can be used in adapter's
> such as bind1st, while using a design that doesn't violate the "is-a"
> rules. =20

But surely logical_not<T> *is a* unary function object taking a T and
returning a bool. It also has an argument type it accepts and a result
type it returns.=20
OTOH it is intended to be used with value semantics, so dynamic
polymorphism shouldn't really enter play.

<pick nit=3D"I'd expect 'logical_not<T>::function_type' to be 'bool(&)(T)=
'
" />

> As another benefit, it would eliminate the potential for code
> bloat with compilers that don't handle the "empty base" optimization
> very well.

As STL functors are usually lightweight objects with a limited lifetime,
this doesn't worry me.=20
If a functor is expensive to construct, it often is big enough that
another byte or so doesn't make much difference. In that case you
usually keep at most one or a few preconstructed ones around.
=20
> What's the thoughts of others?  Did the standard make a mistake in the
> design here? =20

Just my $0.02. IMHO no.

Regards J=F6rg

--=20
J=F6rg Barfurth                         joerg.barfurth@attglobal.net
--------------- =7F=7F=7Fusing std::disclaimer; ------------------
Software Engineer                     joerg.barfurth@germany.sun.com
Star Office GmbH                      =7Fhttp://www.sun.com/staroffice

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/31
Raw View
In article <1eb9mli.1j27ns213qd8psN@slip139-92-12-86.hm.de.prserv.net>,
  joerg.barfurth@attglobal.net (Joerg Barfurth) wrote:
> <sirwillard@my-deja.com> wrote:
>
> > Most of you are aware of "Item 14:  Make destructors virtual in base
> > classes" in Scott Meyers' book "Effective C++".  I'm even willing to
> > bet that most of you completely agree with this item, since I've
seen
> > many people argue this point in the past. =20
>
> Well, I believe that one should "Make destructors virtual in classes
> having at least one virtual function". IOW: Do this in classes
intended
> to be used as base classes with dynamic polymorphism. That latter form
> is even more general, as it also includes classes to be used with RTTI
> (see the 'Acyclic Visitor' pattern).

Unfortunately, it seems that most people responding so far want to take
offense at the generalized rule, providing alternative wordings and
constraints, instead of taking the idea at face value and evaluating
whether or not there may be the potential for abuse in this specific
instance.  This is probably the case because of the wording I chose for
the title of this thread.  My own biasis played a part in this, I must
admit.  I personally abhore the use of the "is a" idiom in cases where
it's only partially applied.  I know all to well that not everyone
agrees with this, however.  What I really wanted to know in this thread
was a) was the rule even considered, b) are the problems possible from
the decision something to be concerned about, c) why was the method
chosen considered "better" than the method I proposed (or was my
implementation not even thought of)?

> OTOH for some other situations that does not hold. In particular a
> virtual destructor for classes intented to be used as base classes
> (only) for use with compile-time polymorphism (i.e. as template
> parameters)

I didn't propose the use of a virtual destructor here.  This would be a
bad choice for many reasons, not the least of which is the added
overhead caused by the virtual table.  Since the "base class" is used
solely to handle type laundering details in other templates, a virtual
destructor would have been a bad idea.

> > Consider:
> >=20
> > std::unary_function<char, bool>* uf =3D new std::logical_not<char>
();
> > delete uf;
> >=20
> > Admittedly this example is extremely contrived.  To be honest, I
can't
> > think of any legitimate use of this idiom.  Since there doesn't
seem to
> > be any legitimate use, maybe this case can be considered
the "exception
> > that proves the rule".  However, before excepting that I felt that I
> > must fully understand why public inheritance from a non-polymorphic
> > base was being used here.  So, carefully reading through the
standard,
> > all I could come up with was this quote:
> >=20
> > "The following classes are provided to simplify the typedefs of the
> > argument and result types"
>
> That is a valid argument.=20
> In particular there is a valid abstraction 'unary_function taking a T
> and returning a U'. It is much easier to read (and less error-prone to
> write), if the declaration of a functor derives from a
> std::unary_function<T,U> than to check manually whether the typedefs
are
> present, complete and correct.

A detail only for the implementors of the library.  Also only a
somewhat valid argument in any event, since the types in question are
simple and small in number thus making the risk for error minimal to
begin with.  And again, you're making the same mistake that Pete Becker
did here... ignoring the solution I proposed instead.  An embeded class
is as easy to read and as safe from error when writing as a base class
would have been.

> As this class is only really useful as a base class of a template
> parameter, my earlier condition holds true as well. You simply cannot
> use a unary_function with dynamic polymorphism, as you can't
> meaningfully use one by pointer or reference. This is because there
are
> no non-type members (except for the trivial compiler-generated ones).

This is the reason why I at first thought "this must be the exception
that proves the rule".  However, I'm not convinced that you can safely
say "you simply can't use a unary_function with dynamic polymoprhism".
Since public inheritance was used and "is a" relationship is implied,
and so a programmer might well be tempted to use the base pointer to
hold related by different types in a std::list, for instance.  Users
who aren't fully versed in C++ could use this idiom to their down
fall.  If we can easily protect the programmer from making such a
mistake doesn't it make sense to do so?

> > [...] However, even
> > this barely scratches the surface of what I consider to be a major
> > mistake.  We could have retained the "lazy coding practice" while
still
> > avoiding this design mistake!  Consider:
> >=20
> > // Hypothetical alternative definition
> >=20
> > template <class T> struct logical_not {
> > typedef unary_function<T,bool> function_type;
> > bool operator()(const T& x) const;
> > };
> >=20
> > This gives us our automatic typedefs which can be used in adapter's
> > such as bind1st, while using a design that doesn't violate the "is-
a"
> > rules. =20
>
> But surely logical_not<T> *is a* unary function object taking a T and
> returning a bool.

Not if it can't be safely used in all places that a unary_function
object could be.  Since deletion through a base pointer is a place that
it's not safe, the "is a" rules were violated.  You can argue that this
is a corner case that can be violated for specific conditions, which is
what you were doing at first, but you can not argue that the "is a"
rules were not violated.

> It also has an argument type it accepts and a result
> type it returns.=20
> OTOH it is intended to be used with value semantics, so dynamic
> polymorphism shouldn't really enter play.

This is the controversial part of the rule.  Anyone that chooses to use
a non-polymorphic base class will invariably make this argument.  I can
some what see their point, which is why I started out
thinking "exception that proves the rule".  But to me, so long as an
alternative design is available that meets all the same criteria, the
alternative should be preferred.  I think I showed an alternative, but
no one seems interested in commenting on it.

> <pick nit=3D"I'd expect 'logical_not<T>::function_type' to be 'bool(&)
(T)=
> '
> " />
>
> > As another benefit, it would eliminate the potential for code
> > bloat with compilers that don't handle the "empty base" optimization
> > very well.
>
> As STL functors are usually lightweight objects with a limited
lifetime,
> this doesn't worry me.=20

I take exception to this, and so it does worry me.  As used by the
standard algorithms the functors are likely to be short lived.  Light
weight can be argued about, since it's possible for a functor to be
several times larger than the function pointer they were meant to
replace if the compiler doesn't handle empty base optimizations well.
However, the standard algorithms only scratch the surface of possible
uses for functors, and I strongly believe that functors will start to
play a stronger and stronger role in software development in C++ in the
future.  Many uses of functors will result in very long lived instances
being created, and so code bloat such as this is definately something
to consider.  For instance, consider signal/slot implementations.  The
functor is used as a call back on an "event", which will be stored for
the lifetime of an object (possibly the lifetime of the program), with
numerous such functors attached to a single "event".

> If a functor is expensive to construct, it often is big enough that
> another byte or so doesn't make much difference. In that case you
> usually keep at most one or a few preconstructed ones around.

This isn't necessarily even possible, and regardless misses the point.
You even mention the expense of construction, but to my mind this is
just the beginning.  Of more concern is simply the expense of the
object's existence.  Functors need to be as light weight as possible,
and this isn't necessarily the case with the standard functors.

> > What's the thoughts of others?  Did the standard make a mistake in
the
> > design here? =20
>
> Just my $0.02. IMHO no.

I do appreciate the $0.02 on this.  I just don't think my actual
concerns were really addressed in your post.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sirwillard@my-deja.com
Date: 2000/05/27
Raw View
Most of you are aware of "Item 14:  Make destructors virtual in base
classes" in Scott Meyers' book "Effective C++".  I'm even willing to
bet that most of you completely agree with this item, since I've seen
many people argue this point in the past.  If you've ever read any of
my posts, you know that I'm a very firm believer in this design tenant,
and wield it like a kudgel.  Well, my resolve on this issue was shaken
a bit (though just a bit, and personally I've come back around to fully
believing in this once again) while working on a little project
recently.  It all started with a deeper study of the C++ standard
library.  Consider:

std::unary_function<char, bool>* uf = new std::logical_not<char>();
delete uf;

Admittedly this example is extremely contrived.  To be honest, I can't
think of any legitimate use of this idiom.  Since there doesn't seem to
be any legitimate use, maybe this case can be considered the "exception
that proves the rule".  However, before excepting that I felt that I
must fully understand why public inheritance from a non-polymorphic
base was being used here.  So, carefully reading through the standard,
all I could come up with was this quote:

"The following classes are provided to simplify the typedefs of the
argument and result types"

Well, using this idiom solely to "simplify" other class definitions is
hardly an excuse for this practice.  Since std::unary_function has no
functionality all that we get is a bit less typing in other classes to
define a few types.  These base classes could have been eliminated, and
the typedefs hand coded in the handful of function object definitions
that exist in the standard and we would have had the same result.  So,
at best, this use shows laziness instead of good design.  However, even
this barely scratches the surface of what I consider to be a major
mistake.  We could have retained the "lazy coding practice" while still
avoiding this design mistake!  Consider:

// Hypothetical alternative definition

template <class T> struct logical_not {
typedef unary_function<T,bool> function_type;
bool operator()(const T& x) const;
};

This gives us our automatic typedefs which can be used in adapter's
such as bind1st, while using a design that doesn't violate the "is-a"
rules.  As another benefit, it would eliminate the potential for code
bloat with compilers that don't handle the "empty base" optimization
very well.

What's the thoughts of others?  Did the standard make a mistake in the
design here?  Since I'm a very strong believer in this design rule I
often find myself explaining to others why this is a mistake.
Explaining it to them is going to be even harder to do if they can
point out cases such as this one.  So I'd really like to hear the
thoughts of others, especially any that may have been involved in the
design or acceptance of this portion of the standard.

--
William E. Kempf
Software Engineer, MS Windows Programmer


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Pete Becker <petebecker@acm.org>
Date: 2000/05/27
Raw View
sirwillard@my-deja.com wrote:
>
> Most of you are aware of "Item 14:  Make destructors virtual in base
> classes" in Scott Meyers' book "Effective C++".  I'm even willing to
> bet that most of you completely agree with this item, since I've seen
> many people argue this point in the past.

I've said many times that this statement is too strong. The true rule is
to make destructors virtual in classes that are intended for use as
polymorphic bases.

> Well, using this idiom solely to "simplify" other class definitions is
> hardly an excuse for this practice.  Since std::unary_function has no
> functionality all that we get is a bit less typing in other classes to
> define a few types.

Yes, we get less typing of duplicate information. Having to type the
same thing multiple times leads to errors. So, while it's literally "a
bit less typing," it is also a technique for reducing error rates.

--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Contibuting Editor, C/C++ Users Journal (http://www.cuj.com)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Bill Wade" <bill.wade@stoner.com>
Date: 2000/05/27
Raw View
> Most of you are aware of "Item 14:  Make destructors virtual in base
> classes" in Scott Meyers' book "Effective C++".  [...] If you've ever read
any of
> my posts, you know that I'm a very firm believer in this design tenant,
> and wield it like a kudgel.

To paraphrase Scott's excellent exposition:

<  Base classes with non-virtual destructors are dangerous with delete. >

There is some real truth there, but it is easy to take this kind of thing
too far.

Here are some other true statements.

Auto and static variables are dangerous with delete.
Class data members are dangerous with delete.
New[] and placement new are dangerous with delete.
Literals are dangerous with delete.

So, Bill's corollary to item 14:

Don't use auto or static variables, classes with data members, new[],
placement new or literals.

Now your cudgel can hit much harder ;-)

Regards.


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]