/* ** $Id: casts.cc 651 2007-03-31 18:34:20Z phf $ ** ** Here are some examples of how casts are done in C++. */ #include // Some basic example classes to start out with, both // B and C are derived from A. class A { public: A() { std::cout << "A::A()" << std::endl; } virtual ~A() { std::cout << "~A::A()" << std::endl; } }; class B: public A { public: B() { std::cout << "B::B()" << std::endl; } virtual ~B() { std::cout << "~B::B()" << std::endl; } // Note that B's test method is different from C's virtual bool test() { std::cout << "B::test()" << std::endl; return true; } }; class C: public A { public: C() { std::cout << "C::C()" << std::endl; } virtual ~C() { std::cout << "~C::C()" << std::endl; } // Note that C's test method is different from B's virtual double test() { std::cout << "C::test()" << std::endl; return 3.14; } }; int main() { // Some basic examples of how constructors and virtual // destructors work. You should check this out very // closely. :-) First two polymorphic assignments of // a subclass object to a superclass pointer. A *ab = new B(); A *ac = new C(); // Now we delete those objects again, check out which // destructors run and when!!! delete ac; delete ab; // As you can see (and have known for a while) we can // always assign an object of a subclass to a pointer // (or reference) of the superclass type. The idea is // that everything we can do with an A we can also do // with a B or a C instance, therefore it's "safe" to // allow the assignment. // However, what about the "other way"? Let's make an // B and C instance again: ab = new B(); ac = new C(); // We *know* that "ab" has a dynamic type of B even // though it has a static type of A. However, if we // want to assign a pointer of static type A to a // pointer of static type B, the compiler won't let // us. // B *ba = ab; // That's reasonable once you think about it. After // all, it *could* be that "ab" points to a C object // and there is no relationship between B and C objects // at all. So we can't allow the assignment based on // just the static types. What we need to do is "cast" // the pointer appropriately. If we do, we'll get a // warning though. C++ still allows C-style casts, but // they are not the preferred way of casting anymore. B *bb = (B*) ab; // If we print these two pointers, we see that they // denote the same object (albeit with different // static types): std::cout << ab << std::endl; std::cout << bb << std::endl; std::cout << "==========" << std::endl; // There are several problems with "old-style" casts, // for example that they are hard to find when you // search through a program in your editor. C++ // introduces a bunch of "new-style" casts, here's // an example: bb = dynamic_cast( ab ); // Much easier to "grep" for, and with the identical // effect in this case: std::cout << ab << std::endl; std::cout << bb << std::endl; std::cout << "==========" << std::endl; // That is, however, not the only advantage. Imagine // we try to cast a pointer with dynamic type C to a // pointer of static type B. Obviously that should // not really work since they have different methods // (the "test" method returns different types!). But // check this out: bb = (B*) ac; // Remember that "ac" points to a C instance, yet the // old-style cast succeeds and doesn't care about this // problem: std::cout << ac << std::endl; std::cout << bb << std::endl; std::cout << "==========" << std::endl; // What would happen if we call the test() method now? // It's not defined, anything can happen... :-/ std::cout << bb->test() << std::endl; std::cout << "==========" << std::endl; // On my system this prints "-1531925468" which is not // a good idea, is it? :-) However, if we had used the // new-style cast, we could have detected this problem: bb = dynamic_cast(ac); // Check out what happens now to the value of "bb": std::cout << ac << std::endl; std::cout << bb << std::endl; std::cout << "==========" << std::endl; // That's right! The dynamic_cast operator will return // NULL if the cast is not a good idea. In Java, you'd // get a ClassCastException instead, but in C++ you // have to check the result of a cast yourself: If it's // NULL, something was wrong about the cast. // The dynamic_cast operator checks the dynamic type, // or runtime type, of the object that's being cast. // There's also a static_cast operator that relies just // on the static type, or compile-time type. bb = static_cast(ac); // Check out what happens *now* to the value of "bb": std::cout << ac << std::endl; std::cout << bb << std::endl; std::cout << "==========" << std::endl; // Yep, the cast succeeds even though it shouldn't! So // in this case, static_cast is just as bad as the old // C-style cast, just easier to "grep" for. There are // also examples where static_cast fails though, but // those cases are detected at compile-time. Consider // this example: // ac = static_cast(bb); // The compiler complains that we can't convert from a // B* to a C* and that's of course true, it's not ever // possible that an object of type B could be used where // an object of type C is expected, they are unrelated! // So even trying to write down the cast doesn't make a // bit of sense. Compare this behaviour with old-style // casts: ac = (C*) bb; // We've seen this before (check above): The old-style // cast literally lets us do *any* conversion, even if // the conversion can't possibly make sense. Finally, // note that dynamic_cast let's us do the conversion // at compile-time: ac = dynamic_cast(bb); // The reason for this is a little complicated, but it // boils down to the fact that even for a conversion // that can't make sense, it's okay to *try* it: It's // going to fail anyway since dynamic_cast will return // a NULL pointer instead: std::cout << ac << std::endl; // There are a lot of different cast operators, you // should look all of them up and understand them: // // static_cast // dynamic_cast // const_cast // reinterpret_cast // // A good article on all of this can be found here: // // http://www.acm.org/crossroads/xrds3-1/ovp3-1.html // // Our textbook covers these as well. For most of the // applications we need I recommend dynamic_cast: It's // the right kind of cast of object-oriented programming, // just remember to check if you get back a NULL pointer. // For other places where you need a cast, I recommend // static_cast because it's easier to search for and it // does some extra checking at compile-type in certain // cases. In the end, the most important thing to keep // in mind: Whenever you need to cast anything, there's // a good chance that you didn't design your program // right to begin with. Be suspicious of every cast you // are writing down, make sure that it's really, really // necessary. // Sail away... return 0; }