range-checked vector



  • Belli schrieb:

    Leite von vector ab, und überlade den [] - Operator.

    Der wird dann aber eher überdeckt und net überladen.



  • Nexus schrieb:

    Belli schrieb:

    Leite von vector ab, und überlade den [] - Operator.

    Schlechte Idee, da std::vector nicht als Basisklasse konzipiert ist (z.B. kein virtueller Destruktor).

    Stroustrup empfiehlt das in "Die C++ Programmiersprache".



  • Nexus schrieb:

    Belli schrieb:

    Leite von vector ab, und überlade den [] - Operator.

    Schlechte Idee, da std::vector nicht als Basisklasse konzipiert ist (z.B. kein virtueller Destruktor).

    btw ... spielt das nicht nur dann eine Rolle, wenn ich einen Zeiger auf einen Vektor anlege und den dann auf ein Objekt meiner abgeleiteten Klasse zeigen lasse?



  • Belli schrieb:

    spielt das nicht nur dann eine Rolle, wenn ich einen Zeiger auf einen Vektor anlege und den dann auf ein Objekt meiner abgeleiteten Klasse zeigen lasse?

    Jupp und wenn das abgeleitete Objekt keine zusätzlichen Member-Variablen beinhaltet und einen "leeren" Destruktor hat, sollte es bei allen gängigen Compilern meiner Meinung nach keine Probleme geben (schön ist's trotzdem nicht).



  • Belli schrieb:

    Stroustrup empfiehlt das in "Die C++ Programmiersprache".

    Okay, dann kann wohl kein Gegenargument mehr gebracht werden. :p

    Wenn schon, würde ich den std::vector wrappen, also als Member verwenden. Aber beide Ansätze sind weitaus übertrieben, wenn man bedenkt, was deren Anstoss war - aus Ästhetikgründen nicht at() statt operator[] verwenden zu wollen. 🙄



  • Nexus schrieb:

    ...aus Ästhetikgründen nicht at() statt operator[] verwenden zu wollen. 🙄

    Naja, nucht NUR das.
    Er wollte ja auch "...per Compilerschalter range checking an- und ausschalten..." (nicht, dass ich das ein erstrebenswerteres Ziel fände, aber das ist schon ein wenig mehr als reine Kosmetik 😃 ).

    Gruß,

    Simon2.



  • #ifdef MIT_CHECK
    #define LIPPENSTIFT .at(
    #define WIMPERNTUSCHE )
    #else
    #define LIPPENSTIFT [
    #define WIMPERNTUSCHE ]
    #endif
    
    // ...
    
    meinVector LIPPENSTIFT 3 WIMPERNTUSCHE = meinVector LIPPENSTIFT 10 WIMPERNTUSCHE;
    

    Dann passt das auch mit der Kosmetik.

    SCNR



  • Nexus schrieb:

    Belli schrieb:

    Stroustrup empfiehlt das in "Die C++ Programmiersprache".

    Okay, dann kann wohl kein Gegenargument mehr gebracht werden. :p

    Wenn schon, würde ich den std::vector wrappen, also als Member verwenden. Aber beide Ansätze sind weitaus übertrieben, wenn man bedenkt, was deren Anstoss war - aus Ästhetikgründen nicht at() statt operator[] verwenden zu wollen. 🙄

    Also für diesem konkreten Fall würde ich wohl private von Vektor erben (was ja ebenfalls einer Aggregation entspricht), und alles, was benötigt wird über using Direktiven wieder sichtbar machen bzw. den operator[] überdecken.

    Ganz prinzipiell halte ich das aber immer noch für einen schlechten Ansatz. Ich würde die Rangechecks wohl eher über das assert() -Makro lösen. Das kann man dann auch einfach im Release-Mode per Makro ausschalten.



  • Mal abgesehn von den bisherigen Diskussionen mal eine kleine Betrachtung ob das überhaupt sinnvoll ist:

    - Wenn Range-Checks eingeschaltet sind, muss eine Exceptionbehandlung her, sonst hat man gegenüber der Version ohne Range-checks keine nennenswerten Vorteile: so oder so schmiert das Programm ab.
    - Hat man die Range-Checks dann ausgeschaltet, braucht man die Exceptionbehandlung auch nicht mehr. auf der anderen Seite hat man dann wohl durch andere Methoden sichergestellt, dass die Indizes jeweils gültig sind (sonst würd einem das Programm ja um die Ohren fliegen)

    Die Entscheidung, ob man at() oder op[] benutzt, ist also eine Designentscheidung die sich auch auf den umliegenden Code auswirkt, nicht nur auf die Aufrufe selbst. Das mit einem #define "mal eben" hin- und hertriggern zu wollen ist also recht sinnfrei.

    Wenns dir nur darum geht während der Entwicklung die Range zu überprüfen, nutz assert(), dazu ist das da (und wird im Releasemodus automatisch weggetriggert)



  • pumuckl schrieb:

    Warum nicht per typedef?

    Ich habe es gerade mit typedef versucht und dabei festgestellt, dass der Standard ja so was wie

    template<class T> typedef vector<T> RVec;
    

    gar nicht erlaubt. Kann man das irgendwie anders mit typedef umsetzen oder muss man doch Präprozessor-Direktiven nutzen?



  • Hast Du die letzten paar Posts auch mal gelesen?



  • Tachyon schrieb:

    Hast Du die letzten paar Posts auch mal gelesen?

    nein, der letzte Beitrag von pumuckl war noch nicht da, als ich meinen Beitrag geschrieben habe, der beantwortet dann ja eigentlich alle Fragen



  • pumuckl schrieb:

    ...
    - Wenn Range-Checks eingeschaltet sind, muss eine Exceptionbehandlung her, sonst hat man gegenüber der Version ohne Range-checks keine nennenswerten Vorteile: so oder so schmiert das Programm ab....

    Obwohl ich Dir im Grunde zustimme, würde ich in dem Detail widersprechen wollen: Wenn bei range violations Programme zuverlässig "abschmierten", wären sie viel ungefährlicher.
    So gesehen hätte IMHO ein range-checking auch ohne exception handling Wert. Man könnte sogar überlegen, statt einer exception direkt ein assert() "zuschlagen zu lassen" (wie jemand oben bereits vorschlug, glaube ich).

    Trotzdem bin ich im Kern immer noch Deiner Meinung.

    Gruß,

    Simon2.



  • ingobulla schrieb:

    Kann man das irgendwie anders mit typedef umsetzen oder muss man doch Präprozessor-Direktiven nutzen?

    template typedefs gehn leider wirklich nicht, daher muss man zu einem kleinen Kniff greifen:

    template <class T>
    struct Container
    {
      typedef std::vector<T> type;
      //oder typedef std::list<T> type;
      //oder typedef MyOwnVectorImpl<T> type;
    };
    
    //später:
    
    void blub()
    {
      Container<int>::type values;
      Container<myClass>::type myCont;
    
      //usw.
    }
    

    Sieht auf den ersten Blick nicht ganz so hilfreich aus, man spart schließlich kaum Tipparbeit - aber man kann den zugrundeliegenden Contyinertyp mit einer Zeile austauschen: im typedef im struct Container<T>.



  • pumuckl schrieb:

    Mal abgesehn von den bisherigen Diskussionen mal eine kleine Betrachtung ob das überhaupt sinnvoll ist:

    - Wenn Range-Checks eingeschaltet sind, muss eine Exceptionbehandlung her, sonst hat man gegenüber der Version ohne Range-checks keine nennenswerten Vorteile: so oder so schmiert das Programm ab.

    Das wäre schön, ist aber nicht zwangsläufig der Fall. Der VC++ hat in seiner STL-Implementierung range checks, sogar im Release build, da schmiert das Programm ab. Bei anderen STL-Implementierungen ist das nicht zwangsläufig der Fall.

    Ich bin auch nicht der Meinung, dass man bei der Verwendung von at() zwangsläufig ein Exception-Handling einbauen muss. Oft genug ist es akzeptabler, ein Programm abstürzen zu lassen als einen buffer overflow zu riskieren. Oft ist in der Nähe von main() einfach ein Exception Handler für alles, dann kann man wenigstens noch was ins logfile schreiben. An Ort und Stelle lassen sich diese Art von Fehlern imho kaum sinnvoll behandeln, da jede Alternative (zum Beispiel size() prüfen) einfacher zu implementieren ist.

    Wenns dir nur darum geht während der Entwicklung die Range zu überprüfen, nutz assert(), dazu ist das da (und wird im Releasemodus automatisch weggetriggert)

    Dieses assert bei jedem Zugriff auf den Vector einzubauen ist eben unpraktikabel. Ich vermute, der Threadersteller wünscht, dass der Test Teil des operator[] ist. Keine Ahnung ob du das gemeint hast, sollte man aber mal dazu schreiben.



  • Ich habe das jetzt mal versucht umzusetzen, im folgenden der Code. Erläuternd sei erstmal gesagt, dass

    • LoggerPtr ein Zeiger auf ein logger-Objekt der Logging-Bibliothek log4cxx ist
    • SAFE eine globale Konstante ist, die festlegt, ob das Programm in einem abgesicherten Modus betrieben werden soll
    • ERROR ein Makro ist, dass dafür sorgt, dass eine Fehlermeldung ausgegeben und geloggt wird
    #ifndef VECTOR_H_
    #define VECTOR_H_
    
    #include "control_consts.h"
    #include "error.h"
    
    #include <log4cxx/logger.h>
    
    #include <vector>
    #include <list>
    
    using namespace log4cxx;
    using namespace std;
    
    template<class T> class RVec : private vector<T>
    {
    private:
    	static LoggerPtr m_logger;
    	static LoggerPtr& logger() { return m_logger; }
    public:
    	using vector<T>::size;
    	using vector<T>::resize;
    	using vector<T>::front;
    	using vector<T>::back;
    	using vector<T>::push_back;
    	using vector<T>::pop_back;
    	using vector<T>::begin;
    	using vector<T>::end;
    	using vector<T>::rbegin;
    	using vector<T>::rend;
    	using vector<T>::iterator;
    	using vector<T>::reverse_iterator;
    	using vector<T>::const_iterator;
    	using vector<T>::const_reverse_iterator;
    	using vector<T>::reference;
    	using vector<T>::const_reference;
    	using vector<T>::clear;
    
    	RVec() : vector<T>() { }
    	RVec(int s) : vector<T>(s) { }
    	RVec(int s, T t) : vector<T>(s, t) { }
    	RVec(typename vector<T>::iterator it, typename vector<T>::iterator jt) : vector<T>(it, jt) { }
    	RVec(typename list<T>::iterator it, typename list<T>::iterator jt) : vector<T>(it, jt) { }
    	RVec(T* p, T* q) : vector<T>(p, q) { }
    
    	T& operator[](int i);
    	const T& operator[](int i) const;
    };
    
    template<class T> LoggerPtr RVec<T>::m_logger(Logger::getLogger("rvec"));
    
    template<class T> T& RVec<T>::operator[](int i)
    {
    	if (SAFE && (i < 0 || i >= size())) {
    		stringstream ss;
    		ss << "Index out of range: index = " << i << ", size = " << size();
    		throw ERROR(logger(), ss.str());
    	}
    	T& t = vector<T>::operator[](i);
    	return t;
    }
    
    template<class T> const T& RVec<T>::operator[](int i) const
    {
    	if (SAFE && (i < 0 || i >= size())) {
    		stringstream ss;
    		ss << "Index out of range: index = " << i << ", size = " << size();
    		throw ERROR(logger(), ss.str());
    	}
    	const T& t = vector<T>::operator[](i);
    	return t;
    }
    
    #endif
    

    Wenn ich nun aber den Code

    RVec<bool> is_act_site(Base_Nd<N, C, T>::num_sites());
    	...
    	for (int i = 0; i < left_most_act_site; ++i)
    		is_act_site[i] = false;
    

    ausführe, erhalte ich die Fehlermeldung

    ../src/rvec.h: In member function ‘T& RVec<T>::operator[](int) [with T = bool]’:
    ../src/node/base_nd_def.h:92: instantiated from ‘Base_Nd<N, C, T>::Base_Nd(T*, N*, N*, boost::variant<int, Empty, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_>) [with N = Rev_Nd, C = Rev_NC, T = Rev_Tree]’
    ../src/node/base_nd.cpp:16: instantiated from here
    ../src/rvec.h:65: error: invalid initialization of non-const reference of type ‘bool&’ from a temporary of type ‘std::_Bit_reference’

    bzgl. Zeile 65 des obigen Codes und der Zeile

    is_act_site[i] = false;
    


  • hmpf. bau halt einfach ein assert im code vom vector ein.


Anmelden zum Antworten