Dear Readers!
Just as a lot of other people out there, I was kind of happy to encounter smart pointers, so as any good beginner, I tried to stick them everywhere I could. It can’t hurt to have automatic lifetime management (even if I’m not sure if it is needed), can it? After watching Herb Sutter’s CppCon 2014 presentation though, things chagend for the better, I believe. But before we see how, let’s have a look at where I started from:
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
/*
* This code is taken from my C++ translation of Head First Design Patterns'
* (Eric Freeman & Elisabeth Ronson) Java code. For the rest of the code and
* more info about it, please visit my Github page at https://github.com/rpuskas0.
*/
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;
};
Experienced C++ programmers probably have some sort of interesting facial expression on their faces right now, and not without reason. The code sample above seems to be the very definition of an overuse of smart pointers (the can’t hurt to have ALM case), not to mention the incorrect use of them in that state constuctor and private members of GumballMachine. As it turns out, the use of references and especially raw pointers is not something we should avoid at all costs by replacing them with smart pointers, but instead, well, let me quote Mr. Sutter here: “Use smart pointers effectively, but still use lots of * and &, they’re great!”, When and how to use a lots of them? Mr. Sutter was thankfully thorough in this regard (as well), so we don’t have to play guessing games:
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);
} // ?!?!?!?!
What does this all mean in plain English? (still Mr. Sutter:)
- Never pass smart pointers (by value or by refernce) unless you actually want to manipulate the pointer (store, change, let go). Prefer passing objects by * or & as usual.
- Be aware of passing in nonlocal *obj into such functions though (watch presentation for details).
- Be aware of passing in nonlocal *obj into such functions though (watch presentation for details).
-
Express ownership using unique_ptr whenever possible, including when you don’t know whether the object will actually be shared.
- Else use make_shared up front whenever possible, if object will be shared.
He also simplifies this all down to:
- “Don’t use owning raw *”
- except in some exceptional situations
-
“Do use non-owning raw * and &, especially for parameters.”
- “Don’t copy/assign refcounted smart pointers…, unless you want to alter object lifetime”.
So let’s have a look back at the code above. All you can see is kinda the exact opposit of what is recommended, so let’s fix it to be recommendation conforming:
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
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;
};
// and the example state now looks like this:
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;
};
There is a little extra change in the code not related to * or &. Can you locate it? If you want to find out more about that, please watch the entirety of the linked presentation, and you’ll see the whys.
Anyway, lesson learned:
Don’t use owning raw *, but do use non-owning raw * and &, especially for parameters. Pass in smart pointers only if you want object lifetime/ownership manipulation.
Thank you for reading.