Eigenen Datentyp definieren



  • Hallo, ich habe ein Funktion mit einem Parameter x. x ist ein unsigned integer mit einer Schranke S(z.B. S=64).

    Statt jetzt sowas zu schreiben:

    void Funktion(unsigned int x) {
       _ASSERT(x<=S); //Sicherstellen das die Schranke eingehalten wird
       x=sqrt(x); //Irgendwas mit x machen
    }
    

    möchte ich das man garnicht erst ein Falsches x eingeben kann/sollte.

    Ich habe mir gedacht ich "definiere mir einen eigenen Typ"

    struct UnsignedMax64 {
    	unsigned int Value;
    	UnsignedMax64(unsigned int Value) {
    		_ASSERT(Value<=64);
    		this->Value = Value;
    	}
    };
    

    und schreib dann sowas

    void Funktion(UnsignedMax64 x) {
       x.Value=sqrt(x.Value); //Irgendwas mit x machen
    }
    

    Man sieht jetzt zumindest ein wenig am Typ was man eingeben kann und was nicht.
    Zudem spar ich mir nun jedesmal die Abfrage ob der Wert von x zuslässig ist.
    Findet ihr das Sinnvoll? Wie würdet ihr das Lösen?



  • UnsignedMax64 foo(57);
    foo.Value += 30;
    Funktion(foo);
    

    😉



  • Dudeldu schrieb:

    Findet ihr das Sinnvoll? Wie würdet ihr das Lösen?

    Ganz einfach: Exceptions. Das ist genau was du willst.

    template<typename T>
    std::string tostr(T const& t)  ///ACHTUNG! Nur für die Fehlermeldung!
    {
        std::ostringstream stream;
        stream << t;
        return stream.str();
    }
    
    void Funktion(unsigned x) 
    {
       if(x > S) //Sicherstellen das die Schranke eingehalten wird
           throw std::out_of_range("Funktion(" + tostr(x) + ") called with value greater " + tostr(S) + "!");
    
       x=sqrt(x); //Irgendwas mit x machen
    }
    

    Wenn der Fehler den weiteren Verlauf des Programms unsinnig oder unmöglich macht, kommen Assertions in Frage. Diese sind allerdings nur im Debugmode "aktiv". Also eigentlich abhängig vom Code, denn wenn der dann letzten Endes Bugfrei ist, braucht man keine Assertions mehr (-> sie würden sowieso nie aktiviert).

    Also merke dir: Ist ein Fehler unvorhersehbar (bspw. Input, Filestreams, o.ä.) dann kommen immer Exceptions in Frage. Ist ein Fehler abhängig von deinem Code, nimm Assertions (denn wenn die Bedingung dann fehlschlägt, heißt das, dass es einen Bug gibt).



  • @Dobi
    Ja, meine Lösung ist z.Z. sehr Fehleranfällig. Deshalb wollte ich wissen wie man das besser macht. Müsste natürlich bei jeder Änderung von Value sicherstellen das es die Schranke nicht überschreitet innerhalb des Structs, wäre wohl ein ziemlicher overkill.

    @Sone
    Ok, dann wird wohl ein anderer Programmiere einmal auf die Nase fallen müssen(_ASSERT), wenn er einen ungültigen Parameter eingibt.

    Dachte man könnte das irgendwie so hinbiegen das andere direkt sehen welche Werte
    unsinn sind.



  • Dudeldu schrieb:

    Man sieht jetzt zumindest ein wenig am Typ was man eingeben kann und was nicht.
    Zudem spar ich mir nun jedesmal die Abfrage ob der Wert von x zuslässig ist.
    Findet ihr das Sinnvoll? Wie würdet ihr das Lösen?

    Sehr sinnvoll!
    Das ist so ziemlich das Gegenteil von "Garbage in, Garbage out" und Du bewahrst die Benutzer Deines Interfaces vor allerlei Schwierigkeiten und Mißverständnissen.
    B. Stroustrup betont in seiner Keynote zur Going Native 2012 genau diesen Punkt in der modernen C++ Entwicklung.
    Irgendwo bei channel9 gibt's seinen Vortrag als Video zum Download.

    Nachteil: nochmehr Kopfzerbrechen beim Interfacedesign...(Verfolgt man Deinen Ansatz konsequent macht das echt Arbeit....)
    Vorteil, den Du noch nicht genannt hast: weniger Dokumentation (die nachher eh keiner liest...)



  • Wie wäre denn folgendes?

    //internal_typedefs.h
    typedef unsigned int uint_max_value_64;
    
    //public_interface.h
    #include "internal_typedefs.h"
    
    void foo(uint_max_value_64 val);
    

    Eine vermutlich überdesignte Alternative wäre ein eigener Integertyp, für den alle Operatoren (natürlich inline) überladen sind.



  • Simpel, frei von Exceptions und Assertions, dafür aber eher unkomfortabel zu benutzen wäre sowas:

    #include <iostream>
    #include <algorithm>
    class UnsignedMax64 {
    public:
        UnsignedMax64(unsigned int value) : value_(Clamp(value)) {}
        unsigned int Get() { return value_; }
        void Set(unsigned int value) { value_ = Clamp(value); }
        static unsigned int Clamp(unsigned int value) {
            return std::min(value, 64U);
        }
    private:
        unsigned int value_;
    };
    int main()
    {
        UnsignedMax64 v(123);
        std::cout << v.Get() << "\n";
    }
    

    Und damit's ein Bischen mehr Spaß macht: 😉

    #include <iostream>
    #include <algorithm>
    template <typename T, T MIN, T MAX>
    class Clamped {
    public:
        Clamped(const T& value) : value_(Clamp(value)) {}
        const T& Get() { return value_; }
        void Set(const T& value) { value_ = Clamp(value); }
        static T Clamp(const T& value) {
            return std::max(MIN, std::min(value, MAX));
        }
    private:
        T value_;
    };
    int main()
    {
        Clamped<int, 0, 64> v(123);
        std::cout << v.Get() << "\n";
    }
    


  • @Dobi

    Die Funktion kann zwar nun nicht mehr falsche Eingaben bekommen, aber letztendlich wurde das Problem nur eine Ebene davor verfrachtet.

    Der Nutzer der Funktion merkt u.U. nicht das es keinen Unterschied macht ob er 64, 123 oder 999 eingibt, wenn er nicht geschnallt hat das UnsignedMax64 Falscheingaben willkürlich auf 64 biegt.

    Ich bin für's verbieten Falscher eingaben(am besten zur Compilezeit) oder für's Ohrfeigen mit Fehlermeldung/Exception o.ä. Aber nicht unsinnige Eingaben durch Geradebiegen verstecken.



  • Joa, kommt halt immer drauf an, wofür man es braucht. Falls es um einen Slider (also das Eingabelement), dem man statt ihn umherzuschieben auch eine Zahl eingeben kann, geht, ist Clampen gar nicht so verkehrt. In anderen Situationen ist es der größte Müll und das Programm muss dem Benutzer natürlich ins Gesicht springen. Um was für eine Situation es sich hier handelt, kannst du nur wissen. 😉
    Also setz' ne Assertion davor wenn der Programmierer doof war, oder schmeiss ne Exception/zeig ne Meldung/sonstwas wenn der Benutzer schuld ist.



  • Dudeldu schrieb:

    Ich bin für's verbieten Falscher eingaben(am besten zur Compilezeit)

    Das geht nur mit non-type Template-Parametern (oder evt. Makros).


Anmelden zum Antworten