checked_cast Implementierung



  • Hallo Leute!

    Ich habe gestern Abend folgenden Usenet Thread in comp.lang.c++.moderated gefunden: Implementing check_cast

    Es kam keine Lösung heraus - also habe ich mich heute nacht hingesetzt und einen checked_cast implementiert (ich finde checked_cast besser als check_cast :))

    Soweit wie ich es sehe läuft es perfekt (gcc 3.2.3 und Comeau Online kompilieren fehlerfrei)
    VC++ läuft nicht, da ich noch nicht die musse hatte um die partiellen spezialierungen workarounds zu schreiben...

    /*****************************************************************************
     *  AUTHOR:      Toni Schornboeck <toni@schornboeck.net>
     *  (c) 2004 by Toni Schornboeck
     *			
     *  This library is free software; you can redistribute it and/or
     *  modify it under the terms of the GNU Lesser General Public
     *  License as published by the Free Software Foundation; either
     *  version 2.1 of the License, or (at your option) any later version.
     *
     *  This library is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     *  Lesser General Public License for more details.
     *
     *  You should have received a copy of the GNU Lesser General Public
     *  License along with this library; See the file COPYING. If not, write
     *  to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
     *  Boston, MA 02111-1307 USA
     *****************************************************************************/
    
    #include<iostream>
    #include<exception>
    #include<typeinfo>
    #include<cassert>
    
    //wenn definiert, dann wird dynamic_cast verwendet
    #define SAFE_CONVERSATION
    
    //wenn definiert, dann wird asserted, wenn nicht, dann gethrowt
    //#define DO_ASSERT
    
    namespace hidden
    {
    //workaround für T& matcht f(T&) und f(T const&) gleich gut
    //also machen wir folgendes: T const& kann nicht f(T&) matchen, also:
    //f(T&, LookUpHelper2 const&) und f(T const&, LookUpHelper const&)
    //der call f(T&, LookUpHelper2()) matcht zwar bei beiden - aber f(T&, LookUpHelper2 const&)
    //ist ein perfect match! und T const& kann nicht T& matchen, aber LookUpHelper2 ist ein
    //LookUpHelper -> matcht also
    struct LookUpHelper {};
    struct LookUpHelper2 : public hidden::LookUpHelper {};
    
    //IsPtr - partial specialization only
    template<typename T> struct IsPtr { enum { value = false }; };
    template<typename T> struct IsPtr<T*> { enum { value = true }; };
    }
    
    //bad_checked_cast fliegt bei einem falschen cast
    //von boost lexical_cast abgeschaut
    class bad_checked_cast : std::bad_cast
    {
    private:
      std::type_info const* from;
      std::type_info const* to;
    
    public:
      bad_checked_cast() : from(0), to(0) {}
    
      bad_checked_cast(std::type_info const& from, std::type_info const& to)
      : from(&from), to(&to)
      {}
    
      std::type_info const& source_type() const
      {
        assert(from);
        return *from;
      }
    
      std::type_info const& target_type() const
      {
        assert(to);
        return *to;
      }
    
      char const* what() const throw()
      {
        return "bad checked cast: source is not a target type";
      }
    };
    #ifdef DO_ASSERT
      #define BAD_CHECKED_CAST(from, to) assert(false)
    #else
      #define BAD_CHECKED_CAST(from, to) throw ::bad_checked_cast(typeid(from), typeid(to))
    #endif
    
    //implementierung des casts
    namespace hidden
    {
    template<typename T, typename X, bool isPtr>
    struct checked_cast_impl;
    
    //pointer variante
    template<typename T, typename X>
    struct checked_cast_impl<T, X, true>
    {
      static T cast(X& x, hidden::LookUpHelper2 const&)
      {
    #ifdef SAFE_CONVERSATION
        T t = dynamic_cast<T>(x);
    
        //cross cast checken
        if (t!=static_cast<T>(x)) BAD_CHECKED_CAST(x, T);
        return t;
    #else
        return static_cast<T>(x);
    #endif
      }
    
      static T cast(X const& x, hidden::LookUpHelper const&)
      {
    #ifdef SAFE_CONVERSATION
        T t = dynamic_cast<T>(x);
    
        //cross cast checken
        if (t!=static_cast<T>(x)) BAD_CHECKED_CAST(x, T);
        return t;
    #else
        return static_cast<T>(x);
    #endif
      }
    };
    
    template<typename T, typename X>
    struct checked_cast_impl<T, X, false>
    {
      static T cast(X& x, hidden::LookUpHelper2 const&)
      {
    #ifdef SAFE_CONVERSATION
        try
        {
          T t=dynamic_cast<T>(x);
    
          //cross cast checken
          if(&t!=&static_cast<T>(x)) throw std::bad_cast();
          return t;
        }
        catch(...)
        {
          BAD_CHECKED_CAST(x, T);
        }
    #else
        return static_cast<T>(x);
    #endif
      }
    
      static T cast(X const& x, hidden::LookUpHelper const&)
      {
    #ifdef SAFE_CONVERSATION
        try
        {
          T t=dynamic_cast<T>(x);
    
          //cross cast checken
          if(&t!=&static_cast<T>(x)) std::bad_cast();
          return t;
        }
        catch(...)
        {
          BAD_CHECKED_CAST(x, T);
        }
    #else
        return static_cast<T>(x);
    #endif
      }
    };
    
    }
    
    template<typename T, typename X>
    inline T checked_cast(X& x)
    {
      return hidden::checked_cast_impl<T, X, hidden::IsPtr<X>::value>::cast(x, hidden::LookUpHelper2());
    }
    
    class Base
    {
    public:
      Base() { std::cout<<"base()\n"; }
      Base(Base const&) { std::cout<<"base(base const&)\n"; }
      virtual ~Base(){std::cout<<"~base()\n";}
    };
    
    class Der : public Base {};
    
    int main()
    {
      try
      {
    
        Base const* pb = new Der;
        Der const* rd = checked_cast<Der const*>(pb);
        Base* px=new Base;
        Der* d = checked_cast<Der*>(px);
        Der const& r2=checked_cast<Der const&>(*pb);
        Der d2;
        Base& r3=d2;
        Der& d3=checked_cast<Der&>(r3);
        Base& r4=d2;
        Der const& d4=checked_cast<Der const&>(r4);
    
        Base* p=new Base;
        Der& r=checked_cast<Der&>(*p); //falsch! p ist ein Base
    
        delete p;
        delete pb;
        delete px;
      }
      catch(bad_checked_cast const& e)
      {
        std::cout<<e.what()<<'\n';
        std::cout<<"Source: "<<e.source_type().name()<<'\n';
        std::cout<<"Target: "<<e.target_type().name()<<'\n';
      }
      catch(std::exception const& e)
      {
        std::cout<<e.what()<<'\n';
      }
      catch(...)
      {
        std::cout<<"unknown exception\n";
      }
    }
    

    soweit ich sehe, funktioniert alles... habe ich etwas übersehen? oder gibt es vielleicht so einen checked_cast bereits?

    Für die die sich fragen wozu checked_cast:
    checked_cast ist im Prinzip ein static_cast - allerdings checkt er, ob die konvertierung auch wirklich geht. allerdings nur in der Debug version 🙂 dh, in der release version kann man sich RTTI sparen.



  • dein bsp main ist ja recht statisch, aber was passiert wenn user eingaben die auswahl der objekte beinflussen

    z.b.

    class Base {};
    class foo : public Base {};
    class bar : public Base {};
    
    int main()
    {
        Base * b;
        std::string user_eingabe;
        cin >> user_eingabe;
        if(user_eingabe == "foo")
            b = new foo;
        else
            b = new bar;
    
        foo * f = checked_cast<foo *>( b ); // duch die debug kommt ich locker wenn ich nur foo einegabe, aber was ist wenn
                                            // sich in der release version die gegeben bedinungen ändern
    }
    

    natürlich müsste man in den bsp auch die andern eingaben prüfen, aber was ist mit viel größeren programmen wo man nicht so leicht alles austesten kann (weil man keine zeit hatte für eine test unit 😉 )



  • Gerard schrieb:

    natürlich müsste man in den bsp auch die andern eingaben prüfen, aber was ist mit viel größeren programmen wo man nicht so leicht alles austesten kann (weil man keine zeit hatte für eine test unit 😉 )

    Naja, das ist ja auch nicht der Sinn von checked_cast.
    checked_cast soll es ja erlauben static_cast zu verwenden, aber dennoch auf der 'sicheren' Seite zu sein.

    Stell dir vor du hast einen Container von Base* - und du weisst genau dass alle Einträge aber in wirklichkeit Derived* sind. Dann kannst du einen static_cast verwenden, und sparst dir die RTTI.

    Zumindest in meinem Code kommt es öfter vor, dass wenn ich einen downcast mache - ich immer weiss welche Klasse ich da vor mir habe. Aber man sich eben nie wirklich sicher sein kann - und deshalb kommt ein dynamic_cast hin, weil jeder ja mal Fehler machen kann. Und hier kommt checked_cast ins Spiel - denn genau hier, wo wir ja genau wissen dass dieses Base* ein Derived* sein muss spielt checked_cast seine stärken aus. In der Debug Version kann man testen ob wirklich alles passt, und in der release version ist alles gut 🙂

    checked_cast kann natürlich nie dynamic_cast ablösen - man muss es eher als erweiterten static_cast sehen 🙂

    Siehe auch: assert_cast



  • Hallo,
    so wie ich das sehe hat deine Implementation ein Problem mit rvalues.
    Beispiel:

    Base& q();
    void f()
    {
        Derived* d1 = dynamic_cast<Derived*>(new Base()); // ok -> 0
        Derived* d2 = checked_cast<Derived*>(new Base()); // Fehler! Keine passende Überladung
    
        Derived d3 = dynamic_cast<Derived*>(&q()); // ok
        Derived d4 )= checked_cast<Derived*>(&q()); // Fehler! Keine passende Überladung
    }
    

Log in to reply