Problem with static downcast of base type to derived type

D

Dom Jackson

I have a program which crashes when:

1 - I use static_cast to turn a base type pointer into a pointer to a
derived type

2 - I use this new pointer to call a function in an object of the
derived type

3 - this function then 'grows' the derived type object (by pushing
onto a vector).

Is this actually possible, or should I be doing something else? In
more detail, what I'm doing is (there's a complete test program below,
which crashes):

1 - I have a base class which doesn't do very much;

2 - I declare a derived class with public inheritance from the base
class;

3 - the derived class has a method which pushes onto a vector in the
derived class;

4 - I create an STL set of base class objects;

5 - I then insert an object of the derived class into this set. I use
the set 'insert' method, which returns an iterator to the new object
in the set, but this is an iterator to a *base* object (I hope)

6 - I convert the iterator to a pointer, and static_cast the pointer
to a pointer to an object of the derived class

7 - I call my new method via this pointer, pushing onto the vector in
the derived-class-object. Bang.

Any help much appreciated.

Thanks -

Dom

===========================================
#include <vector>
#include <set>
#include <iostream>

using std::vector;
using std::set;
using std::cout;

class A {
public:
A(int v) :
val(v) {}
int val;
};

class D : public A {
public:
D(int v) :
A(v) {}

mutable vector<int> vec;

void dfunc(void) const {
std::cout << "hello world " << val << "!\n";
// ********************************************
// Ok without this line, crashes with this line
vec.push_back(val);
// ********************************************
}
};

class ALess {
public :
bool operator() (const A& a1, const A& a2) const {
return a1.val < a2.val;
}
};

typedef set<A, ALess> ASet;
typedef ASet::iterator ASetIter;
typedef std::pair<ASetIter, bool> ASetInsRetVal;

int main() {
ASetInsRetVal retval;
ASet aset;

for(int i=0; i<4; i++) {
retval = aset.insert(D(i));
if(!retval.second)
cout << "insert failed\n";
else {
const D *dptr = static_cast<const D*>(&(*retval.first));
dptr->dfunc(); // crashes - see above
}
}
}
==============================================
 
E

Erik Wikström

I have a program which crashes when:

1 - I use static_cast to turn a base type pointer into a pointer to a
derived type

2 - I use this new pointer to call a function in an object of the
derived type

3 - this function then 'grows' the derived type object (by pushing
onto a vector).

Is this actually possible, or should I be doing something else? In
more detail, what I'm doing is (there's a complete test program below,
which crashes):

1 - I have a base class which doesn't do very much;

2 - I declare a derived class with public inheritance from the base
class;

3 - the derived class has a method which pushes onto a vector in the
derived class;

4 - I create an STL set of base class objects;

5 - I then insert an object of the derived class into this set. I use
the set 'insert' method, which returns an iterator to the new object
in the set, but this is an iterator to a *base* object (I hope)

6 - I convert the iterator to a pointer, and static_cast the pointer
to a pointer to an object of the derived class

7 - I call my new method via this pointer, pushing onto the vector in
the derived-class-object. Bang.

This will not work, std::set holds objects of the type given as a
parameter, in this case base-objects and not derived objects, the
derived objects will be sliced into base objects. So what you are doing
is trying to call a function which does not exist to manipulate a vector
that does not exist.

To solve your problem you could use a set of pointers to base instead of
a set of base (e.g. std::set<Base*> instead of std::set<Base>) and
create the objects using new (e.g. set.insert(new Derived());).

Besides that, when casting from base to derived you should use
dynamic_cast and check if the cast succeeded (in which case the returned
pointer will be non-0). If you do that in your code you will notice that
you will always get a 0-pointer. A cast from pointer to base class to a
pointer to derived is only allowed if the object pointed to is of type
derived.

You can always cast from pointer to derived to pointer to base using
static_cast, but when going the other way you shall use dynamic_cast.
 
J

Jonathan Mcdougall

You can always cast from pointer to derived to pointer to base using
static_cast, but when going the other way you shall use dynamic_cast.

