[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

[Q] Strange dynamic_cast problem

Eric

11/4/2008 4:58:00 PM

Ok...this seems to be treading into some really esoteric areas of c++.
I will do my best to explain this issue, but I don't fully understand
what is happening myself. I am hoping that the problem comes down to
standard c++ stuff and is not specific to Mac OS X compiler&linker.

I have put together a simple test project which can be found at:

http://ericgorr.net/LibraryL...

which demonstrates the problem.

In Shared.h, there are the definitions of two purely virtual classes -
A & B. B is a subclass of A.

In LibraryA, there is a implementation of class B called BImp. There
is a function called GetA which returns a pointer to an instance of
BImp and returns an A*.

In LibraryB, there is a function called test. This function takes a
void *, which will end up being a function pointer to the GetA
function from LibraryA.

The problem comes in the test function with the line:

B* myB = dynamic_cast<B*> (myA);

The dynamic_cast fails and myB is assigned NULL. This _should not_
fail because myA is an instance of class B.

However, I can make this dynamic_cast succeed, if in main.cp, which is
a part of the Application target, I set:

#define CASE_A 1

which allows

A *myA = functionPtr();
B *myB = dynamic_cast<B*> (myA);

to be executed before the test function from LibraryB is called.

Any idea why this allows it to work?
Any idea why it is failing in the first place?

In main.cp, there are two #define's.

#define CASE_A 0
#define CASE_B 0

If both are set to zero, it will crash. This is simplest crashing
case.
If CASE_A is 1, it will work. Any idea why?
If CASE_B is 1, it will crash and this is the case closest to the real
case I am working with.



Since I started composing this message, I've been looking into the
issue a bit more and thought that if it were possible to make the
destructors pure virtual functions as well, then that would solve the
problem.

Googling for "pure virtual destructors c++", I came across:

http://www.linuxtopia.org/online_books/programming_books/thinking_in_c++/Chapter1...

While it seems strange, apparently this is allowed in C++.

So, if I changed Shared.h to look like:

*****
*****
#define VISIBLE __attribute__ ((visibility("default")))

class VISIBLE A
{
public:
virtual ~A( void ) = 0;
virtual void Func( void ) = 0;
};
A::~A() {}


class VISIBLE B : public A
{
public:
virtual ~B( void ) = 0;
virtual void Func1( void ) = 0;
};
B::~B() {}


extern "C" VISIBLE A *GetA( void );
*****
*****

everything worked in all three cases.

Any comments on this solution? Any reason why this wouldn't be
perfectly valid?

Any thoughts or comments would be appreciated.



19 Answers

sean_in_raleigh

11/4/2008 7:18:00 PM

0


You're more likely to get a good response
if you post a short, compilable example
that exhibits the problem.

Make it easy for us to help you!

Sean

Eric

11/4/2008 7:31:00 PM

0

On Nov 4, 2:18 pm, sean_in_rale...@yahoo.com wrote:
> You're more likely to get a good response
> if you post a short, compilable example
> that exhibits the problem.
>
> Make it easy for us to help you!

This is what:

http://ericgorr.net/LibraryL...

is supposed to be. I realize that it is Mac specific and that this
will limit the number of people who will be able to help, but there
really isn't much code there at all if you wanted to take a look.

In a different forum, someone pointed out that:

-----
All the C++ specification specifies is what happens in a set of C++
source modules, all linked together to form one application. Nowhere
is it discussed what happens if you have DLL's, shared libraries, or
anything else with respect to the language.
-----

which does actually help. In the 'real' case, this is working just
fine within Visual Studio.

If how this works is basically undefined, there will not be a 'right'
solution - or the 'right' solution will be platform specific. In which
case, I will need to deal exclusively with Apple and their forums.

Eric

11/4/2008 7:31:00 PM

0

On Nov 4, 2:18 pm, sean_in_rale...@yahoo.com wrote:
> You're more likely to get a good response
> if you post a short, compilable example
> that exhibits the problem.
>
> Make it easy for us to help you!

This is what:

http://ericgorr.net/LibraryL...

is supposed to be. I realize that it is Mac specific and that this
will limit the number of people who will be able to help, but there
really isn't much code there at all if you wanted to take a look.

In a different forum, someone pointed out that:

-----
All the C++ specification specifies is what happens in a set of C++
source modules, all linked together to form one application. Nowhere
is it discussed what happens if you have DLL's, shared libraries, or
anything else with respect to the language.
-----

which does actually help. In the 'real' case, this is working just
fine within Visual Studio.

If how this works is basically undefined, there will not be a 'right'
solution - or the 'right' solution will be platform specific. In which
case, I will need to deal exclusively with Apple and their forums.

Salt_Peter

11/4/2008 7:40:00 PM

0

On Nov 4, 11:58 am, Eric <ericg...@gmail.com> wrote:
> Ok...this seems to be treading into some really esoteric areas of c++.
> I will do my best to explain this issue, but I don't fully understand
> what is happening myself. I am hoping that the problem comes down to
> standard c++ stuff and is not specific to Mac OS X compiler&linker.
>
> I have put together a simple test project which can be found at:
>
>  http://ericgorr.net/LibraryL...
>
> which demonstrates the problem.
>
> In Shared.h, there are the definitions of two purely virtual classes -
> A & B. B is a subclass of A.
>
> In LibraryA, there is a implementation of class B called BImp. There
> is a function called GetA which returns a pointer to an instance of
> BImp and returns an A*.
>
> In LibraryB, there is a function called test. This function takes a
> void *, which will end up being a function pointer to the GetA
> function from LibraryA.
>
> The problem comes in the test function with the line:
>
>         B* myB = dynamic_cast<B*> (myA);
>
> The dynamic_cast fails and myB is assigned NULL. This _should not_
> fail because myA is an instance of class B.

prove it:

#include <iostream>
#include <typeinfo>

....
// should print a mangled representation of class B
std::cout << typeid(myA).name() << std::endl;


>
> However, I can make this dynamic_cast succeed, if in main.cp, which is
> a part of the Application target, I set:
>
> #define CASE_A 1
>
> which allows
>
>                 A *myA = functionPtr();
>                 B *myB = dynamic_cast<B*> (myA);
>
> to be executed before the test function from LibraryB is called.
>
> Any idea why this allows it to work?
> Any idea why it is failing in the first place?
>
> In main.cp, there are two #define's.
>
> #define CASE_A 0
> #define CASE_B 0
>
> If both are set to zero, it will crash. This is simplest crashing
> case.
> If CASE_A is 1, it will work. Any idea why?
> If CASE_B is 1, it will crash and this is the case closest to the real
> case I am working with.
>
> Since I started composing this message, I've been looking into the
> issue a bit more and thought that if it were possible to make the
> destructors pure virtual functions as well, then that would solve the
> problem.
>
> Googling for "pure virtual destructors c++", I came across:
>
> http://www.linuxtopia.org/online_books/programming_books/th......
>
> While it seems strange, apparently this is allowed in C++.
>
> So, if I changed Shared.h to look like:
>
> *****
> *****
> #define VISIBLE __attribute__ ((visibility("default")))
>
> class VISIBLE A
> {
> public:
>     virtual ~A( void )          = 0;
>     virtual void Func( void )   = 0;};
>
> A::~A() {}
>
> class VISIBLE B : public A
> {
> public:
>     virtual ~B( void )          = 0;
>     virtual void Func1( void )  = 0;};
>
> B::~B() {}
>
> extern "C" VISIBLE A *GetA( void );
> *****
> *****
>
> everything worked in all three cases.
>
> Any comments on this solution? Any reason why this wouldn't be
> perfectly valid?
>
> Any thoughts or comments would be appreciated.

Whether the destructor is virtual or not won't matter, unless that
d~tor were the only function available. All you need is one virtual
function in a base class.

A dynamic_cast will fail if:

a) the types involved are not polymorphic: base has at least one
virtual member function
b) the compiler is not configured to store RTTI (Real Time Type
Information)
c) the object *at* the dynamic_cast's arguement is a Parent type of
the lhv.

