downcast?

C

cronusf

Why doesn't the following throw an exception. It seems like you
should not be able to downcast from A to B since A is not a B.

#include <iostream>

using namespace std;

class A {
public:
virtual void first();
};

class B: public A {
public:
void first();
void second();
};

void A::first() {
cout << "A::first()" << endl;
}

void B::first() {
cout << "B::first()" << endl;
}

void B::second() {
cout << "B::second()" << endl;
}

int main() {
A* a = new A;
B* b = (B*)a;
b->second();
return 0;
}
 
R

raicuandi

Why doesn't the following throw an exception.  It seems like you
should not be able to downcast from A to B since A is not a B.

#include <iostream>

using namespace std;

class A {
public:
   virtual void first();

};

class B: public A {
public:
   void first();
   void second();

};

void A::first() {
   cout << "A::first()" << endl;

}

void B::first() {
   cout << "B::first()" << endl;

}

void B::second() {
   cout << "B::second()" << endl;

}

int main() {
   A* a = new A;
   B* b = (B*)a;
   b->second();
   return 0;

}

First, from base to derived (A to B) is called an "upcast", not a
"downcast".
Second, you should not do
B* b = (B*)a;
but instead use one of those fancy C++ casters, dynamic_cast I think,
but I never use them because I understand C :)
Probably something like: B* b = dynamic_cast<B*>(a);
And third, it doesn't crash, and actually works perfectly fine,
because the second() method does not use any instance data of a/b.
The "this" 'keyword' is just pushed on the stack as a hidden argument,
but since second() does not access 'this', it acts like a static
method.

If you would have done this:


class B: public A {
public:
void first();
void second();

int mInstanceData;
};

void B::second() {
cout << "B::second()" << mInstanceData << endl;
}

then the following should crash, yes:

int main() {
A* a = new A;
B* b = (B*)a;
b->second();
return 0;
}


Cheers!
-- raicuandi
 
R

raicuandi

Why doesn't the following throw an exception.  It seems like you
should not be able to downcast from A to B since A is not a B.

#include <iostream>

using namespace std;

class A {
public:
   virtual void first();

};

class B: public A {
public:
   void first();
   void second();

};

void A::first() {
   cout << "A::first()" << endl;

}

void B::first() {
   cout << "B::first()" << endl;

}

void B::second() {
   cout << "B::second()" << endl;

}

int main() {
   A* a = new A;
   B* b = (B*)a;
   b->second();
   return 0;

}

Btw, to continue, this means that nomather what value you have in the
pointer 'b', it would still work. Including NULL.

B* b = (B*)NULL;
b->second(); // works, because B::second does not access any instance
data
 
I

Ian Collins

Why doesn't the following throw an exception. It seems like you
should not be able to downcast from A to B since A is not a B.

#include <iostream>

using namespace std;

class A {
public:
virtual void first();
};

class B: public A {
public:
void first();
void second();
};

void A::first() {
cout << "A::first()" << endl;
}

void B::first() {
cout << "B::first()" << endl;
}

void B::second() {
cout << "B::second()" << endl;
}

int main() {
A* a = new A;
B* b = (B*)a;
b->second();
return 0;
}

You have undefined behaviour. a is not a B, so calling b->second() is
undefined.

You should use dynamic_cast to cast form an A* to a B*. In your example
above, dynamic_cast would fail and b would be NULL. If you were to try
and dynamic_cast to a reference, you would get a bad_cast exception:

B& b = dynamic_cast<B&>(*a);
 
J

James Kanze

First, from base to derived (A to B) is called an "upcast", not a
"downcast".

I've always heard it called a downcast. Maybe because in class
diagrams on paper, the base classes are usually at the top of
the page; I don't know. But I find it confusing as well.
Second, you should not do
B* b = (B*)a;
but instead use one of those fancy C++ casters, dynamic_cast I
think, but I never use them because I understand C :)

Definitely. What he's written here is a static_cast. Which
tells the compiler that he knows that a actually points to a B,
and that the compiler should take his word for it, and not
check.
Probably something like: B* b = dynamic_cast<B*>(a);

Exactly. Generally speaking, it's best to use dynamic_cast when
navigating within a hierarchy, and static_cast when converting
between numeric types (although frankly, I've really nothing
against the old C style casts there). And for the most part,
anything which requires reinterpret_cast (or a static_cast's
with a void* in them somewhere) or a const_cast is suspect.
And third, it doesn't crash, and actually works perfectly
fine, because the second() method does not use any instance
data of a/b.

