Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Elements of Modern C++ Style
Adapting to the New World Order
Winter is coming...
Warning: this talk assumes working knowledge of C++98/03
Winter is coming...
“C++11 feels like a new language. I write code differently now than I did in C++98. The C++11 code is shorter, simpler, and usually more efficient than what I used to write.”
Bjarne StroustrupInventor of C++
Winter is coming...
C++ is hard to use● operator overloading● template programming● multiple inheritance● pointers● STL
Winter is coming...
Unfamiliar things are hard to use● lambdas● type deduction● move semantics● smart pointers● memory model
Winter is coming...Not your grandfather’s C++
Client MantisServer::CreateClient(const std::string &clientid, const std::string &streamid, const std::string &connectionid) {
std::shared_ptr<Client> client;
if (requestqueue_) {
requestqueue_->push({[=, &client](mantis::core::Server *server) {
client = server->CreateClient(clientid, streamid, connectionid);
}});
}
Winter is coming...Mission: completely understand what this is doing
Client MantisServer::CreateClient(const std::string &clientid, const std::string &streamid, const std::string &connectionid) {
std::shared_ptr<Client> client;
if (requestqueue_) {
requestqueue_->push({[=, &client](mantis::core::Server *server) {
client = server->CreateClient(clientid, streamid, connectionid);
}});
}
On upcoming standards
● C++14 is a minor update on 11○ fix errata○ fix holes/overlooked issues○ make features more generic
● C++17 is a major update on 11○ new std libraries for filesystem and networking○ new concurrency constructs○ optional and any types
lambdastruct Object {
int value;
};
struct ObjectTooBigPredicate {
int max;
ObjectTooBigPredicate(int m) : max(m) {}
bool operator()(const &Object obj) { return obj.value > max; }
}
void foo(const std::vector<Object> &objs) {
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(),
ObjectTooBigPredicate(5));
// *i is TOO BIG (compared to 5)!
lambdastruct Object {
int value;
};
void foo(const std::vector<Object> &objs) {
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(),
[](const Object &obj) {
return obj.value > ??;
});
// *i is TOO BIG!
lambdastruct Object {
int value;
};
void foo(const std::vector<Object> &objs) {
const int max = 5;
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(),
[max](const Object &obj) {
return obj.value > max;
});
// *i is TOO BIG (compared to 5)!
lambdastruct Object {
int value;
};
void foo(const std::vector<Object> &objs) {
size_t count = 0;
const int max = 5;
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(),
[max, &count](const Object &obj) {
if (obj.value > max) {
++count; return true;
}
return false;
});
log(“found: “ << count); // actually find_if doesn’t work this way
lambdastruct Object {
int value;
};
void foo(const std::vector<Object> &objs) {
size_t count = 0;
const int max = 5;
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(),
[&](const Object &obj) {
if (obj.value > max) {
++count; return true;
}
return false;
});
log(“found: “ << count); // actually find_if doesn’t work this way
lambdastruct Object {
int value;
};
void foo(const std::vector<Object> &objs) {
size_t count = 0;
const int max = 5;
std::function<bool(const Object &)> pred = [&](const Object &obj) {
if (obj.value > max) {
++count; return true;
}
return false;
}
std::vector<Object>::const_iterator i = std::find_if(objs.begin(), objs.end(), pred);
Element 1: Prefer lambda to bind
● std::bind is opaque to compiler optimizations● requires awkward placeholder notation● doesn’t do anything lambda can’t
Element 1: Prefer lambda to bind // turn a class method into a void function!
struct Foo {
void bar(int v) { log(“bar: ” + v); }
};
template<typename Function>
void baz(Function function) {
function();
}
//...
Foo foo;
baz([]{ foo.bar(5); }); // OK!
type deductionThe compiler already knows these types
bool foo(const std::vector<Object> &objs) {
typename std::vector<Object>::const_iterator i = objs.begin();
typename std::vector<Object>::const_iterator e = objs.end();
for (; i != e; ++i)
if (i->bar() == 5)
return false;
return true;
}
type deductionThe compiler already knows these types
auto foo(const std::vector<Object> &objs) {
auto i = begin(objs);
auto e = end(objs);
for (; i != e; ++i)
if (i->bar() == 5)
return false;
return true;
}
type deductionOr better yet
auto foo(const std::vector<Object> &objs) {
for (auto obj : objs)
if (obj.bar() == 5)
return false;
return true;
}
type deductionWorks well with STL and lambdas
auto predicate = [](const Object &obj) {
return obj.value > 5;
};
std::vector<Object> list = { a, b, c, d };
auto found = std::find_if(begin(list), end(list), pred);
if (found != end(list)) {
log(“found it!”);
}
Element 2: Prefer type deductionint foo();
//...
int main() {
size_t n = foo(); // DANGER
auto m = foo(); // OK
if (m) log(“it worked!”);
}
Element 2: Prefer type deductionObject foo();
//...
int main() {
size_t n = foo(); // ERROR
auto m = foo(); // OK
if (m) log(“it worked!”); // OK? conversion operator
}
Element 2: Prefer type deductionLooks good right?
void foo(const std::vector<Object> &objs) {
for (Object obj : objs) {
bar(obj);
}
}
Element 2: Prefer type deductionWhat if something changes?
void foo(const std::vector<Object *> &objs) {
for (Object obj : objs) { // ERROR
bar(obj);
}
}
Element 2: Prefer type deduction
void foo(const std::vector<Object *> &objs) {
for (auto obj : objs) { // LIFE IS SWEET
bar(obj); // depending on bar()
}
}
Element 2: Prefer type deduction
void foo(const std::vector<Object> &objs) {
for (const auto &obj : objs) {
bar(obj);
}
}
value semantics“There is a couple of things that make C++ unique among other contemporary mainstream programming languages. One of these things is value semantics. Value semantics is the programming style, or the way of thinking, where we focus on values that are stored in the objects rather than objects themselves. The objects are only used to convey values: we do not care about object’s identity.”
Andrzej Krzemieński
value semantics
● contrast with reference semantics○ needed with objects that have identity (actors)○ commonly found in object-oriented languages
● values are immutable and copied on assignment○ easy to reason about○ commonly found in functional languages
value semanticseasy to reason about
int foo(int v) {
v += 6;
return v;
}
int a = 5;
int b = foo(a);
log(“a: “ << a << “ b: “ << b);
value semanticseasy to reason about
int foo(int v) {
v += 6;
return v;
}
int a = 5;
int b = std::async(foo(a)).get();
log(“a: “ << a << “ b: “ << b);
value semanticseasy to reason about
std::string foo(std::string v) {
v += “6”;
return v;
}
std::string a = “5”;
std::string b = foo(a);
// a == “5”, b == “56”
value semanticseasy to reason about?
Object foo(Object obj) {
obj += 6;
return obj;
}
Object a = 5;
Object b = foo(a);
// a and b are independent values
value semanticscopy constructor/assignment
Object::Object(const Object ©) {
// copy over internal state.
}
Object &operator=(const Object ©) {
// copy over internal state.
}
value semanticseasy to reason about?
Object foo(Object &obj); // obj *is* a
Object a = 5;
Object b = foo(a);
// foo has side-effects on a
value semanticseasy to reason about??
Object *foo(Object *obj); // obj *is* a
Object *a = new Object(5);
Object *b = foo(a); // who is b??
// who manages these lifetimes?
value semantics● heap-based objects
○ have identity (memory address)○ cheap to move (pointer assignment)○ mutable by anyone who knows the address
● stack or register based objects○ no identity (stacks/registers go away)○ expensive to move (copy value)○ mutable only locally
value semanticsHeap allocated data + Stack allocated owner● std::vector, std::string, etc.● std::shared_ptr● copy-on-write data structures
C++03 Element: Prefer value semantics
Use stack-based objects to manage heap-based resources
move semanticsHeap allocated data + Stack allocated owner● std::vector, std::string, etc.● std::shared_ptr● copy-on-write data structures
What about std::unqiue_ptr, or std::thread??
move semanticswhat does this even mean?
Object obj; std::thread th1(foo(obj));
std::thread th2 = th1;
th2.join();
// have we spawned two threads?
// if so where is obj?
move semanticswhat does this even mean?
std::unique_ptr<Object> ptr1(new Object());
std::unique_ptr<Object> ptr2(ptr1);
// who deletes Object?
move semanticscan we make transfer of ownership explicit?
std::unique_ptr<Object> ptr1(new Object());
std::unique_ptr<Object> ptr2(std::move(ptr1));
// ptr2 deletes Object.
move semantics
1. enforces unique ownership2. avoids unnecessary copies
move semanticsmove constructor/assignment
Object::Object(Object &&move) {
// move over internal state.
}
Object &operator=(Object &&move) {
// move over internal state.
}
move semantics
std::move is a function that casts T to T&&
move semanticsavoid unnecessary copies
Object foo() {
Object bar; // default constructor
return bar;
}
Object obj(foo()); // copy in C++03
move semanticsavoid unnecessary copies
Object foo() {
Object bar; // default constructor
return bar;
}
Object obj(foo()); // move in C++11
move semanticsproblem: lots of new overloads!
Object::setBar(const Bar &bar); // copy read-only Bar
Object::setBar(Bar &&bar); // move temporary Bar
move semanticssolution: new parameter passing idiom
Object::setBar(Bar bar) { // copied or moved
mybar_ = std::move(bar); // no copy
}
Element 3: Understand move semantics
● Libraries will do most of the work for you● Libraries will expose move-only objects● Unique-owner implies move-only semantics● Move operators can save you copies● Understand parameter passing idiom
Element 3: Understand move semantics
Future APIs will look like
struct Foo {
void consume(Resource resource);
Resource produce();
void observe(const Object &object);
void share(std::shared_ptr<Object> actor);
void sink(std::unique_ptr<Object> slave);
};
smart pointers“Always use the standard smart pointers, and non-owning raw pointers. Never use owning raw pointers and delete. Use shared_ptr to express shared ownership, and use unique_ptr to express unique ownership. Prefer std::make_shared or std::make_unique to construct shared or uniquely-owned objects.”
[Paraphrased]Herb Sutter
Scott Meyers
smart pointers
● shared_ptr○ weak_ptr prevents cycles○ reference counted○ custom deleters○ thread-safe
● unique_ptr○ ownership semantics○ source/sink semantics○ automatic deletion
smart pointersavoid direct use of new/delete operators
auto sh1 = std::make_shared<Object>(a,b,c,d);
std::shared_ptr<Object> sh2 = sh1;
std::thread th(foo(sh2)); th.detach();
sh1.reset(); // sh1 no longer shares object
assert(sh1 == false);
sh2.reset(); // sh2 no longer shares object
// object will delete when thread completes
smart pointersweak_ptr
auto shared = std::make_shared<Object>(a,b,c,d);
std::weak_ptr<Object> weak = shared;
//...
auto upgraded = weak.lock();
if (!upgraded) log(“shared data expired!”);
smart pointersraw pointers can still be used to alias buffers
uint8_t buf[N];
auto p1 = &buf[i];
auto p2 = &buf[j];
swap(*p1, *p2);
memory modelSorry, this is a whole other talk!
conclusionCan anyone tell me what this does now?
Client MantisServer::CreateClient(const std::string &clientid, const std::string &streamid, const std::string &connectionid) {
std::shared_ptr<Client> client;
if (requestqueue_) {
requestqueue_->push({[=, &client](mantis::core::Server *server) {
client = server->CreateClient(clientid, streamid, connectionid);
}});
}
much moreThere’s much more to learn!
● https://isocpp.org/tour● http://herbsutter.com/elements-of-modern-c-style/● https://isocpp.org/blog/2012/10/c11-style-a-touch-of-class-bjarne-
stroustrup1● http://scottmeyers.blogspot.com/2014/07/free-excerpt-from-draft-emc-
now.html
Questions?