Array of derived classes

  • Thread starter Michael Grünewald
  • Start date
M

Michael Grünewald

Dear newsgroup,

I have a basis class A and derivatives B1…Bn and would like to build an
array containing various Bi's, and was unable to do so. Your help would
be much appreciated!

Ideally A would be a virtual (non instantiable) class, but this
constraint is not realizable if we want to declare an array of objects
of type A.

In my attempt, I defined a `=` operator for objects of type A
and declared an array

A T[n];

and initialised it like this

T[0] = B0();
…

Which (of course?) ended up with T containing n copies of the implicit
object of type A underlying Bi, but nothing like Bi.

Am I doing here something wrong or pointers are the only way to obtain
heterogeneous arrays?

Is an allocation à la C

A T[n] = { B0(), … };

allowed in C++ ?

I would also be very interested in literature references on this topic
or similar topics, I am very new to C++ (but skilled in other languages,
some OO, some not) and do not know yet about all these good books…

Thank you very much for your help!
 
K

Kevin McCarty

Hi Michael,

Dear newsgroup,

I have a basis class A and derivatives B1…Bn and would like to build an
array containing various Bi's, and was unable to do so.  Your help would
be much appreciated!

I am afraid the language does not permit it. If you define an array

A arr[n];

(by the way, I used "arr" above rather than "T" ... "T" is typically
used to indicate a templated type rather than a variable name in
idiomatic example code snippets)

.... and try to assign derived objects of A into it, then you will just
end up with "slicing" exactly as you describe below.

Which (of course?) ended up with T containing n copies of the implicit
object of type A underlying Bi, but nothing like Bi.

Am I doing here something wrong

Almost certainly, sorry.
or pointers are the only way to obtain heterogeneous arrays?

More or less. But instead of A* arr[n], consider something like one
of the following work-alike solutions:

* boost:: (or in C++11, std::) shared_ptr<A>[n], or
std::unique_ptr<A>[n]

* boost::ptr_array<A, size>

* More generally, the Letter-Envelope idiom may be what you want; see
for instance
http://www.panix.com/~elflord/cpp/envelope/

* In the (quite unlikely) case that you know at compile-time what the
ordering of the derived types of your objects in your data structure
would be, you could in principle use boost:: (or in C++11, std::)
Is an allocation à la C

   A T[n] = { B0(), … };

allowed in C++ ?

It is allowed, but it too will lead to slicing, and again you will
only find base-class "A" objects in T[].

I would also be very interested in literature references on this topic
or similar topics, I am very new to C++ (but skilled in other languages,
some OO, some not) and do not know yet about all these good books…

