Re: inheritance from STL-container

From: Jerry Coffin (jcoffin_at_taeus.us)
Date: 08/12/04


Date: Wed, 11 Aug 2004 18:48:27 -0600

In article <410682c7.23796998@news.individual.de>,
XXXMartin.Aupperle@PrimaProgrammXXX.de says...
> >> - providing means for DBC (Design by Contract). For Example: testing
> >> whether iterators really point to my container.....
> >
> >How exactly would inheriting from a container help in that?
> >
> class D : public B
> {
> ...
> void f();
> };
>
> void D::f()
> {
> ASSERT( whatever ); // DBC-precondition
>
> B::f();
>
> ASSERT( something ); // postcondition
> }

I know how DBC works in general, but you apparently missed the real
point of my question, which was how you apply it in the case of the
standard library.

This has a number of problems. First of all, you've shown the DBC
being applied in a member function -- but only a relatively small
number of algorithms are implemented as member functions. Most
algorithms are free-standing templates, so derivation from the
container wouldn't allow you to add the DBC stuff to them anyway.

If you want to apply DBC to the standard library, most of it ends up
happening in two places: the iterators and the algorithms. Even
here, it becomes somewhat difficult to come up with meaningful
preconditions, because an iterator can be anything that supports a
few specific operations, so even if you create your own iterator
types, the algorithms can't enforce their use.

In the end, you can ensure that when one of your iterators is used
that it refers to something meaningful, and that when you compare two
of your iterators, that it's a meaningful comparison. It's hard to do
much beyond that without breaking quite a bit of legitimate code.

In any case, deriving from the container is neither necessary nor
sufficient for this task. Deriving from the existing iterators is
possible, but also unnecessary (and provides little of use).

The main place you have to work closely with the standard library is
when you provide your own specializations of the algorithms in the
standard library. By my reading of the standard, you can already
provide an explicit specialization of standard template, and you can
even put it in the std namespace when it's specialized for a type
you've defined (such as your special iterators).

That brings us back to my original question: how does deriving from a
standard container contribute to incorporating DBC? I'll answer that:
at most, it contributes a bit in the few cases where you're dealing
with member functions.

[ ... ]

> I did the same, and I find it convenient to have a container that
> stores pointers but also is the owner of the objects.
>
> I did not ask for support of such a container by the Standard. But I
> ask for a design that allows me to do it myself.

That seems to be essentially the current case: it does essentially
nothing to support it, but does nothing to prevent you from doing it
either.
 
> Pls explain why you find that it is difficult to do well, and also
> tell me what you use instead.