Base base;
Base* p_base = &base;
Derived* p_d = dynamic_cast< Derived* >(p_base); // failure

So the following should run with no errors:

#include <iostream>
#include <stdexcept>

class Base
{
virtual void test() {}
};

class Derived: public Base
{
void test() {}
};

int main ()
{
try
{
Derived derived;
Base* p_base = &derived;
Derived* p_der = dynamic_cast< Derived* >(p_base);
if (p_der == 0)
throw std::runtime_error("failed dynamic_cast< Derived* >");
}
catch(const std::exception& e)
{
std::cout << "Error: ";
std::cout << e.what() << std::endl;
}
}

No need for a virtual d~tor since i'm not allocating and deallocating
manually using Base* anywhere. I say your problem is b), but thats a
dice roll.

Eric

11/4/2008 8:40:00 PM

0

On Nov 4, 2:40 pm, Salt_Peter <pj_h...@yahoo.com> wrote:
> On Nov 4, 11:58 am, Eric <ericg...@gmail.com> wrote:
>
>
>
> > Ok...this seems to be treading into some really esoteric areas of c++.
> > I will do my best to explain this issue, but I don't fully understand
> > what is happening myself. I am hoping that the problem comes down to
> > standard c++ stuff and is not specific to Mac OS X compiler&linker.
>
> > I have put together a simple test project which can be found at:
>
> >  http://ericgorr.net/LibraryL...
>
> > which demonstrates the problem.
>
> > In Shared.h, there are the definitions of two purely virtual classes -
> > A & B. B is a subclass of A.
>
> > In LibraryA, there is a implementation of class B called BImp. There
> > is a function called GetA which returns a pointer to an instance of
> > BImp and returns an A*.
>
> > In LibraryB, there is a function called test. This function takes a
> > void *, which will end up being a function pointer to the GetA
> > function from LibraryA.
>
> > The problem comes in the test function with the line:
>
> >         B* myB = dynamic_cast<B*> (myA);
>
> > The dynamic_cast fails and myB is assigned NULL. This _should not_
> > fail because myA is an instance of class B.
>
> prove it:
>
> #include <iostream>
> #include <typeinfo>
>
> ...
> // should print a mangled representation of class B
> std::cout << typeid(myA).name() << std::endl;