Letter-Envelope idiom is apparently described well (I say apparently,
since I don't have the book myself) in _Advanced C++ Programming
Styles and Idioms_, James Coplien, Addison Wesley (1991)... the book
overall would be pretty dated though.

boost::shared_ptr and boost::ptr_array are described in the Boost
library documentation. std::shared_ptr and std::unique_ptr in, for
instance, _The C++ Standard Library_, 2nd Edition, Nicolai Josuttis
(2012). Book web site is at http://www.cppstdlib.com/

- Kevin B. McCarty
 
K

K. Frank

Hello Michael!

Dear newsgroup,

I have a basis class A and derivatives B1…Bn and would like to build an
array containing various Bi's, and was unable to do so.  Your help would
be much appreciated!

As you've discovered, you can't, exactly, make an array
of various Bi's. And as you've speculated, and Paavo and
Kevin have replied, you can use an array of pointers to
achieve your goal.

The key point is that in c++ polymorphism is implemented
using pointers (and references). So a pointer to your
base class, A:

A *pointer_to_A;

can point not only to an A, but also to a derived Bi:

B1 b1;
B2 b2;
pointer_to_A = &b1; // points to a B1
pointer_to_A = &b2; // now it points to a B2

Other object-oriented languages manage polymorphism
differently, but this is how c++ does it.

(For completeness, the other important piece of the
polymorphism puzzle is the use of virtual functions.
If class A defines a virtual member function, say,

virtual void A::doSomething() { x = 0; }

and the derived classes, Bi, override doSomething() with
their own versions that do something different:

void B1::doSomething() { x = 1; }
void B2::doSomething() { x = 2; }

then calling doSomthing() through pointer_to_A:

pointer_to_A->doSomething();

doesn't necessarily call A's version, A::doSomething(),
(which would set x to 0), but depends on to which type
of object pointer_to_A actually points. So if
pointer_to_A points to a B1, then x will get set to 1
rather than to 0.)
Ideally A would be a virtual (non instantiable) class, but this
constraint is not realizable if we want to declare an array of objects
of type A.

But this constraint is perfectly fine if you use an
array of pointers. The conventional c++ jargon for
your non-instantiatable base class is an "abstract base
class." The derived classes that can be instantiated
are typically called concrete classes.

It's perfectly fine for A to be an abstract base class
and have a variable of type pointer-to-A (or an array
of pointers to A). These can point to instances of the
concrete derived classes, Bi.
...
Am I doing here something wrong or pointers are the only way to obtain
heterogeneous arrays?

Yes, pointers are the way to go to get heterogeneous (in the
sense of polymorphic) arrays. That's how c++ is designed to
work.

(Of course, as Paavo and Kevin mentioned, you might want
to use some fancier and "better" container such as a
std::vector instead of an array, and you might want
to use a fancier and "smarter" pointer such as a
std::shared_pointer instead of a raw pointer, but the
basic issue -- that c++ uses pointers (and references)
for polymorphism is still the same.)
...
I would also be very interested in literature references on this topic
or similar topics, I am very new to C++ (but skilled in other languages,
some OO, some not) and do not know yet about all these good books…

A good place to start, especially if you are new to c++, is
Marshall Cline's C++ FAQ-Lite:

http://www.parashift.com/c++-faq-lite/

His sections on inheritance, starting with:

http://www.parashift.com/c++-faq-lite/basics-of-inheritance.html

are relevant, and he gives a synopsis of c++ polymorphism in:

http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.2

(The C++ FAQ Lite is worth reading in general -- both to
look up answers to specific questions, and to browse just
for inspiration and general learning. It is not, however,
in any sense complete, so it's not really a good tutorial,
and is certainly not a comprehensive reference.)
Thank you very much for your help!

Good luck, and Happy Hacking!


K. Frank
 
M

Michael Grünewald

Hi Kavin & Paalo!

Thank you very much for your detailed answers,
I found them useful!

Kevin said:
* In the (quite unlikely) case that you know at compile-time what the
ordering of the derived types of your objects in your data structure
would be, you could in principle use boost:: (or in C++11, std::)
tuple<B1, B2, B3, ...> or whatever.

Well, here you hit something very peculiar to my situation: B1…Bn are
parsers.

In a nicer world, B1…Bn would be instances of A, an object describing
the interface of parsers and accepting sufficiently general description
of the parsing task.

Unfortunately, families of parsers are difficult to parametrise, even in
simple cases, so I opted for the (rather clumsy) solution provided by
specialisation of A into the Bi's.
Letter-Envelope idiom is apparently described well (I say apparently,
since I don't have the book myself) in _Advanced C++ Programming
Styles and Idioms_, James Coplien, Addison Wesley (1991)... the book
overall would be pretty dated though.

boost::shared_ptr and boost::ptr_array are described in the Boost
library documentation. std::shared_ptr and std::unique_ptr in, for
instance, _The C++ Standard Library_, 2nd Edition, Nicolai Josuttis
(2012). Book web site is at http://www.cppstdlib.com/

Thank you for your input!
 
M

Michael Grünewald

He Frank!

Thank you for your nice answer,

K. Frank said:
But this constraint is perfectly fine if you use an
array of pointers. The conventional c++ jargon for
your non-instantiatable base class is an "abstract base
class." The derived classes that can be instantiated
are typically called concrete classes.

It's perfectly fine for A to be an abstract base class
and have a variable of type pointer-to-A (or an array
of pointers to A). These can point to instances of the
concrete derived classes, Bi.

This is good to know!

However I feel this is a bit odd. On the one hand, pointers are tricky,
and the shortest way to undefined behavior of a program. On the other
hand, they allow a terse way to iterate over an array (with the ++
operator(s)), that every C or C++ programmer likes. It seems to me that
the introduction in the language of references (not so unsafe pointers
that you can use as function arguments) and of the overloading (for the
definition of smart containers understanding the ++ idiom) try to make
pointers a not so crucial part of the language.¹ But somehow “banned”
from the language, pointers “come back” with polymorphism!

¹ I finally managed to write a sentence that qualifies as bad example
for manuals of style, sorry! :)

(Of course, as Paavo and Kevin mentioned, you might want
to use some fancier and "better" container such as a
std::vector instead of an array, and you might want
to use a fancier and "smarter" pointer such as a
std::shared_pointer instead of a raw pointer, but the
basic issue -- that c++ uses pointers (and references)
for polymorphism is still the same.)

As I mentioned in my answer to Paavo & Kevin, the Bi's are parser and my
array is constant…
(The C++ FAQ Lite is worth reading in general -- both to
look up answers to specific questions, and to browse just
for inspiration and general learning. It is not, however,
in any sense complete, so it's not really a good tutorial,
and is certainly not a comprehensive reference.)

Wonderful, I will try to get acquainted to it, the sooner, the better.

Thank you again!
 
I

Ian Collins

Hey, I even wrote a "goto" into the code last week, as this was the
cleanest and easiest way to achieve the needed goal *in the situation at
hand*.

Good lord, someone actually found a case where goto was appropriate! I
guess there's a first time for everything.

:)
 