Generally smart pointers. It's difficult to do well primarily
because of two things: first of all, the container is rarely in a
position to handle ownership issues very well. Second, because the
primary time storing pointers provides a real advantage are also
those in which it progresses from difficult to impossible for the
container to handle ownership properly (e.g. storing a number of
pointers to a single object that can't reasonably be copied).

By contrast, the right smart pointer is nearly always in a MUCH
better position to handle the ownership issues not only efficiently,
but correctly as well.

[ ... ]
  
> >At least all those you want to make public in B -- but if this is a
> >very large number, chances are that A has problems anyway.
>
> Not necessarily. Are you a friend of styleguides that say "a class
> must not have more than x members" ? A class must have an (explicit)
> destructor, copy constructor etc?

No -- as I said, "chances are". I'm not prepared to say that every
possible class with (say) 50 member functions must be poor. OTOH,
I've dealt with OO code for over 20 years, and in that time I've
neither seen a well-designed class with that many members, nor have I
seen a situation in which I thought that would be the best available
design.
 
> No no, there is absolutely no correlation between size and quality of
> a class.

Theoretically that may be partially true. Statistically it's
absolutely false: the VAST majority of extremely large classes are
also extremely poorly designed.

> E.G. we have a grid that has well over 50 public functions,
> not counted the interface for the data model and the selection model
> (which can be plugged in). Is that bad? Why do we have problems anyway
> here? Would these problems go away if the functionality is divided
> into even more classes? I doubt.

Since I've never seen the code in question, I can't really comment on
it directly. It's always possible that if I saw it I'd agree that it
was the best way to deal with its particular problem. I can say,
however, that if that was the case, it would unique in my experience.
 
[ ... ]

> Well, I agree that there is something like "design for STL". But I
> would not say that designs that do not confirm to that are
> automatically poor.

I wouldn't say such a thing either -- but I would say that so far
your posts contain a lot more assertions than evidence.

Let's assume, for the moment that your assertion above is correct --
your grid with is a close approximation of optimal as it stands.

For the sake of argument (lacking any details to go on) I'm going to
assume that your grid is vaguely similar to std::vector<std::vector
<something> >, so we have at least something to which to do a
comparison. Likewise, I'm going to assume that "well over 50" really
means 75 (so we can do some math on a concrete number).

In that case, we have a problem: std::vector only has around 20
member functions (unless you count all overloads separately). That
means that if you could derive from std::vector, it would provide (at
best) only 20 out of 75, or just over 25% of your member functions.
Realistically, it's unlikely that what you've done maps quite so
directly to what it provides, so it might really provide 15 member
functions, leaving you 60 more to implement yourself -- reducing the
savings to something like 20% (and in all honesty, I'm probably still
being generous).
 
> You might tell me pls how you handle ownership in your programs, and
> why having the container do it is bad.

Having the container do it is bad because a container is almost never
in a position to do it well.

With pointers, all the other code has to be highly aware of the fact
that it's working with something it doesn't own. Just for example,
you get a pointer from the collection and dereference it.
Unfortunately, if you store it even extremely temporarily, you have
to account for all possible code paths between obtaing and using the
pointer, to ensure that nothing else can delete the pointer from the
collection in the meantime.

That generally means you just about need to do something on the order
of explicit reference counting, in which you lock the pointer anytime
you obtain it, and then release it when you're done. That, however,
is thoroughly error-prone -- to the point that I've never seen it
done correctly, even once, in any more than toy-sized sample code.

> I agree. But I do not see why there need to be a tradeoff in the first
> place. For example, one could provide a trait that specifies whether
> the dtor should be virtual, or not.

It's certainly true that traits, policies, etc., can do a lot. It may
be that in this case all the expensive operations can be factored out
in such ways. OTOH, I've yet to see (or try to write) a real
implementation. While I don't see obvious barriers to its being
done, until or unless somebody has done it, I hesitate to assume that
it will be trivial to do it.

For that matter, I'd note that the current specification explicitly
almost all of the standard library to support extra arguments as long
as default values are provided so the standard invocations will work.
IOW, while it doesn't guarantee that it'll be portable, it allows a
vendor to use virtual functions if he wishes, and to use a traits
class to determine what (if anything) to make virtual, assuming he
provides a default argument (or overload, specialization, etc.) that
doesn't require that extra argument to be passed.

Even if we blithely assume that it can be done with no tradeoff at
all (something I've yet to see) it still looks to me like something
that only rarely provides any benefit at all, and even when it does,
the benefit is likely to be quite small.

-- 
    Later,
    Jerry.
The universe is a figment of its own imagination.


Relevant Pages

  • Re: Strategies for avoiding new?
    ... >allocate and store in a container" strategy. ... So whether you store pointers or values in the container depends upon what ...
    (alt.comp.lang.learn.c-cpp)
  • Re: Strategies for avoiding new?
    ... > container is important (which is why I use map or whatever.) ... from the container and manipulate it directly. ... So after my snippet, ... using pointers is unnecessary. ...
    (alt.comp.lang.learn.c-cpp)
  • Re: Code critique
    ... Other than the 'less typing' argument, is there any reason ... I intended it to be a standard container such as deque. ... The aim is to keep using a container of pointers, ... Rethrowing the same exception often hints at bad ...
    (comp.lang.cpp)
  • Re: Does deleting a container of pointers also delete the (contained) pointers?
    ... When using an STL container, ... > pointers also call delete on the pointers? ... > int uid; ... > will that destroy the queue and the inner elements as well, ...
    (comp.lang.cpp)
  • Re: REPORT FORM.. PREVIEW locks underlying table?
    ... It doens't look a free table (including cursors), but a table/view ... inside a container (DBC). ...
    (microsoft.public.fox.programmer.exchange)

Loading