Referenz mit Standardwert?


  • Administrator

    hustbaer schrieb:

    @Dravere:
    Klar gibt's Nullreferenzen.

    Und wie soll dies gehen? Tut mir Leid, wenn ich hier was offensichtliches übersehe, habe letzte Nacht nur 4 Stunden geschlafen 😉
    Aber eine Referenz muss doch auf ein gültiges Objekt initialisiert werden, sobald diese erstellt wird. Ich kann eine Referenz nicht auf Nichts referenzieren lassen.

    Grüssli



  • Dravere schrieb:

    hustbaer schrieb:

    @Dravere:
    Klar gibt's Nullreferenzen.

    Und wie soll dies gehen? Tut mir Leid, wenn ich hier was offensichtliches übersehe, habe letzte Nacht nur 4 Stunden geschlafen 😉
    Aber eine Referenz muss doch auf ein gültiges Objekt initialisiert werden, sobald diese erstellt wird. Ich kann eine Referenz nicht auf Nichts referenzieren lassen.

    Du weiß sicherlich, daß sowas gemeint war.

    int& ri=*(int*)0;
    


  • Microsoft C/C++ Runtime schrieb:

    Unhandled exception at 0x009113da in test.exe: 0xC0000005: Access violation reading location 0x00000006.

    😃

    Edit: Auch zu müde!



  • hustbaer schrieb:

    Klar gibt's Nullreferenzen. Die Frage ist nur ob es laut Standard erlaubt ist welche zu erzeugen. Wobei sich da IIRC das Committee selbst nicht einig ist.

    Denkst du gerade an einen bestimmten Punkt, wo es nicht eindeutig ist? Mir fiele nämlich keiner ein. Man könnte zwar argumentieren, dass

    T& ref = *(T*)0;
    

    okay sei, solange nicht direkt auf ref zugegriffen wird (d.h. nur mit Adressoperator, sizeof oder so). Aber das scheint mir mehr Spekulation zu sein.

    Der C++-Standard spricht diesbezüglich deutliche Worte:

    §8.3.2/4 schrieb:

    A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. [...]]

    Und die Dereferenzierung eines Nullzeigers ist nur in einem mir bekannten Kontext wohldefiniert, nämlich innerhalb eines typeid -Operators. Der Referenz bringt das nichts. Ich finde die Aussage, es gäbe keine (in C++ erlaubten) Nullreferenzen, also gar nicht so abwegig. Zumal diese keinen praktischen Nutzen haben und man sich auf nichts verlassen kann, sobald man sie einsetzt.



  • Nexus schrieb:

    Und die Dereferenzierung eines Nullzeigers ist nur in einem mir bekannten Kontext wohldefiniert, nämlich innerhalb eines typeid -Operators.

    Das überrascht mich. Erwarten würde ich, daß, wenn typeid es kann, auch sizeof es kann.



  • volkard schrieb:

    Das überrascht mich. Erwarten würde ich, daß, wenn typeid es kann, auch sizeof es kann.

    Da der sizeof -Operator den übergebenen Ausdruck nicht vollständig auswertet, spielt der Wert des Zeigers keine Rolle. Es kommt also keine Dereferenzierung zum Zuge, selbst wenns so aussieht.

    Ist natürlich Definitionssache, was nun Dereferenzierung ist. Es ist wahrscheinlich besser, nicht nur die Laufzeit zu betrachten. Solche Aktionen dürfen prinzipiell auch fehlschlagen, falls ptr Null ist:

    &*ptr
    


  • Nexus schrieb:

    hustbaer schrieb:

    Klar gibt's Nullreferenzen. Die Frage ist nur ob es laut Standard erlaubt ist welche zu erzeugen. Wobei sich da IIRC das Committee selbst nicht einig ist.

    Denkst du gerade an einen bestimmten Punkt, wo es nicht eindeutig ist? Mir fiele nämlich keiner ein. Man könnte zwar argumentieren, dass

    T& ref = *(T*)0;
    

    okay sei, solange nicht direkt auf ref zugegriffen wird (d.h. nur mit Adressoperator, sizeof oder so). Aber das scheint mir mehr Spekulation zu sein.

    Der C++-Standard spricht diesbezüglich deutliche Worte:

    §8.3.2/4 schrieb:

    A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. [...]]

    Und die Dereferenzierung eines Nullzeigers ist nur in einem mir bekannten Kontext wohldefiniert, nämlich innerhalb eines typeid -Operators. Der Referenz bringt das nichts. Ich finde die Aussage, es gäbe keine (in C++ erlaubten) Nullreferenzen, also gar nicht so abwegig. Zumal diese keinen praktischen Nutzen haben und man sich auf nichts verlassen kann, sobald man sie einsetzt.

    Irgendwer (vielleicht camper oder pumuckl?) hat hier mal was verlinkt wo im Endeffekt eine Diskussion zu finden war ob das nun erlaubt sein sollte oder nicht.
    Da dein Zitat ja belegt dass es im aktuellen Standard klardefinierterweise nicht OK ist, ging es da vermutlich um den kommenden Standard, und Änderungen die da gemacht werden sollen bzw. ob und wenn wie sie gemacht werden sollen.

    Und: gibt es im aktuellen Standard denn eine Ausnahme was typeid()/sizeof() angeht? Ich meine es ist logisch dass es egal sein muss, aber steht es auch im Standard? 🙂

    Kann auch sein dass genau das einer der Gründe war, warum vorgeschlagen wurde da etwas zu ändern. Also damit ein für allemal klar ist, dass es in sizeof/typeid OK ist. Und ich glaube mich zu erinnern, dass auch vorgeschlagen wurde das gleich so weit auszudehnen, dass es überall OK wäre. Gründe weiss ich nimmer, und ich bin auch nimmer 100% sicher.

    Kann mich nurmehr erinnern dass ich mir gedacht habe huch, dann fällt vielleicht die 'Referenzen müssen immer auf was zeigen' Sache - hoffentlich fängt die nächste Generation von C++ Programmierern nicht an das auch so zu verwenden.

    Im Prinzip gaukelt dieser Passus im Standard einem ja bereits heute eine falsche Sicherheit vor. Es steht ja nur da dass Referenzen immer mit einem gültigen Objekt *initialisiert* werden müssen. Ich kenne aber einige Programme, wo nicht sichergestellt ist, dass solche Objekte nicht gelöscht werden, bevor die Referenz gelöscht wird. Nur die Referenz wird danach halt einfach nimmer verwendet. Geben tut es sie aber noch, und sie zeigt fröhlich ins Nirvana. Ist natürlich was anderes als eine Nullreferenz, aber auch nicht ganz ohne.



  • hustbaer schrieb:

    Im Prinzip gaukelt dieser Passus im Standard einem ja bereits heute eine falsche Sicherheit vor. Es steht ja nur da dass Referenzen immer mit einem gültigen Objekt *initialisiert* werden müssen. Ich kenne aber einige Programme, wo nicht sichergestellt ist, dass solche Objekte nicht gelöscht werden, bevor die Referenz gelöscht wird. Nur die Referenz wird danach halt einfach nimmer verwendet. Geben tut es sie aber noch, und sie zeigt fröhlich ins Nirvana. Ist natürlich was anderes als eine Nullreferenz, aber auch nicht ganz ohne.

    Naja. Das ist aber imo ein grober Programmierfehler. Das Problem hast du bei Zeigern genau gleich. Eine "void-safe" Sprache zu machen ist nicht gerade ein trivialer Task. Wird im Moment bei Eiffel dran gearbeitet und funktioniert nur teilweise.



  • Nexus schrieb:

    &*ptr
    

    der &*it steht im alten Standard als Codebeispiel, auch wenn *it undefiniert ist, weil it==foo.end(). Man ist wohl stillschqweigend davon ausgegangen, daß ein ordentlicher Compiler das schaffen. 🙂



  • hustbaer schrieb:

    Irgendwer (vielleicht camper oder pumuckl?) hat hier mal was verlinkt wo im Endeffekt eine Diskussion zu finden war ob das nun erlaubt sein sollte oder nicht.

    Ich finde leider weder den Thread noch den Artikel, aber weiss, welchen du meinst. Hat ihn jemand? Soweit ich mich erinnere, war das Ergebnis, dass der C++-Standard von 2003 hier nicht ganz konsequent ist. Meist spricht dieser davon, die Dereferenzierung des Nullzeigers führe zu undefiniertem Verhalten, aber er erwähnt auch Ausnahmen wie typeid .

    hustbaer schrieb:

    Und: gibt es im aktuellen Standard denn eine Ausnahme was typeid()/sizeof() angeht? Ich meine es ist logisch dass es egal sein muss, aber steht es auch im Standard? 🙂

    Ja. Bei typeid auf Nullzeiger wird std::bad_typeid geworfen. Bei sizeof kann man aus der Nichtauswertung des Ausdrucks schliessen, dass keine Dereferenzierung zur Laufzeit erfolgen kann. Hier die betreffenden Stellen:

    Zu typeid; §5.2.8/2 schrieb:

    If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws the bad_typeid exception (18.5.3).

    Zu sizeof; §5.3.3/1 schrieb:

    The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id.

    volkard schrieb:

    der &*it steht im alten Standard als Codebeispiel, auch wenn *it undefiniert ist, weil it==foo.end(). Man ist wohl stillschqweigend davon ausgegangen, daß ein ordentlicher Compiler das schaffen. 🙂

    Bei Iteratoren ist es sogar noch schlimmer, da &*it in sehr vielen Fällen etwas anderes ist als it , sogar einen anderen Typ hat. Da kann der Compiler viel weniger optimieren und klug sein. Steht das wirklich drin?



  • Nexus schrieb:

    Compiler viel weniger optimieren und klug sein. Steht das wirklich drin?

    20.4.4.1
    (Kann aus meiner pdf nicht copy&pasten, ich nehme den entsprechenden von http://www.cplusplus.com/reference/std/memory/uninitialized_copy/ )

    The behavior of this function template is equivalent to:

    template<class InputIterator, class OutputIterator>
      OutputIterator copy ( InputIterator first, InputIterator last, OutputIterator result )
    {
      for (; first!=last; ++result, ++first)
        new (static_cast<void*>(&*result))
          typename iterator_traits<ForwardIterator>::value_type(*first);
      return result;
    }
    


  • Die Autoren setzen wohl voraus, dass [ first , last [ eine gültige Iterator-Range ist, also kein end() mittendrin. Wahrscheinlich hierauf bezogen:

    §24.1/7 Iterator requirements schrieb:

    Most of the library’s algorithmic templates that operate on data structures have interfaces that use ranges. A range is a pair of iterators that designate the beginning and end of the computation. A range [i, i) is an empty range; in general, a range [i, j) refers to the elements in the data structure starting with the one pointed to by i and up to but not including the one pointed to by j. Range [i, j) is valid if and only if j is reachable from i. The result of the application of functions in the library to invalid ranges is undefined.



  • Ja, aber darf ich mit * ein Objekt ziehen, das nicht konstruiert wurde?

    string* s=new string;
    delete s;
    *s;//erlaubt?
    &*s;//erlaubt?
    

    Ich denke nicht (*an meine Sig denk*).

    Oder mitr Iteratoren

    vector<string> v;
    v.reserve(1);
    *v.begin();//erlaubt?
    &*v.begin();//erlaubt?
    


  • Ah, ich dachte jetzt nur gerade an end() . Aber vor uninitialized_copy() ist ja der Speicher ein Trümmerfeld.

    Nein, du hast Recht. Ich würde auch sagen, auf Raw-Speicher darf man Iteratoren nicht dereferenzieren, auch nicht vorübergehend.

    Was gäbe es denn für Alternativen? operator-> gäbe einen T* zurück, sieht schon mal gut aus. Meinst du, sowas wäre ok?

    new (static_cast<void*>(result.operator->())) T(...)
    


  • Nexus schrieb:

    Ah, ich dachte jetzt nur gerade an end() . Aber vor uninitialized_copy() ist ja der Speicher ein Trümmerfeld.

    Ich dachte an end() eines Arrays. Da ist ja versprochen, daß die Zeigerarithmetik noch funktioniert, ich bin sogar bisher immer davon augegangen, daß der Speicher von *end() da ist, aber halt ein Trümmerfeld. Deswegen habe ich den Fall vorhin in der end-Schublade gefunden.



  • Um nochmal auf den ursprünglichen Fall zurückzukommen, es gibt mehrere Wege, so ein Interface zu realisieren. Am einfachsten ist wohl Überladung, etwa

    namespace impl {
      void call_backend(std::string const &strModule, HMODULE *hModule) {
        HMODULE hTemp = NULL;
        // mach was
    
        if (hModule != NULL)
        {
            *hModule = hTemp;
        } 
      }
    }
    
    void Call(std::string strModule) {
      impl::call_backend(stdModule, NULL);
    }
    
    void Call(std::string strModule, HMODULE &hModule) {
      impl::call_backend(strModule, &hModule);
    }
    

    Eine andere Möglichkeit, vor allem dann sinnvoll, wenn das ganze oft gebraucht wird, wäre eine Klassenvorlage, die die Referenz verwaltet, beispielsweise (ungetestet)

    template<typename T>
    class optional_ref {
    public:
      optional_ref() : ref_(0) { }
      optional_ref(T &ref) : ref_(&ref) { }
    
      // Hier kann man natürlich auch allerlei mit Operatorüberladung
      // machen, um die Benutzung natürlicher zu gestalten.
      void set(T const &val) { *ref_ = val; }
      T const &get() const { return *ref; }
    
      bool valid() const { return ref != 0; }
    
    private:
      T *ref_;
    };
    
    void Call(std::string strModule, optional_ref<HMODULE> hModule = optional_ref<HMODULE>()) {
      HMODULE hTemp = NULL;
      // mach was
    
      if (hModule.valid())
      {
        hModule.set(hTemp);
      } 
    }
    


  • Dein optional_ref<T> würde ich mit boost::optional<T&> ersetzen. Finde aber beides nicht so toll.

    Überladung FTW!

    Kann man - wie ich ja bereits gezeigt habe - auch einfacher machen.



  • Nexus schrieb:

    Ah, ich dachte jetzt nur gerade an end() . Aber vor uninitialized_copy() ist ja der Speicher ein Trümmerfeld.

    Nein, du hast Recht. Ich würde auch sagen, auf Raw-Speicher darf man Iteratoren nicht dereferenzieren, auch nicht vorübergehend.

    Was gäbe es denn für Alternativen? operator-> gäbe einen T* zurück, sieht schon mal gut aus. Meinst du, sowas wäre ok?

    new (static_cast<void*>(result.operator->())) T(...)
    

    LOL!
    Wie einfach. Ich habs damals nicht hingekriegt. Bin einfach nicht auf die Idee gekommen, den operator-> aufzurufen. Mir hätte das gereicht.
    Ganz ok? Weiß nicht, wie ich 24.1.1 zu verstehen habe, er muß nur it->m haben, falls (*it).m geht. Und das geht, sobald der Iterator ein Template ist und Klasseninstanzen verwalten könnte. Aber naja, besser als &* ist es sicherlich, denn my_vector<int>::iterator::operator*() könnte ja einen Proxy zurückgeben, der...
    Oh, vector<bool>
    Der operator-> kann auch einen Proxy zurückgeben.
    Bin wieder verwirrt.



  • volkard schrieb:

    LOL!
    Wie einfach. Ich habs damals nicht hingekriegt. Bin einfach nicht auf die Idee gekommen, den operator-> aufzurufen. Mir hätte das gereicht.

    Hab zuerst auch gedacht, man müsse wohl eine Funktion haben, die von Iteratoren den Zeiger extrahieren kann. Dann hatte ich plötzlich diesen Geistesblitz. Liegt wohl an der Uhrzeit... 😉

    volkard schrieb:

    denn my_vector<int>::iterator::operator*() könnte ja einen Proxy zurückgeben, der...

    Du meinst wegen des reference -Typedefs? Kennst du ausser vector<bool> eine Implementierung, die nicht T& zurückgibt (nicht, dass man es nicht dürfte, interessiert mich nur)?

    volkard schrieb:

    Oh, vector<bool>
    Der operator-> kann auch einen Proxy zurückgeben.
    Bin wieder verwirrt.

    Ja, vector<bool> bekämpft leider Generizität, wo er nur kann. Eigentlich sollte man den rauswerfen. Ich frage mich, wie viele Programmierer schon von dessen Speicherersparnis profitiert haben? Mit std::bitset und boost::dynamic_bitset gibt es ohnehin fähigere Klassen.



  • Nexus schrieb:

    volkard schrieb:

    denn my_vector<int>::iterator::operator*() könnte ja einen Proxy zurückgeben, der...

    Du meinst wegen des reference -Typedefs? Kennst du ausser vector<bool> eine Implementierung, die nicht T& zurückgibt (nicht, dass man es nicht dürfte, interessiert mich nur)?

    Mit so Sachen habe ich damals angefangen. Auf einem 386-er mit DOS und "Dinge" mußten beim Dereferenzieren automatisch aus den 4M extended memory oder gar von Platte in den Bereich unter 640k hüpfen. Deswegen denke ich recht früh an Proxies. Aber ich kenne gerade keine. Das macht ja heute alles die Hardware.


Anmelden zum Antworten