You are on page 1of 66

Modern Maintainable Code

Mark Isaacson

Why?
● Good style prevents bugs by design ● Good style makes it easier to fix bugs when they do happen ● Creates efficient code ‘by-default’ ● Easily reworked and refined

We don’t teach it to you!
(Except for 381)

Where to begin? ● ● ● ● ● ● ● ● Auto Curly-brace initialization Smart pointers Standard algorithms Automated testing Documenting invariants Patterns Const-correctness ● ● ● ● ● ● ● ● No unsigned Move semantics C++ casts RAII Pragma once Typed literals Encapsulation Lambdas .

Where to begin? ● ● ● ● ● ● ● ● Auto Curly-brace initialization Smart pointers Standard algorithms Automated testing Documenting invariants Patterns Const-correctness ● ● ● ● ● ● ● ● No unsigned Move semantics C++ casts RAII Pragma once Typed literals Encapsulation Lambdas .

Self-Documentation (It’s what you don’t write that makes my eyes bleed) .

Real Problems: Real Code auto meep = (x > y ? y : x). What does it do? .

Say it loud! auto meep = min(x. y). What does it do? .

Real Problems: Real Code *Vertices 3 1 "Node 1” 2 "Node 2" 3 "Node 3" *Edges 3 1 2 2 3 3 1 .

g. return g.Names and Abstraction Graph readInput(const string& filename) { ifstream infile{filename}.nodes_ = readNodes(infile). g.arcs_ = readArcs(infile. } . Graph g. g.nodes_).

//Are we the parent: if (pid > 0) { cout << “Hello” << endl. } cout << “Goodbye” << endl. Thoughts? .Real Problems: Real Code auto pid = fork().

auto isParent = pid > 0.Real Problems: Real Code auto pid = fork(). . if (isParent) { cout << “Hello” << endl. } cout << “Goodbye” << endl.