The getMyA function only has a single line of code:

return new BImp;

and BImp is subclass of B which is a subclass of A.

Of course, typeid(myA).name() does not print a mangled representation
of class B (it prints 'P1A'), but this is the problem I am trying to
solve.

I don't know why (for certain) this does not work. Looking at the code
alone and stepping through it with the debugger, it is trivial to see
that it should be working. But when the dynamic_cast it hit, it fails.

I am beginning to think this is an OS specific issue as I mentioned in
a reply to sean.


> A dynamic_cast will fail if:
>
> b) the compiler is not configured to store RTTI (Real Time Type
> Information)

> I say your problem is b), but thats a
> dice roll.

RTTI is configured properly.

thank you for your comments.

Eric

11/4/2008 8:40:00 PM

0

On Nov 4, 2:40 pm, Salt_Peter <pj_h...@yahoo.com> wrote:
> On Nov 4, 11:58 am, Eric <ericg...@gmail.com> wrote:
>
>
>
> > Ok...this seems to be treading into some really esoteric areas of c++.
> > I will do my best to explain this issue, but I don't fully understand
> > what is happening myself. I am hoping that the problem comes down to
> > standard c++ stuff and is not specific to Mac OS X compiler&linker.
>
> > I have put together a simple test project which can be found at:
>
> >  http://ericgorr.net/LibraryL...
>
> > which demonstrates the problem.
>
> > In Shared.h, there are the definitions of two purely virtual classes -
> > A & B. B is a subclass of A.
>
> > In LibraryA, there is a implementation of class B called BImp. There
> > is a function called GetA which returns a pointer to an instance of
> > BImp and returns an A*.
>
> > In LibraryB, there is a function called test. This function takes a
> > void *, which will end up being a function pointer to the GetA
> > function from LibraryA.
>
> > The problem comes in the test function with the line:
>
> >         B* myB = dynamic_cast<B*> (myA);
>
> > The dynamic_cast fails and myB is assigned NULL. This _should not_
> > fail because myA is an instance of class B.
>
> prove it:
>
> #include <iostream>
> #include <typeinfo>
>
> ...
> // should print a mangled representation of class B
> std::cout << typeid(myA).name() << std::endl;

The getMyA function only has a single line of code:

return new BImp;

and BImp is subclass of B which is a subclass of A.

Of course, typeid(myA).name() does not print a mangled representation
of class B (it prints 'P1A'), but this is the problem I am trying to
solve.

I don't know why (for certain) this does not work. Looking at the code
alone and stepping through it with the debugger, it is trivial to see
that it should be working. But when the dynamic_cast it hit, it fails.

I am beginning to think this is an OS specific issue as I mentioned in
a reply to sean.


> A dynamic_cast will fail if:
>
> b) the compiler is not configured to store RTTI (Real Time Type
> Information)

