Herb Sutter's "Writing Good C++14 …By Default" (PDF)



  • In Rust sieht das gleiche so aus:

    struct S {
    	m: Vec<i32>
    }
    
    impl S {
    	pub fn f(&mut self) {
    		let i = &mut self.m[0];
    		self.g();
    		*i = 23;
    	}
    
    	fn g(&mut self) {
    		self.m.push(0);
    	}
    }
    

    Wenn man C++ gewohnt ist, ist das ein wenig wortreich auf den ersten Blick. Explizite Mutability ist das aber auf jeden Fall wert. Auch das explizite self erhört IMO eher die Lesbarkeit, weil man nie raten muss, ob der Bezeichner womöglich ein Member ist.

    Am besten ist aber die Fehlermeldung des Compilers, die exakt erklärt, wo das Problem ist:

    src/main.rs:17:3: 17:7 error: cannot borrow `*self` as mutable more than once at a time
    src/main.rs:17 		self.g();
                   		^~~~
    src/main.rs:16:16: 16:22 note: previous borrow of `self.m` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `self.m` until the borrow ends
    src/main.rs:16 		let i = &mut self.m[0];
                   		             ^~~~~~
    src/main.rs:19:3: 19:3 note: previous borrow ends here
    src/main.rs:15 	pub fn f(&mut self) {
    src/main.rs:16 		let i = &mut self.m[0];
    src/main.rs:17 		self.g();
    src/main.rs:18 		*i = 23;
    src/main.rs:19 	}
                   	^
    

    Das "warum" steht hier nicht drin, aber das gehört in ein Rust-Tutorial und nicht in Compiler-Output.

    krümelkacker schrieb:

    Hast Du Dir etwas von X "ausgeliehen" (ein int von *this ), darfst Du solange das, worüber Du das komplette X erreichen kannst ( this ), nicht an eine andere Funktion ( g ) weitergegen.

    Gute Idee, aber das ist keine hinreichende Bedingung. Die Regel würde viel zu viele False Positives erzeugen.

    struct S
    {
        void f()
        {
    		m.resize(1);
    		m.reserve(2);
            int &i = m.front();
            g();
            i = 23;
        }
    
    private:
    
        std::vector<int> m;
    
        void g()
        {
            m.emplace_back(0);
        }
    };
    


  • Arcoth schrieb:

    [[modifies(m)]] void g() // Das sollte ein Analyzer selbst können.
        { 
            m.emplace_back(0); 
        } 
    };
    

    Das wäre meiner Ansicht nach keine lokale Analyse mehr. Bei der Prüfung von f sollte der Analyzer nicht wissen müssen, was in g passiert. Das sollte allein von der Signatur her klar sein, was darin passieren kann. Der Benutzer darf natürlich was annotieren. Aber das soll ja nicht überhand nehmen. Wenn der Compiler das selbst heraus finden soll, dann ist die Gefahr, dass der Analyseaufwand explodiert, weil g wieder andere Funktionen aufrufen können, die wieder andere Funktionen aufrufen können und so weiter.

    TyRoXx schrieb:

    Die Regel würde viel zu viele False Positives erzeugen.

    Das meinte ich mit "pessimistisch".

    Mein Vertrauen, Speichersicherheit auf diesem Wege an C++ dranflanschen zu können, ist im Moment nicht so hoch. Die werden damit noch ihren Spaß haben…



  • krümelkacker schrieb:

    Mein Vertrauen, Speichersicherheit auf diesem Wege an C++ dranflanschen zu können, ist im Moment nicht so hoch. Die werden damit noch ihren Spaß haben…

    Bezweifel ich auch, denn bei Rust muss man ja mit ziemlichen Einschraenkungen leben, damit der Compiler den Code durchwinkt. Gerade was mehrfachen Zugriff auf unabhaengige Member von self betrifft, muss man haeufig mit type destructuring, mem::replace oder zusaetzlichen Scopes hantieren.

    Man koennte aber zumindest die Sicherheit erhoehen. Gerade array_view sollte mal in den Standard kommen, denn es nervt mich schon lange, dass es keine andere standadisierte Moeglichkeit zu pointer-Laenge-Paar und iterator-paar gibt. Natuerlich geht selbst schreiben immer, aber im Standard erhoeht es doch ziemlich die konsistenz bei mehreren libraries.



  • Das Video von Herb's Vortrag ist jetzt auch online:
    https://www.youtube.com/watch?v=hEx5DNLWGgA



  • TyRoXx schrieb:

    In Rust sieht das gleiche so aus:

    Kannst du bitte mal deinen Codeabschnitt (Compilerausgaben) auf ca. 80 Zeichen kürzen? Kein Mensch kann die erste Threadseite lesen. 😡

    Danke!


  • Mod

    Artchi schrieb:

    Kannst du bitte mal deinen Codeabschnitt (Compilerausgaben) auf ca. 80 Zeichen kürzen? Kein Mensch kann die erste Threadseite lesen. 😡

    Doch. Was für einen Browser und welche Auflösung hast du denn?



  • Artchi schrieb:

    TyRoXx schrieb:

    In Rust sieht das gleiche so aus:

    Kannst du bitte mal deinen Codeabschnitt (Compilerausgaben) auf ca. 80 Zeichen kürzen? Kein Mensch kann die erste Threadseite lesen. 😡

    Danke!

    Hab auch keine Probleme (Chrome).

    MfG SideWinder



  • krümelkacker schrieb:

    Das wäre meiner Ansicht nach keine lokale Analyse mehr. Bei der Prüfung von f sollte der Analyzer nicht wissen müssen, was in g passiert. Das sollte allein von der Signatur her klar sein, was darin passieren kann. Der Benutzer darf natürlich was annotieren. Aber das soll ja nicht überhand nehmen. Wenn der Compiler das selbst heraus finden soll, dann ist die Gefahr, dass der Analyseaufwand explodiert, weil g wieder andere Funktionen aufrufen können, die wieder andere Funktionen aufrufen können und so weiter.

    Das erscheint auf den 1. Blick sehr aufwendig.
    Ist es aber mMn. nach nicht.
    Der Compiler muss sowieso alle Funktionen (mindestens) 1x compilieren die irgendwo verwendet werden.
    Wenn er das "bottom up" macht, dann kann er alle nötigen "Annotationen" in seinen internen Datenstrukturen selbst vornehmen. Wenn man die nötigen Infos mit im .obj File abspeichert, dann kann man auch weiterhin inkrementell compilieren bzw. ist nichtmal vom Source abhängig.

    Ein kleines Problem dabei sind natürlich rekursive Funktionen (weil es da halt kein "bottom up" gibt). Das grösste Problem sehe ich aber bei Funktionszeiger/Funktoren. Denn spätestens da kommt man nicht mehr mit dem Speichern von Annotationen zu einzelnen Funktionen bzw. Typen aus. std::function<T& (U&)> kann halt alle möglichen Abhängigkeiten haben und alles mögliche modifizieren.

    Bzw. es reicht schon std::function<void()> oder ein ganz normaler Funktionszeiger, gibt ja schliesslich Lambdas bzw. bind .



  • Nachdem ich mir das Vortragsvideo angeschaut und das dazugehörige Arbeitspapier durchgelesen habe, kann ich das nur nochmal bestätigen, wie ich mir die Analyse vorgestellt habe:

    struct S
    {
    	void f()
    	{
    		int &i = m.front(); // pset(i) = { this, this' }
    		                    // bedeutet: i bezieht sich auf etwas von
    		                    // this oder was this über eine (')
    		                    // Indirektionen gehört.
    
    		g();                // g bekommt ein nicht-const this und
    		                    // kann damit alle Zeiger, die this'
    		                    // oder this'' im pset haben invalidieren.
    		                    // => pset(i) = { invalid }
    
    		i = 23;             // nicht zulässig, da pset(i) = { invalid }
    	}
    
    private:
    
    	std::vector<int> m;
    
    	void g()
    	{
    		m.emplace_back(0);
    	}
    };
    

    "pset" heißt "pointer-to set", gibt also an, wo der Zeiger hinzeigen kann und wer der Besitzer davon ist. So eine Menge kann { invalid } sein, wenn man weiß, dass der Zeiger ungültig ist oder sein kann. Wenn die Menge nicht { invalid } ist, dann enthält sie mindestens eines der folgenden: null, static, X, X', X'' (wobei X ein beliebiger Besitzer ist). Mit einem if (ptr) {...} wird für den then-Block ggf null aus dem pset rausgeschmissen. Beim Dereferenzieren wird das pset geprüft und ggf mit einer Warnung reagiert.

    So ganz ausgekocht ist das noch nicht. Der Regelsatz wird im Papier eher informell erklärt. Das Schlüsselwort mutable wird hier wohl auch noch ein bisschen Ärger machen.



  • Arcoth schrieb:

    Artchi schrieb:

    Kannst du bitte mal deinen Codeabschnitt (Compilerausgaben) auf ca. 80 Zeichen kürzen? Kein Mensch kann die erste Threadseite lesen. 😡

    Doch. Was für einen Browser und welche Auflösung hast du denn?

    Firefox und 1600x1200 Pixel.

    Aber ist klar... ist zuviel verlangt mal nen Return in der einen zu langen Zeile rein zu hauen... 🙄


Anmelden zum Antworten