Topic: polymorphism in STL containers


Author: "Philip Koester" <kozmik@okay.net>
Date: 1999/02/08
Raw View
>The STL containers make a copy of your actual type and that is what gets
put
>in containers. If you want polymorphism in containers you need to put
>pointers of the base type in your containers and you have true
polymorphism.
>It is then your responsibility to free the pointers before the container
goes
>out of scope or you will have memory leaks.

Storing pointers in STL containers may sometimes do you what you want, but
not always.  There are two disadvantages with that.  Firstly, you cannot
always ensure that the container's elements point to an existing object:

    vector<int*> v;
    int* p = new int(0);
    v.push_back(p); // store an int pointer in a vector
    delete p; // delete an "element" without the vector noticing that
    cout << *v[0] << endl; // undefined behavior

To overcome this problem, you can write an own smart pointer class that uses
reference counting.  The second problem is, obviously, that sorting doesnt
work in sorted containers, since you want objects compared rather than their
pointers.  So, to solve this problem, while you're at it, make your smart
pointer class compare the *objects* they are pointing to, using operator
==() and operator <().  (This is taken from a German book on the STL by N.
Josuttis.)  Then, what you end up with is a clean and robust way to store
"references" in STL containers.

Regards,

    Philip
---
[ 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: "Alex Martelli" <alex@magenta.com>
Date: 1999/02/02
Raw View
Oliver Poe-min Wu wrote in message <36a8c7ab.10212252@news.ntu.edu.tw>...
    [snip]
> I'm wondering when I put different derived data types in a
>container of base data type, why can't C++ treat them polymorphically?

Because the container holds its contents by-value; thus,
when any item is placed into it, it is sliced to its base
class subobject, just as it would be were it assigned to
a scalar variable defined as being of that base type.  An
unpleasant effect (which has led some, such as Scott
Meyers, to suggest one should never inherit from a
concrete class), but an inevitable consequence of C++
variables, and container items, holding objects' values
rather than implicit references to the objects (the latter
tack is followed in other OO languages that I know of).

Letting containers hold their contents by-value gives you,
the container's user, most flexibility, since you can easily
declare a container of pointers if that is what you desire;
this flexibility, when used to best effect, allows you to
obtain optimal efficiency.  When you choose to hold by
value, you will, however, get slicing rather than polymorphic
behaviour, and, if these semantics are not acceptable for
your program's needs, then you must forego the possible
efficiency gain and hold pointers (or some kind of smart
pointers) rather than values.


Alex





-----------== Posted via Newsfeeds.Com, Uncensored Usenet News ==----------
 http://www.newsfeeds.com/       The Largest Usenet Servers in the World!
-----------== Over 66,000 Groups, Plus  a  Dedicated  Binaries Server ==----------
---
[ 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: James Kuyper <kuyper@wizard.net>
Date: 1999/01/30
Raw View
David R Tribble wrote:
....
> objects.  (You can't have polymorphic objects, only polymorphic
> pointers to objects.)

A union could be considered a polymorphic object, particularly if
encapsulated as a private member of a class, along with a type field
indicating which member of the union was currently in use, and access
methods that keep the type field accurate.


[ 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: "Michael Ormsby" <mormsby@uswest.com>
Date: 1999/01/21
Raw View
Oliver Poe-min Wu wrote:

>  Hi:
>          I'm wondering when I put different derived data types in a
>  container of base data type, why can't C++ treat them polymorphically?


Mumit Khan's excellent 'STL Newbie Guide' has some pointers on
workarounds for this situation. Look for the URL:

  http://www.xraylith.wisc.edu/~khan/software/stl/STL.newbie.html

and then look for the heading 'How do I store derived objects in STL
containers?'.

|----------------------------------------------------------------------|
| Mike Ormsby            mormsby@uswest.com      +1 303 294 1688       |
| U S WEST Wireless LLC  1999 Broadway, 11th Fl  Denver, CO 80202 USA  |
|----------------------------------------------------------------------|



[ 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: David R Tribble <dtribble@technologist.com>
Date: 1999/01/22
Raw View
Oliver Poe-min Wu wrote:
>> Hi:
>>         I'm wondering when I put different derived data types in a
>> container of base data type, why can't C++ treat them
>> polymorphically?

Edward Diener wrote:
> The STL containers make a copy of your actual type and that is what
> gets put in containers. If you want polymorphism in containers you
> need to put pointers of the base type in your containers and you have
> true polymorphism.  It is then your responsibility to free the
> pointers before the container goes out of scope or you will have
> emory leaks.

It's the same problem as trying to make arrays of non-pointer
objects act "polymorphically".  For example:

    class Base { ... };
    class Der: public Base { ... };

    void foo(Base array[], int sz)
    {
        for (int i = 0;  i < sz;  i++)
            ...do something to array[i],
               which is a Base object
    }

    void bar()
    {
        Der    objs[10];
        foo(objs, 10);    // Error
    }

Even though this will compile, because the call to foo() is passed
a polymorphic Der pointer (pointing to array objs[]), it will fail
to run because foo() thinks the objects in array[] are actually
Base objects.  (Consider the difference between &array[0] and
&array[1].)

This is similar to the situation with STL containers.  The solution
is to use containers of polymorphic POINTERS, not "polymorphic"
objects.  (You can't have polymorphic objects, only polymorphic
pointers to objects.)

-- David R. Tribble, dtribble@technologist.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: James Kuyper <kuyper@wizard.net>
Date: 1999/01/23
Raw View
David R Tribble wrote:
...
> objects.  (You can't have polymorphic objects, only polymorphic
> pointers to objects.)

A struct containing a union plus a type code to determine which member
is in use could be considered a polymorphic object. This is more useful
in C than in C++, given the restrictions on union members.


[ 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: blargg@flash.net (Gargantua Blargg)
Date: 1999/01/18
Raw View
In article <36a8c7ab.10212252@news.ntu.edu.tw>, OLIVERWU@ms4.url.com.tw wrote:

> Hi:
>         I'm wondering when I put different derived data types in a
> container of base data type, why can't C++ treat them polymorphically?
>
>         Like this:

[shorter example]

struct vehicle { };
struct car : vehicle { };
struct motorcycle :car { };

int main()
{
    car A;
    motorcycle B;
    list<vehicle> plate;

    plate.push_back(A);
    plate.push_back(B);
}

> The result of _CODE_TWO_ [which uses pointers instead] is the desired
> "car motorcycle" while the result of _CODE_ONE_ [which is the example above]
> is "vehicle vehicle".
>
>         I ran this program with djgpp 2.8.1.
>
>         I know that FAQ says "a bag of apple is not a bag of fruit,"
> and I can understand it. But why can't "a bag of fruit is a bag of
> apple and banana and orange, etc." It's not confusing getting an apple
> from a bag of fruit, isn't it? And since I can do what I want by using
> pointers, it's wierd why can't STL do this.

C++, like C, is statically typed. It's that simple. Variables have a
static type, and stay that type, no matter what. It cannot be changed.

Going by value, a variable of type vehicle is always a variable of type vehicle.

Going by pointer, a variable of type vehicle* is always a variable of type
vehicle*, but through inheritance, it can point to something whose type is
derived from vehicle (but note that the pointer is *always* type
vehicle*).

Static typing catches many errors sooner than dynamic typing does (what it
sounds like you want), and will cause endless grief if you don't
understand and work with it. But when you work with it, it can perform
amazing feats. "know your tools"

--
"I don't like my edges rounded off" - Ani DiFranco

Gargantua Blargg | blargg@flash.net | http://www.flash.net/~blargg/
---
[ 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: "Makita" <Makita@NOSPAM-hotmail.com>
Date: 1999/01/17
Raw View

The behavior is not specific to STL or even collections for that matter.
It's a matter of copy by reference vs copy by value.

In the first example the collection is by value. That means that push_back
creates a new vehicle and calls the (compiler generated) vehicle copy
constructor passing the car or motorcycle as a _vehicle_  argument. So you
have assigned the vehicle-specific attributes of that vehicle to the new
element. In this example there are no attributes, but the point is that the
object's value is being copied, not its address.

In the second example, the collection is by reference (i.e. pointer). That
means push_back only creates a vehicle pointer and assigns it the address
of whatever was passed in. The original car or motorcycle object ( and
therefore its vtable ) is what is referenced when you iterate over the
collection.

Oliver Poe-min Wu <OLIVERWU@ms4.url.com.tw> wrote in article
<36a8c7ab.10212252@news.ntu.edu.tw>...

> I know that FAQ says "a bag of apple is not a bag of fruit,"
> and I can understand it. But why can't "a bag of fruit is a bag of
> apple and banana and orange, etc." It's not confusing getting an apple
> from a bag of fruit, isn't it? And since I can do what I want by using
> pointers, it's wierd why can't STL do this.




[ 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: Edward Diener <eddielee@abraxis.com>
Date: 1999/01/17
Raw View
The STL containers make a copy of your actual type and that is what gets put
in containers. If you want polymorphism in containers you need to put
pointers of the base type in your containers and you have true polymorphism.
It is then your responsibility to free the pointers before the container goes
out of scope or you will have memory leaks.

Oliver Poe-min Wu wrote:

> Hi:
>         I'm wondering when I put different derived data types in a
> container of base data type, why can't C++ treat them polymorphically?
---
[ 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: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/01/17
Raw View
On 16 Jan 1999 16:59:25 GMT, Oliver Poe-min Wu <OLIVERWU@ms4.url.com.tw> wrote:

> I'm wondering when I put different derived data types in a
>container of base data type, why can't C++ treat them polymorphically?

Ie, why do we need to use Vector<Shape*> instead of Vector<Shape>.

One reason -- because sizeof(Circle) is different from
sizeof(Triangle).  Just say you create a Vector with a capacity for
10 Shape objects "Vector<Shape> v; v.reserve(10)".  What should the
size of the array inside the Vector be?  Whatever it is, it has to
be larger than 10*sizeof(Shape), because some of the shapes will
be Circle objects, and some will be Triangle objects, and the size
of either of these is larger than size of Shape.

In sizeof(Square)==sizeof(Triangle)==N, then we could always
make class Shape have a size of N and make a Vector<Shape>.  When
we call v[3].area(), the polymorphic tag inside each Shape object
(ie, the virtual pointer or type index, or whatever) would determine
whether we call Square::area() or Triangle::area().  Either of
these functions knows how to interpret the raw N bytes of data
in class Shape.  One problem with this design is that it calls
for protected data in the base class, as well as a reinterpration
of the data for each dynamic type.  This is technically possible,
but bad for maintainance.  And the big problem is that in most
inheritance hierarchies, sizeof(Square)!=sizeof(Triangle).


Two reason -- because C++ is strongly typed.  Remember that each
element in the container still has the same static type.  When
you insert an element into the container, it is copied using the
copy constructor for the type.  So say you have a Vector<Shape>
and you insert a Circle into it, the shape is inserted by
calling Shape::Shape(const Shape&).  The result is that you slice
off the Circle part of the shape.  But if you had a Vector<Shape*>,
then the insertion is done using Shape*::Shape*(const Shape*&),
which is just a harmless copy of a pointer.

So if you use Vector<Shape*>, why doesn't the container support
automatic deletion of the pointed to objects.  Because in general
you might not want to delete the pointed to objects.


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ 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: 1999/01/17
Raw View
In article <36a8c7ab.10212252@news.ntu.edu.tw>, Oliver Poe-min Wu
<OLIVERWU@ms4.url.com.tw> writes
>       I know that FAQ says "a bag of apple is not a bag of fruit,"
>and I can understand it. But why can't "a bag of fruit is a bag of
>apple and banana and orange, etc." It's not confusing getting an apple
>from a bag of fruit, isn't it? And since I can do what I want by using
>pointers, it's wierd why can't STL do this.

If all the objects were the same size you would probably have no problem
in practice.  Had C++ been designed so that instances of polymorphic
objects tracked their size you would probably have no problem until you
tried random access which could no lobnger be constant time.  For
example suppose you had:

Fruit basket[20];
// code filling the basket

Hey, hang about how much storage should the first line have provided?

OK so we cannot do it for simple arrays so let us try a vector:

vector<Fruit> basket(0);
// fill it by using push_back
basket[5].eat();  // I specialised vector and added an eat function

How will the program compute the address of basket[5]? Only be locating
each of the previous items but that is not how vector's are specified.

Of course once we understand the design issues we can do several things
such as create a handle class.

>

Francis Glassborow      Chair of 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              ]