> I say your problem is b), but thats a
> dice roll.

RTTI is configured properly.

thank you for your comments.

john

11/4/2008 9:43:00 PM

0

Eric wrote
> In LibraryB, there is a function called test. This function takes a
> void *, which will end up being a function pointer to the GetA
> function from LibraryA.
>
> The problem comes in the test function with the line:
>
> B* myB = dynamic_cast<B*> (myA);
>
> The dynamic_cast fails and myB is assigned NULL. This _should not_
> fail because myA is an instance of class B.

A lot of people make the mistake of casting in and out of void* using
different types. This will cause UB. The reason being can be seen in
4.10.2:

The result of converting a "pointer to cv T" to a "pointer to cv void"
points to the start of the storage location where the object of type T
resides, as if the object is a most derived object (1.8) of type T (that
is, not a base class subobject).

In other words, if you cast a B* to a void*, then cast that void* to an
A* (or visa-versa) you're going to have serious problems. The A* will
now, possibly incorrectly, point to a location in memory that is a B*
without the correct interpretation step that would normally transpire in
a static_cast. The result of accessing this pointer in any way is then UB.

If you're using MI you are GOING to have problems here. Otherwise you
may or may not.

I solve this problem in our shop by disallowing the passing of variables
through a void* without a type-safe wrapper like boost::any. This makes
sure that the pointers on both sides of the cast will be the exact same
type, as they must be to avoid UB.

Eric

11/4/2008 9:47:00 PM

0

On Nov 4, 4:42 pm, Noah Roberts <u...@example.net> wrote:
> Eric wrote
>
> > In LibraryB, there is a function called test. This function takes a
> > void *, which will end up being a function pointer to the GetA
> > function from LibraryA.
>
> > The problem comes in the test function with the line:
>
> >    B* myB = dynamic_cast<B*> (myA);
>
> > The dynamic_cast fails and myB is assigned NULL. This _should not_
> > fail because myA is an instance of class B.
>
> A lot of people make the mistake of casting in and out of void* using
> different types.  This will cause UB.  The reason being can be seen in
> 4.10.2:
>
> The result of converting a "pointer to cv T" to a "pointer to cv void"
> points to the start of the storage location where the object of type T
> resides, as if the object is a most derived object (1.8) of type T (that
> is, not a base class subobject).
>
> In other words, if you cast a B* to a void*, then cast that void* to an
> A* (or visa-versa) you're going to have serious problems.  The A* will
> now, possibly incorrectly, point to a location in memory that is a B*
> without the correct interpretation step that would normally transpire in
> a static_cast.  The result of accessing this pointer in any way is then UB.
>
> If you're using MI you are GOING to have problems here.  Otherwise you
> may or may not.
>
> I solve this problem in our shop by disallowing the passing of variables
> through a void* without a type-safe wrapper like boost::any.  This makes
> sure that the pointers on both sides of the cast will be the exact same
> type, as they must be to avoid UB.

Just to be clear, none of the variables are ever passing through a
void*.

The only use of a void* in the sample code is to pass around a pointer
to a function. In every case, the function is always successfully
passed and called.

Again, it is unlikely the usage of the void* in the code is the cause
of this problem.

Any other suggestions people have are always appreciated.

Eric

11/4/2008 9:47:00 PM

0

On Nov 4, 4:42 pm, Noah Roberts <u...@example.net> wrote:
> Eric wrote
>
> > In LibraryB, there is a function called test. This function takes a
> > void *, which will end up being a function pointer to the GetA
> > function from LibraryA.
>
> > The problem comes in the test function with the line:
>
> >    B* myB = dynamic_cast<B*> (myA);
>
> > The dynamic_cast fails and myB is assigned NULL. This _should not_
> > fail because myA is an instance of class B.
>
> A lot of people make the mistake of casting in and out of void* using
> different types.  This will cause UB.  The reason being can be seen in
> 4.10.2:
>
> The result of converting a "pointer to cv T" to a "pointer to cv void"
> points to the start of the storage location where the object of type T
> resides, as if the object is a most derived object (1.8) of type T (that
> is, not a base class subobject).
>
> In other words, if you cast a B* to a void*, then cast that void* to an
> A* (or visa-versa) you're going to have serious problems.  The A* will
> now, possibly incorrectly, point to a location in memory that is a B*
> without the correct interpretation step that would normally transpire in
> a static_cast.  The result of accessing this pointer in any way is then UB.
>
> If you're using MI you are GOING to have problems here.  Otherwise you
> may or may not.
>
> I solve this problem in our shop by disallowing the passing of variables
> through a void* without a type-safe wrapper like boost::any.  This makes
> sure that the pointers on both sides of the cast will be the exact same
> type, as they must be to avoid UB.

