Type casting

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Jacob Jensen

    Type casting

    This question has probably been asked a million time, but here it comes
    again. I want to learn the difference between the three type cast operators:
    static_cast, reinterpret_cas t, dynamic_cast. A good way to do this is by
    example. So I will give an example and please tell me what you think:

    I have a base
    class A
    with a virtual destructor, and a
    class B
    that is it inherits publicly from A and defines som extra stuff.

    If I have the following variables:

    A* pA;
    B* pB1 = new B();
    B* pB2;

    and the following assignment
    pA = pB1;

    which is most correct of the following
    pB2 = static_cast< B* > (pA);
    pB2 = reinterpret_cas t< B* > (pA);
    pB2 = dynamic_cast< B* > (pA);

    The last one works only if I have RTTI set.
    Why should I use one of them isntead of the other? Please could you explain
    this to me as if I were a 6 year old? I have an idea about it, but I am
    finding it difficult to understand that idea, if you know what I mean.

    And thank you very much

    Jacob


  • Alf P. Steinbach

    #2
    Re: Type casting

    * "Jacob Jensen" <jacob_news_dkN oSpaaaammmm@yah oo.co.uk> schriebt:[color=blue]
    >
    > I have a base
    > class A
    > with a virtual destructor, and a
    > class B
    > that is it inherits publicly from A and defines som extra stuff.
    >
    > If I have the following variables:
    >
    > A* pA;
    > B* pB1 = new B();
    > B* pB2;
    >
    > and the following assignment
    > pA = pB1;
    >
    > which is most correct of the following
    > pB2 = static_cast< B* > (pA); // 1
    > pB2 = reinterpret_cas t< B* > (pA); // 2
    > pB2 = dynamic_cast< B* > (pA); // 3[/color]

    reinterpret_cas t is just a way of telling the compiler to regard the bits
    as having some other type than the original, and that's dangerous. The bits
    may not even be meaningful when regarded as a value of the new type. So you
    have essentially no well-defined operations on the result, other than casting
    it back to the original type.

    In this context a reinterpret_cas t is meaningless and incorrect and dangerous,
    although it might seem to "work" with a given compiler.

    static_cast says to the compiler: change the type appropriately if there is
    a well-defined conversion to the new type -- just do it, I know it's safe.

    In this situation static_cast is appropriate because _you_ know it's safe.

    dynamic_cast is like a static_cast except you're saying: "I'm not quite sure
    whether this is safe, could you please do a runtime type check at this point".

    Since you do know that the cast is safe a dynamic_cast would be inappropriate,
    but not right out incorrect, here.

    Summing up, _in this situation_ the most appropriate choice from the three you
    have listed is static_cast.



    (Comment: a downcast can often be avoided by introducing a virtual method.)


    [color=blue]
    > The last one works only if I have RTTI set.[/color]

    No, it works with any conforming C++ compiler.

    However, some C++ compilers, e.g. Microsoft's Visual C++, can be configured to
    act like non-C++ compilers.

    And in fact, Microsoft's Visual C++ compiler does not act like a C++ compiler
    by default; it must be configured to act like one (options /GX and /GR to support
    exceptions and RTTI, plus possibly esoteric linker option to support standard
    'main').

    Comment

    • John Harrison

      #3
      Re: Type casting

      >[color=blue]
      > dynamic_cast is like a static_cast except you're saying: "I'm not quite[/color]
      sure[color=blue]
      > whether this is safe, could you please do a runtime type check at this[/color]
      point".[color=blue]
      >
      > Since you do know that the cast is safe a dynamic_cast would be[/color]
      inappropriate,[color=blue]
      > but not right out incorrect, here.
      >[/color]

      Just a minor point to add, which the OP may not know.

      The way that dynamic_cast says that a cast is unsafe is by returning 0 (or
      NULL) in the case of pointers and by throwing an exception (std::bad_cast I
      think) in the case of references.

      john


      Comment

      • Jacob Jensen

        #4
        Re: Type casting

        > reinterpret_cas t is just a way of telling the compiler to regard the bits[color=blue]
        > as having some other type than the original, and that's dangerous. The[/color]
        bits[color=blue]
        > may not even be meaningful when regarded as a value of the new type. So[/color]
        you[color=blue]
        > have essentially no well-defined operations on the result, other than[/color]
        casting[color=blue]
        > it back to the original type.[/color]

        Is it because a pointer to a type A may have a different byte representation
        that a pointer to a type B?
        But as you say, I will have "no well-defined operations on the result, other
        than casting it back to the original type".
        But is that not what I am doing? I am casting it back to the type it
        originally was/is.
        So if that is correct, then please tell me why I should not use it instead
        of using static_cast (Note: Only tell me if my statement of casting it to
        its original type is the correct)?
        And when do I use reinterpret_cas t then?

        [color=blue]
        > dynamic_cast is like a static_cast except you're saying: "I'm not quite[/color]
        sure[color=blue]
        > whether this is safe, could you please do a runtime type check at this[/color]
        point".

        Do I understand you correctly concerning the dynamic_cast thingy: There are
        some cases when I do not know what type of pointer I have. And in that case,
        I can try to cast it, and see if it gives a NULL pointer. That is my code
        MUST handle the case where the cast is not valid. Am I right?
        [color=blue]
        > (Comment: a downcast can often be avoided by introducing a virtual[/color]
        method.)

        I would be interested in seeing a simple example of this. I need the above
        downcast, because I have some API functions which I have to use that only
        take the base class pointer as argument. So I have to upcast my pointer to
        call the function ( func1() ), and then downcast it in a function that is
        called by func1(), which is a function I have defined.
        [color=blue][color=green]
        > > The last one works only if I have RTTI set.[/color]
        >
        > No, it works with any conforming C++ compiler.
        >
        > However, some C++ compilers, e.g. Microsoft's Visual C++, can be[/color]
        configured to[color=blue]
        > act like non-C++ compilers.[/color]

        Do you mean that all standard C++ compilers should have RTTI set?

        Thank you again for your time. I really appreciate it.


        Comment

        • Alf P. Steinbach

          #5
          Re: Type casting

          * "Jacob Jensen" <jacob_news_dkN oSpaaaammmm@yah oo.co.uk> schriebt:[color=blue]
          > * Alf wrote:[color=green]
          > >
          > > reinterpret_cas t is just a way of telling the compiler to regard the bits
          > > as having some other type than the original, and that's dangerous. The
          > > bits may not even be meaningful when regarded as a value of the new type.
          > > So you have essentially no well-defined operations on the result, other than
          > > casting it back to the original type.[/color]
          >
          > Is it because a pointer to a type A may have a different byte [you mean bit]
          > representation that a pointer to a type B?[/color]

          It may in general, yes.

          The standard lays out the rules in ยง5.2.10, with slightly different details
          for different pointer types (pointer to function, to object, to member function),
          but in general the only well-defined operation is to reinterpret_cas t back to
          the original type, in which case you get the original value back.

          Exception: reinterpret_cas t of null-pointer yields a valid null-pointer.

          In your specific example I can't see any way the bits could be different,
          but the standard is conservative: you can't use the result of a
          reinterpret_cas t for anything other than casting it back -- unless you
          use knowledge of a particular compiler and system.

          An example where it's obvious that different bit-level values must be
          involved is when you have

          class A{ ... };
          class Middle1: public A { ... };
          class Middle2: public A { ... };
          class B: public Middle1, public Middle2 { ... };

          Here a B object contains two different A subobjects. Casting using
          static_cast from B* to Middle1* to A* gets you a pointer to one of them,
          casting using static_cast from B* to Middle2* to A* gets you a pointer to
          the other, and these two pointers can't very well be the same bit pattern,
          for if they were then they would refer to the same A subobject.

          In above paragraph I use 'casting' and 'static_cast' even though no actual
          cast notation need be involved, since it's pure upcasting. But without
          cast notation what you get _is_ a static_cast. It goes like:



          #include <iostream>

          struct A{ int ahum; };
          struct Middle1: A { int m1; };
          struct Middle2: A { int m2; };
          struct B: Middle1, Middle2 { int bah; };

          int main()
          {
          B object;
          B* b = &object;
          Middle1* m1 = b;
          Middle2* m2 = b;
          A* a1 = m1;
          A* a2 = m2;

          std::cout
          << a1 << "\n" // Some pointer value.
          << a2 << "\n" // Some different pointer value.
          << std::flush;

          // Try to go directly from an A subobject to the B object.
          // Not accepted, the compiler doesn't know _which_ A subobject.
          // Not knowing where the pointer points it can't adjust it.
          //
          // B* bummer = static_cast<B*> ( a1 ); // Nope.

          // Go via the relevant subobject that _you_ know.
          B* better = static_cast<B*> ( static_cast<Mid dle2*>( a2 ) );

          std::cout
          << b << "\n" // Some pointer value.
          << better << "\n" // The same pointer value.
          << std::flush;
          }


          [color=blue]
          > But as you say, I will have "no well-defined operations on the result, other
          > than casting it back to the original type".
          > But is that not what I am doing? I am casting it back to the type it
          > originally was/is.[/color]

          In your particular case: yes in practice no in standard. In general just no.

          The reason is that the innocent upcasts are static_cast's, which in general
          may change the pointer value (bitpattern).

          A bitpattern-preserving (and not even that much is guaranteed!) reinterpret_cas t
          is then not guaranteed to get back the original value.


          [color=blue]
          > So if that is correct, then please tell me why I should not use it instead
          > of using static_cast (Note: Only tell me if my statement of casting it to
          > its original type is the correct)?[/color]

          OK.

          [color=blue]
          > And when do I use reinterpret_cas t then?[/color]

          Only in very low-level, platform-specific code.



          [color=blue][color=green]
          > > dynamic_cast is like a static_cast except you're saying: "I'm not quite
          > > sure whether this is safe, could you please do a runtime type check at
          > > this point".[/color]
          >
          > Do I understand you correctly concerning the dynamic_cast thingy: There are
          > some cases when I do not know what type of pointer I have. And in that case,
          > I can try to cast it, and see if it gives a NULL pointer. That is my code
          > MUST handle the case where the cast is not valid. Am I right?[/color]

          Yep. Except as John Harrison noted, if you're casting to reference instead
          of to pointer what you get as ungood-signal is an exception, not NULL. With
          casting to pointer you get NULL.

          [color=blue][color=green]
          > > (Comment: a downcast can often be avoided by introducing a virtual[/color]
          > method.)
          >
          > I would be interested in seeing a simple example of this. I need the above
          > downcast, because I have some API functions which I have to use that only
          > take the base class pointer as argument. So I have to upcast my pointer to
          > call the function ( func1() ), and then downcast it in a function that is
          > called by func1(), which is a function I have defined.[/color]

          Off the cuff,

          struct A{ virtual void something(){} };
          struct B{ virtual void something(){ ... B stuff ... } };

          void calledFunc( A* pObj ){ pObj->something(); } // Does B stuff.

          void func1( A* pObj ){ calledFunc( pObj ); }

          int main()
          {
          B obj;
          func1( &obj );
          }

          (Actually the best explanation I know of for virtual functions is as a
          solution to avoid downcasts in e.g. an arithmetic expression evaluator.
          But that's a digression. It's a shame that example isn't wider known.)

          If the above doesn't seem possible there are more advanced versions of the
          same basic idea -- look up the "visitor pattern".

          And if that doesn't seem possible perhaps some redesign can do the trick.

          In general you should never need downcasts except when taking objects out
          of polymorphic containers (that is, containers containing a bunch of
          objects of different but related types).



          [color=blue][color=green][color=darkred]
          > > > The last one works only if I have RTTI set.[/color]
          > >
          > > No, it works with any conforming C++ compiler.
          > >
          > > However, some C++ compilers, e.g. Microsoft's Visual C++, can be
          > > configured to act like non-C++ compilers.[/color]
          >
          > Do you mean that all standard C++ compilers should have RTTI set?[/color]

          Yes.

          All C++ compilers must support RTTI and compile RTTI-based code
          correctly, since RTTI is part of the language defined by the standard.

          Without RTTI (or exceptions, or 'main' ;-) ) you have only a subset of C++.

          --
          A: Because it messes up the order in which people normally read text.
          Q: Why is top-posting such a bad thing?
          A: Top-posting.
          Q: What is the most annoying thing on usenet and in e-mail?

          Comment

          Working...