Liebe Leserinnen, liebe Leser,

genauso wie viele andere Leute da draußen war ich auch fröhlich, Smart-Pointers zu begegnen. Wie die meiste Anfänger probierte ich sie überall zu benutzen, weil ich dachte, dass das Vorhandensein von automatischer Lebensldauerverwaltung nicht schaden kann (wenn auch ich darüber nicht sicher bin, ob es nötig ist). Nachdem ich mir die CppCon 2014 Präsentation von Herb Sutter angeschaut habe, hat sich die Situation ein bisschen verbessert. Bevor ich das dennoch zeige, werde ich den Anfang unter die Lupe stellen:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
 * Dieser Code ist aus meiner Übersetzung des Java Codes von Head First Design Patterns
 * (Eric Freeman & Elisabeth Ronson). Der Rest des Codes und weitere Informationen
 * sind auf meiner Github-Seite (https://github.com/rpuskas0) zu finden.
 */

class GumballMachine {
public:
    
    // some machinery

    void setState(const std::shared_ptr<State>& state) {currentState = state;}

    // some additional machinery
    
private:
    std::shared_ptr<State> soldState;
    std::shared_ptr<State> soldOutState;
    std::shared_ptr<State> noQuarterState;
    std::shared_ptr<State> hasQuarterState;
    std::shared_ptr<State> winnerState;
    
    std::shared_ptr<State> currentState;
};


// an example state:

class SoldOutState : public State {
public:
    SoldOutState(std::shared_ptr<GumballMachine> ggumballMachine)
        : gumballMachine(ggumballMachine) {}
    
    void insertQuarter() override;
    void ejectQuarter() override;
    void turnCrank() override;
    void dispense() override;
    void refill() override;
    std::string print() override;
    
private:
    std::weak_ptr<GumballMachine> gumballMachine;
};

Geübte C++ Programmierer haben jetzt wahrscheinlich einen interessanten Gesichtsausdruck, und das ist nicht ohne Grund. Der oben zu sehende Codeausschnitt ist eine Definition der überflüssigen Nutzung von Smart-Pointers (automatische Lebensldauerverwaltung mag nicht schaden, oder?), ganz zu schweigen von der inkorrekten Nutzung des Zeigers in dem Konstruktor und der privaten Membervariable der GumballMachine Klasse. Als es sich herausstellte, sollte die Nutzung von Referenzen und rohen Zeigern durch dem Ersatz mit Smart-Pointers um jeden Preis nicht vermeidet werden, stattdessen werde ich die Aussage von Herr Sutter zitieren: “_Smart-Pointers sollten effektiv verwendet werden, jedoch sollen auch * und & vielmals benutzt werden, weil sie großartig sind!” Wann und wie soll man sie vielmals benutzen? Zum Glück war Herr Sutter mal wieder gründlich, also wir brauchen nicht schätzen:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
 * Mr. Sutter's guide to proper smart pointer, * and & usage:
 */

// Cool usage:
void f(widget& w) { // if required
    use(w);
}

void g(widget* w) { // if optional
    if(w) use(*w)
}

auto upw = make_unique<widget>();
...
f(*upw);

auto spw = make_shared<widget>();
...
g(spw.get());


// "Hurt Pain Pain" usage:
void f(refcnt_ptr<widget>& w) {
    use(w);
} //?

void f(refcnt_ptr<widget> w) { // if optional
    if(w) use(*w)
} // ?!?!

refcnt_ptr<widget> w = ...;
for(auto& e : baz) {
    auto w2 = w;
    use(w2,*w2,w,*w,whatever);
} // ?!?!?!?!

Was bedeutet das eigentlich? (mal wieder Herr Sutter)

  • Smart-Pointers sollten durch Wert oder Referenz nie weitergegeben werden, bis auf man den Zeiger manipulieren will (speichern, ändern, freigeben). Die Weitergabe der Objekte sollte durch * oder & bevorzugt werden.
    • Man soll bei der Weitergabe von nicht lokalen *obj in solche Funktionen vorsichtig sein (weitere Einzelheiten in der Präsentation)

  • Wenn es möglich ist, sollte der Besitz von Objekten mit unique_ptr ausgedrückt werden, einschließlich wenn es unbekannt ist, ob das Objekt geteilt werden wird.

  • Andernfalls soll make_shared vorab benutzt werden, falls das Objekt geteilt werden wird.

Herr Sutter vereinfacht das dann zu:

  • “Verwende besitzender roher * nicht”
    • außer in außergewöhnlichen Situationen
  • “Verwende nicht besitzender rohe * und &, besonders für Parameter”

  • “Kopiere Smart-Pointers mit Referenzanzahl nicht…, außer wenn die Lebensdauer des Objekts geändert werden muss”

Lasst uns den Code oben wieder anschauen. Der Code oben zeigt genau das Gegenteil der Empfehlung, also es soll aufgrund der Empfehlung behoben werden:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class GumballMachine {
public:
    
    // some machinery

    void setState(State* state) {currentState = state;}

    // some additional machinery
    
private:
    std::unique_ptr<State> soldState;
    std::unique_ptr<State> soldOutState;
    std::unique_ptr<State> noQuarterState;
    std::unique_ptr<State> hasQuarterState;
    std::unique_ptr<State> winnerState;
    
    State* currentState;
};

class SoldOutState : public State {
public:
    SoldOutState(GumballMachine& ggumballMachine)
        : gumballMachine(ggumballMachine) {}
    
    void insertQuarter() override;
    void ejectQuarter() override;
    void turnCrank() override;
    void dispense() override;
    void refill() override;
    auto print() -> std::string override;
    
private:
    GumballMachine& gumballMachine;
};

Es gibt eine zusätzliche Änderung in dem Code, die mit * und & nicht verbunden ist. Können Sie das finden? Wenn Sie mehr darüber herausfinden wollen, schauen Sie sich bitte die verlinkte Präsentation völlig an.

Die Lektion des Tages ist:

Roh besitzender * sollte nicht verwendet werden, aber die Nutzung von besitzendem * und & ist empfohlen, besonders für Parameter. Smart-Pointers sollten nur im solchen Fällen weitergegeben werden, wenn der Lebensdauer oder Besitz des Objekts manipuliert werden muss.

Vielen Dank fürs Lesen.