J

Juha Nieminen

Paavo Helde said:
Also note that a raw array like A T[n] is very rarely the right choice in
C++. One should normally use std::vector or some other STL container.

It's the better choice when:

1) The size of the array is known at compile time and never changes at
runtime.

2) The array needs to take as little RAM as possible, and be as efficient
as possible. (Most typically this is the case with a very small array
as a member of a class that gets instantiated a lot. In this situation
using std::vector would kill performance.)

Of course with C++11 it may be better to use std::array instead, which
is basically the same thing, but wrapped in a class with utility member
functions (and also directly copyable, which static arrays aren't, except
if they are a member of a class/struct).
 
M

Michael Grünewald

Hi Paavo,

thank you for your comments!

Paavo said:
Programming is an engineering discipline, not some politics or religion.

I may have not been incredibly well inspired when I wrote “banned,” this
was not to take too literally… I meant “one tries to avoid or find
better solutions for pointers in the contexts where these drawbacks
outweigh benefits” but your words are definitely better. :)
 
J

Jorgen Grahn

.
However I feel this is a bit odd. On the one hand, pointers are tricky,
and the shortest way to undefined behavior of a program. On the other
hand, they allow a terse way to iterate over an array (with the ++
operator(s)), that every C or C++ programmer likes. It seems to me that
the introduction in the language of references (not so unsafe pointers
that you can use as function arguments) and of the overloading (for the
definition of smart containers understanding the ++ idiom) try to make
pointers a not so crucial part of the language.¹ But somehow "banned"
from the language, pointers "come back" with polymorphism!

That pretty much sums it up for me. I like pointers as iterators, but
I'm careful when I have to use them in the role of handles to some
long-lived resource. (And it's not just for run-time polymorphism you
need that.)

It's usually not a big deal, though. Make the pointer a class member,
make that class responsible for the lifetime of the pointed-to object,
and you're safe. In my code, I still haven't seen a need to apply
generic smart pointers yet, but YMMV.

/Jorgen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,817
Latest member
DicWeils

Latest Threads

Top