It's undefined behavior, and anything can happen. Including
that the code appears to work.
The "this" 'keyword' is just pushed on the stack as a hidden
argument,

Again, this is unspecified (and certainly not the case with the
compilers I use on Sun Sparc).
but since second() does not access 'this', it acts like a
static method.

Or doesn't. It's undefined behavior. What you're describing is
a frequent result of common implementations, but it's nothing he
can count on.
If you would have done this:
class B: public A {
public:
void first();
void second();

int mInstanceData;
};
void B::second() {
cout << "B::second()" << mInstanceData << endl;
}
then the following should crash, yes:
int main() {
A* a = new A;
B* b = (B*)a;
b->second();
return 0;
}

Or not. It's also undefined behavior, and I wouldn't be
surprised if it didn't just output some random value, rather
than crashing.
 
J

James Kanze

On Jul 29, 12:26 pm, (e-mail address removed) wrote:

[...]
Btw, to continue, this means that nomather what value you have
in the pointer 'b', it would still work. Including NULL.
B* b = (B*)NULL;
b->second(); // works, because B::second does not access any instance
data

Again, undefined behavior. And in this case, I've actually used
a compiler which would crash with this code.
 
B

Bo Persson

Why doesn't the following throw an exception. It seems like you
should not be able to downcast from A to B since A is not a B.

#include <iostream>

using namespace std;

class A {
public:
virtual void first();
};

class B: public A {
public:
void first();
void second();
};

void A::first() {
cout << "A::first()" << endl;
}

void B::first() {
cout << "B::first()" << endl;
}

void B::second() {
cout << "B::second()" << endl;
}

int main() {
A* a = new A;
B* b = (B*)a;

This shows the danger of using C style casts. Effectively you are not
asking the compiler to do this, you are telling it: "Pointer a
actually points to an object of class B. Trust me, I know what I'm
doing!".

Like others have told you, using a dynamic_cast is asking the compiler
to do the cast, *if possible*.
b->second();
return 0;
}


Bo Persson
 
R

raicuandi

This shows the danger of using C style casts. Effectively you are not
asking the compiler to do this, you are telling it: "Pointer a
actually points to an object of class B. Trust me, I know what I'm
doing!".

Like others have told you, using a dynamic_cast is asking the compiler
to do the cast, *if possible*.


Bo Persson

The REAL problem with C style cast is that it requires the programmer
to *think*, and apply the mythical powers of "common sense" when
designing software, instead of relying on obscure things to fill that
gap. Like RTTI.
Trust me, I know what I'm doing!

I thought that's the whole point of writing C/C++ :) Or else why not
use Java, where it takes a screenful of code to read a line from a
file. But hey, you're safe there. Every other line is an exception
handler, to protect you from your own code. Brilliant!

Cheers!
-- raicuandi
 
J

James Kanze

<4b1312db-e94b-4083-a622-4f508e33a...@c65g2000hsa.googlegroups.com>, James
The problem with a C-style cast is that it can do a
reinterpret_cast too easily.

Yes. The problem is that you can't distinguish between the two.
But of course, as long as only numeric types are involved, the
only legal new style cast is static_cast, so it doesn't matter.
(int) foo could be a reinterpret_cast if foo is unexpectedly a
pointer type.

If you don't know whether foo is a pointer or an integral type,
your code isn't going to work. Regardless of the type of cast.
Unfortunately, even a functional-style cast is no better; int
(foo) will behave exactly the same. I'd really like a compiler
which warned when a functional-style cast was doing anything
beyond an implicit conversion.

You mean anytime you use one to create a temporary object, like
MyClass()?

Probably the reason why I often use C style casts for numeric
types is that I always put the parentheses around the argument,
so they really look about the same as a functional style cast
(e.g. "(unsigned int)( whatever )", as compared to "MyClass(
whatever )"). And while the standard considers all of these to
be casts, I (and I think many others) think of them more as the
creation of a temporary object: I find it hard to think of
"MyClass()" or "MyClass( a, b, c )" as a type conversion,
regardless of what the standard says. And in many cases, I
think of something like "(double)( someInt )" in the same way.
In the case of double, I could actually write "double(
someInt )", but this won't work if the typename is more
complex, e.g. unsigned char, or long long (probably the two most
frequent cases in my code).

What I'd like is for the compiler to warn anytime I use anything
but a new style cast, but only if a pointer or reference is
involved (on one side or the other).
 

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,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top