Just to be clear, none of the variables are ever passing through a
void*.

The only use of a void* in the sample code is to pass around a pointer
to a function. In every case, the function is always successfully
passed and called.

Again, it is unlikely the usage of the void* in the code is the cause
of this problem.

Any other suggestions people have are always appreciated.

Salt_Peter

11/4/2008 10:01:00 PM

0

On Nov 4, 3:40 pm, Eric <ericg...@gmail.com> wrote:
> On Nov 4, 2:40 pm, Salt_Peter <pj_h...@yahoo.com> wrote:
>
>
>
>
>
> > On Nov 4, 11:58 am, Eric <ericg...@gmail.com> wrote:
>
> > > Ok...this seems to be treading into some really esoteric areas of c++.
> > > I will do my best to explain this issue, but I don't fully understand
> > > what is happening myself. I am hoping that the problem comes down to
> > > standard c++ stuff and is not specific to Mac OS X compiler&linker.
>
> > > I have put together a simple test project which can be found at:
>
> > >  http://ericgorr.net/LibraryL...
>
> > > which demonstrates the problem.
>
> > > In Shared.h, there are the definitions of two purely virtual classes -
> > > A & B. B is a subclass of A.
>
> > > In LibraryA, there is a implementation of class B called BImp. There
> > > is a function called GetA which returns a pointer to an instance of
> > > BImp and returns an A*.
>
> > > In LibraryB, there is a function called test. This function takes a
> > > void *, which will end up being a function pointer to the GetA
> > > function from LibraryA.
>
> > > The problem comes in the test function with the line:
>
> > >         B* myB = dynamic_cast<B*> (myA);
>
> > > The dynamic_cast fails and myB is assigned NULL. This _should not_
> > > fail because myA is an instance of class B.
>
> > prove it:
>
> > #include <iostream>
> > #include <typeinfo>
>
> > ...
> > // should print a mangled representation of class B
> > std::cout << typeid(myA).name() << std::endl;
>
> The getMyA function only has a single line of code:
>
>   return new BImp;
>
> and BImp is subclass of B which is a subclass of A.
>
> Of course, typeid(myA).name() does not print a mangled representation
> of class B (it prints 'P1A'), but this is the problem I am trying to
> solve.
>
> I don't know why (for certain) this does not work. Looking at the code
> alone and stepping through it with the debugger, it is trivial to see
> that it should be working. But when the dynamic_cast it hit, it fails.
>
> I am beginning to think this is an OS specific issue as I mentioned in
> a reply to sean.
>
> > A dynamic_cast will fail if:
>
> > b) the compiler is not configured to store RTTI (Real Time Type
> > Information)
> > I say your problem is b), but thats a
> > dice roll.
>
> RTTI is configured properly.
>

Ah, so...

1) you are manually allocating and deallocating with new/delete so
virtual destructors are required in your base class. Not doing so
equates to a memory leak unless you use certain smart_pointers.

2) You stated:

[ The dynamic_cast fails and myB is assigned NULL. This _should not_
fail because myA is an instance of class B. ]

therefore myA is a pointer to an instance of B, should be

typeid( *myA ).name();

and you should be getting a B then.
If you still need help with this issue, construct a short, simple
program:

##include <iostream>
#include <typeinfo>

class A
{
public:
virtual ~A() { std::cout << "~A()\n"; }
};

class B : public A { };

int main ()
{
A* p_a = new B;
std::cout << typeid( *p_a ).name() << std::endl;
delete p_a;

std::cout << "Press ENTER to EXIT.\n";
std::cin.get();
}

/*
1B // type B as expected
~A()
*/

If you make A's destructor non-virtual, you have a memory leak, part
of your BImp doesn't get zapped on delete (test it here, ~A() would
not get invoked).

I never,ever use new/delete unless no other solution is available, in
fact i hate naked pointers like the plague, prefer references.
Something like boost::shared_ptr or even std::auto_ptr is a far better
solution. Or you can store elements in a container like std::vector<>
(instead of new overhere and then delete overthere).

My 3 cents