Thoughts? . (size_t)VM_PAGESIZE). '\0'.Real Problems: Real Code memset( (char *) vm_physmem + (physicalPageIndex*(unsigned) VM_PAGESIZE).

Real Problems: Real Code memset( (char *) vm_physmem + (physicalPageIndex*(unsigned) VM_PAGESIZE). fill_n(startOfPage. (size_t)VM_PAGESIZE). Transformed: auto physicalBaseAddr = static_cast<char*>(vm_physmem). . '\0'. auto startOfPage = physicalBaseAddr + offsetFromBase. VM_PAGESIZE. auto offsetFromBase = physicalPageIndex * VM_PAGE_SIZE. '\0').

second << endl.Very Real Problems: Very Real Code for (auto& entry : myMap) { cout << entry. } What’s the type of an ‘entry’? What does this mean? .

Very Real Problems: Very Real Code for (auto& entry : myMap) { cout << entry. } .value << endl.

The Evils of Pair pair<string. int> getPlayerData(). What does getPlayerData return? .

int score_. PlayerData getPlayerData(). What does getPlayerData return? .The Evils of Pair struct PlayerData { string name_. }.

} .Curly brace 1-line ifs! Showed this a few slides ago: if (isParent) { cout << “Hello” << endl.

Return by move More on how it works later. } return x. ++i) { x. for now: vector<int> foo() { vector<int> x.push_back(i). i < numeric_limits<int>::max(). // This takes O(1) time! } . for (auto i = 0.

.Abstract Code Organization Not just interfaces.. .

The Scene .

Code tells a story: ● ● ● ● ● ● getConnectionInfo() createClientSocket() createServerSocket() clientStart() serverStart() main() .

Code tells a story:
● ● ● ● ● ● main() serverStart() clientStart() createServerSocket() createClientSocket() getConnectionInfo()

Auto Everywhere
Even ints!

What is Auto?

Strong Motivation: Pop quiz: vector<int> v { 1. 5.size(). 6. 2. 10 }. v. 7. 9. 3. 8. 4. What’s the type of the value returned by size()? .

++itr) { cout << *itr << ‘ ‘ << endl.crend(). } And: for (string& str : c) { cout << str << ‘ ‘ << endl. “Bob”. “how”. vector<string>::const_reverse_iterator itr = c. “goes?” }. itr != c.crbegin(). } .Strong Motivation: vector<string> c { “Hello”. for (.

crend(). } . ++itr) { cout << *itr << ‘ ‘ << endl. } And: for (char* str : c) { cout << str << ‘ ‘ << endl. itr != c. unordered_set<char*>::const_reverse_iterator itr = c.Strong Motivation: unordered_set<char*> c { “Hello”. for (. “goes?” }.crbegin(). “how”. “Bob”.

++itr) { cout << *itr << ‘ ‘ << endl. } . itr != c. “goes?” }. “Bob”. } And: for (auto& str : c) { cout << str << ‘ ‘ << endl.crend().crbegin(). “how”. for (auto itr = c.Strong Motivation: vector<string> c { “Hello”.

crbegin(). ++itr) { cout << *itr << ‘ ‘ << endl. “Bob”. itr != c.Strong Motivation: unordered_set<char*> c { “Hello”.crend(). for (auto itr = c. “goes?” }. } . “how”. } And: for (auto& str : c) { cout << str << ‘ ‘ << endl.

find(“Bob”). PhoneNumber>& nameToPhone) { auto itr = nameToPhone.Aliases: void foo(const map<string. if (itr != end(nameToPhone)) { auto& bobsNumber = itr->second. //Do stuff } } .

. or auto myWidget = Widget{getGadget()}.Explicit Conversions: Widget myWidget = getGadget().

auto ptr = make_shared<Foo>(). shared_ptr<Foo> ptr = make_shared<Foo>(). . or auto rawPtr = new Foo*{}. shared_ptr<Foo> sillyPtr = shared_ptr<Foo>{new Foo{}}.Stop Repeating Types! Foo* rawPtr = new Foo*{}.

What did auto buy us? ● ● ● ● Efficiency Correctness Maintenance Convenience .

Even ints. 5fL. //Don’t.. 5u. “Hello”s. 5 f. auto auto auto auto auto auto a b c d e f = = = = = = 5. 5 L. //C++14 ..

} Why? if (argv[1] == “--help”_s) { cout << “Help stuff. } . size_t len) { return string{str. len}.” << endl.‘Till C++14 string operator"" _s(const char* str.

vector<Widget> v. . decltype(w + g) e. Gadget g. decltype(v) c. decltype Widget w.Auto’s Cousin. decltype(w) a. decltype(v)::value_type d. decltype(foo()) b. int foo().

An Aside: template<typename T. const U& u) -> decltype(t + u) { return t + u. typename U> auto add(const T& t. } .

.

} . typename U> auto add(const T& t.An Aside: template<typename T. const U& u) -> decltype(t + u) { return t + u.

typename U> auto add(const T& t. } .C++14 template<typename T. const U& u) { return t + u.

Move Semantics Clean fast interfaces .

2. 4. What’s in v1? . 5 auto v2 = v1. 3. v2[0] = -1. }.Prelude to Move Semantics vector<int> v1 { 1.

3.Prelude to Move Semantics vector<int> v1 { 1. vector<int> v2 { 6. What’s in v1? . 7. 9. v2). 5 swap(v1. 4. }. 10 }. 2. 8.

2. 4. What’s in v1? . 3.Move Semantics vector<int> v1 { 1. 5 auto v2 = move(v1). }.

int* data_. }. 3.Move Semantics vector<int> v1 { 1. 4. v1 int size_. 2. 1 2 3 4 5 . 5 auto v2 = move(v1).

Move Semantics vector<int> v1 { 1. int* data_. 2. }. int* data_. 4. 3. 5 auto v2 = move(v1). v1 int size_. v2 int size_. nullptr 1 2 3 4 5 .

for (auto i = 0. } return x.Return by move Back to this: vector<int> foo() { vector<int> x.push_back(i). // This takes O(1) time! } . ++i) { x. i < numeric_limits<int>::max().

while (getline(infile.Pass by move ifstream infile{“filename.push_back(move(line)).txt”}. //Avoids string copying! } . line)) { lines. vector<string> lines. string line.

} This works too. but doesn’t actually ‘move’: void print(const string& str) { cout << str << endl. } .Receive by move void print(string str) { cout << str << endl.

L-values and R-values .Things we won’t talk about Foo(Foo&& foo). What std::move() *actually* does. Foo& operator=(Foo&& rhs).

Smart Pointers Because we’re all clever and stuff .

Quick look at ifstream ifstream infile{“filename. } Am I missing anything? . line)) { cout << line << endl.txt”}. string line. while (getline(infile.

Meet unique_ptr void useString(string& str). useNullableString(strPtr. strPtr->size(). void foo() { auto strPtr = make_unique<string>(“Hello”). //Don’t write delete! } //Memory deallocated here . void useNullableString(string* strPtr). useString(*strPtr).get()).

useString(*strPtr). } void foo() { auto strPtr = make_unique<string>(“Hello”).”}. } .So? void useString(string& str) { throw runtime_error{“Out for lunch.

//w is a unique_ptr<int[]> .What about arrays? auto intArray = make_unique<int[]>(5).

auto moved = move(w). //Error! Won’t compile. //Happy :) . //w is a unique_ptr<Widget> auto copy = w.Unique Ownership auto w = make_unique<Widget>().

Containers class MyIntVector { public: MyIntVector() : size_{0}. begin(data_)). end(src.size_} { data_ = make_unique<int[]>(src. }. } private: int size_.size_).data_). . copy(begin(src.data_). unique_ptr<int[]> data_. data_{nullptr} {} MyIntVector(const MyIntVector& src) : size_{src.

} //Heyyyyy! //Copy falls out of scope //More efficient //Memory deallocated here .Meet shared_ptr Reference counted: void foo() { auto w = make_shared<Widget>(). //w is a shared_ptr<Widget> { auto copy = w. } auto moved = move(w).

.Shared Ownership Being ‘present in multiple data structures’ is not enough: vector<unique_ptr<Zombie>> masterList. priority_queue<Zombie*> attackPriority.

}. . }.Shared Ownership class MapView { … private: vector<shared_ptr<Ship>> ships_. class Warship : public Ship { … private: shared_ptr<Ship> attackee_.

} void setData(shared_ptr<const Data> newData) { lock_guard<mutex> guard{m_}.Shared Ownership struct Server { shared_ptr<const Data> getData() const { lock_guard<mutex> guard{m_}. data_ = newData. shared_ptr< const Data> data_. } private: mutable mutex m_. . return data_.

shared_ptr will always work .

Strive to be a Craftsperson .

More Resources ● ● ● ● ● umich.edu/~eecs381/ isocpp.com Polkadots .org herbsutter.logdown.com/gotw maintainablecode.