"Shall" might be a bit strong here. There are cases where, because of
the
context, downcasting cannot fail. In this case, using dynamic cast
gives you
nothing except less performance.

You could however have a separate function that uses dynamic cast in
debug
but reverts to static cast in release, except for cases where the
dynamic cast
is actually part of the design (such as type switches, but these
should be
avoided when possible anyways).
 
A

Andre Kostur

Comments inline:


I have a program which crashes when:

[snip.. the code is more interesting]
Any help much appreciated.

Thanks -

Dom

===========================================
#include <vector>
#include <set>
#include <iostream>

using std::vector;
using std::set;
using std::cout;

class A {
public:
A(int v) :
val(v) {}
int val;
};

class D : public A {
public:
D(int v) :
A(v) {}

mutable vector<int> vec;

void dfunc(void) const {
std::cout << "hello world " << val << "!\n";
// ********************************************
// Ok without this line, crashes with this line
vec.push_back(val);
// ********************************************
}
};

class ALess {
public :
bool operator() (const A& a1, const A& a2) const {
return a1.val < a2.val;
}
};

typedef set<A, ALess> ASet;
typedef ASet::iterator ASetIter;
typedef std::pair<ASetIter, bool> ASetInsRetVal;

int main() {
ASetInsRetVal retval;
ASet aset;

for(int i=0; i<4; i++) {
retval = aset.insert(D(i));

This will slice your temporary D object into an A object. ASet contains
objects of type A, and only type A.
if(!retval.second)
cout << "insert failed\n";
else {
const D *dptr = static_cast<const D*>(&(*retval.first));

Undefined behaviour. retval->first is an A object. Which you then
force the compiler to try to treat it like a D object...
dptr->dfunc(); // crashes - see above

.... particularly when you attempt to access members which only exist in
D objects.
 
A

acehreli

On 2008-07-02 21:13, Dom Jackson wrote:
Besides that, when casting from base to derived you should use
dynamic_cast and check if the cast succeeded (in which case the returned
pointer will be non-0). If you do that in your code you will notice that
you will always get a 0-pointer.

Of course the OP must have at least one virtual function in Base for
dynamic_cast to work.
A cast from pointer to base class to a
pointer to derived is only allowed if the object pointed to is of type
derived.
True.

You can always cast from pointer to derived to pointer to base using
static_cast, but when going the other way you shall use dynamic_cast.

static_cast is fine even for downcasting, as long as the target type
is correct. static_cast is for when the destination type is known at
compile time.

Did you mean that static_cast can not be used in polymorphic
hierarchies? If so, it's news to me and I don't have the standard
handy to check. :)

Ali
 
D

Dom Jackson

Thanks folks - I had no idea that you couldn't put a derived type in a
set of base types. I can't immediately see anything in Josuttis about
this. Can anyone tell me what the problem is here? Is this common to
all STL containers? I had rather naively assumed that an object of a
derived type actually was, to all intents and purposes, also a base
object, but that's obviously not the case.

Thanks -

Dom
 
A

acehreli

Thanks folks - I had no idea that you couldn't put a derived type in a
set of base types. I can't immediately see anything in Josuttis about
this. Can anyone tell me what the problem is here?

One practical problem is that in general a derived object may not fit
in space for a base object.
Is this common to
all STL containers?

It is not specific to containers; "slicing" can be demonstrated even
with this code:

Derived d;
Base b = d;

What happens is that the Base part of d is copied to b. The same thing
happens when you copy Derived objects into a vector said:
I had rather naively assumed that an object of a
derived type actually was, to all intents and purposes, also a base
object, but that's obviously not the case.

That is all true. As long as the object maintains its own identity. As
soon as you copy it to Base, it is sliced to its Base part.

In order to have a collection of polymorpic objects, you must use a
type that would prevent slicing the objects. These all work fine:

boost::ptr_vector<Base> v0;
vector<boost::shared_ptr<Base> > v0;

This works too, but not exception safe:

vector<Base *> v1;

Ali
 
J

James Kanze

"Shall" might be a bit strong here. There are cases where,
because of the context, downcasting cannot fail. In this case,
using dynamic cast gives you nothing except less performance.

It also gives you defined behavior in case you are mistaken.
You should use dynamic_cast until the profiler says otherwise,
even if you think you know that static_cast would be safe.
You could however have a separate function that uses dynamic
cast in debug but reverts to static cast in release,

You don't really mean to say that you deliver different code
than what you've tested, do you? Again, unless the profiler
says it's absolutely imperative, you deliver the same code,
compiled with the same options, as you use during development.
 
J

James Kanze

Of course the OP must have at least one virtual function in
Base for dynamic_cast to work.

Of course, if Base doesn't have at least one virtual function,
there's probably no point in deriving from it.
static_cast is fine even for downcasting, as long as the
target type is correct.

Which, of course, means that there is a distinct risk of an
error, resulting in undefined behavior. The choice is basically
whether you want undefined behavior or defined behavior in case
of an error.
static_cast is for when the destination type is known at
compile time.

Static_cast is for many things, but in the case of Base* to
Derived*, it should only be used when the profiler says you have
no other choice. (Practically, of course, there should be very,
very few Base* to Derived* conversions to begin with.)
Did you mean that static_cast can not be used in polymorphic
hierarchies? If so, it's news to me and I don't have the
standard handy to check. :)

It can only be used in limited ways: you can't convert from a
virtual base to a derived (pointer or reference), for example,
and you can't arbitrarily navigate in the hierarchy.
 
J

James Kanze

Thanks folks - I had no idea that you couldn't put a derived
type in a set of base types. I can't immediately see anything
in Josuttis about this.

What should he say? He certainly says that the containers
contain objects of type T. In C++, an object of type T is an
object of type T, and not an object of some other type.
Polymorphism only works through references and pointers.
Can anyone tell me what the problem is here?

There is no problem. In C++, containers contain objects, with
value semantics.
Is this common to all STL containers? I had rather naively
assumed that an object of a derived type actually was, to all
intents and purposes, also a base object, but that's obviously
not the case.

It's never the case. An object of a derived type is an object
of a derived type, and an object of a base type is an object of
a base type. Every object has one, and only one type. This is
a fundamental aspect of the C++ object model (and also the
object model of many other languages).

Written correctly, an object of a derived type can be used as an
object of a base type, but only if the "use" involves an
indirection (reference or pointer). The object still has a
specific type, which never changes (but a pointer or a
reference can designate different objects).
 
J

James Kanze

On Jul 2, 2:29 pm, Dom Jackson <[email protected]> wrote:

[...]
In order to have a collection of polymorpic objects, you must use a
type that would prevent slicing the objects. These all work fine:
boost::ptr_vector<Base> v0;
vector<boost::shared_ptr<Base> > v0;
This works too, but not exception safe:
vector<Base *> v1;

Don't confuse the issues. The last is perfectly exception safe.
The difference has nothing to do with exceptions, but rather
with semantics (and the cases where a container of shared_ptr
has appropriate semantics are fairly rare in most business or
industrial software).
 
G

Greg Herlihy

This will not work, std::set holds objects of the type given as a
parameter, in this case base-objects and not derived objects, the
derived objects will be sliced into base objects. So what you are doing
is trying to call a function which does not exist to manipulate a vector
that does not exist.

To solve your problem you could use a set of pointers to base instead of
a set of base (e.g. std::set<Base*> instead of std::set<Base>) and
create the objects using new (e.g. set.insert(new Derived());).

You can always cast from pointer to derived to pointer to base using
static_cast, but when going the other way you shall use dynamic_cast.

No, a dynamic_cast<> cannot be used in the original program to cast
from an A to a B pointer - because the A class is not a polymorphic
type.

Greg
 
D

Dom Jackson

Thanks everyone - I re-implemented my set as a collection of pointers
and it's all working now.

Dom
 

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,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top