Unblocking The Main Thread Solving ANRs and Frozen Frames
C++11 talk
1. Talk Overview
● C++11 features
● Smart pointer family
● Rvalue references
● Thread Api
2. I. C++11 Smart Pointers
● auto_ptr deprecated
● New types
● unique_ptr
● shared_ptr / weak_ptr
● Automatic garbage collection
● Use RAII idiom
● Exception safe
● STL container safe
● Work with dynamically created arrays
● Pointer semantics
3. unique_ptr details
● auto_ptr replacement
● No copy semantics allowed
● Strict single ownership usage
● Uses 'move semantics' to transfer ownership
● Restricted usage in STL containers
● Custom deleter support
4. unique_ptr example 1
void doSomethingWithFoo(std::unique_ptr<Foo> fooPtr) {
fooPtr->doSomething();
} // fooPtr automatically destructed at the end of the scope.
int main(int argc, char* argv[]) {
// Foo* foo(new Foo());
// std::unique_ptr<Foo> fooPtr1(foo); // Don't do this.
// std::unique_ptr<Foo> fooPtr2(foo); // A foo pointer must be owned by single unique_ptr.
std::unique_ptr<Foo> fooPtr(new Foo()); // Use RAII always.
…
doSomethingRisky(); // Exception safe, foo cleanup on stack unwind.
...
// doSomeThingWithFoo(fooPtr); // Compiler error, cannot use copy constructor.
doSomethingWithFoo(std::move(fooPtr)); // Transfer ownership using explicit 'move'.
// Shouldn't access the fooPtr members after the move.
...
}
5. unique_ptr example 2
typedef std::unique_ptr<Foo> FooUniquePtr;
void doSomethingWithReference( FooUniquePtr& fooPtr) {
fooPtr->doSomething();
}
int main(int argc, char* argv[]) {
FooUniquePtr fooPtr1(new Foo(1));
FooUniquePtr fooPtr2(new Foo(2));
doSomethingWithReference(fooPtr1); // Passing a reference not ownership.
std::vector<Foo*> foos;
foos.push_back(fooPtr1.get()); // still own, passing just a pointer not ownership.
foos.push_back(fooPtr2.get()); // ditto
fooPtr1.reset(new Foo(3)); // Foo(1) gets destructed.
fooPtr1 = std::move(fooPtr2); // move foo(2) into fooPtr1, causes Foo(3) destr.
std::vector< FooUniquePtr> fooPtrs;
fooPtrs.push_back(std::move(fooPtr1)); // Transfer ownership to the container.
}
6. shared_ptr details
● Multi/shared ownership usage
● Thread safe (similar to primitives)
● Custom destructor support
● Susceptible to cyclic references memory leak
● Uses copy semantics
● Uses an atomic reference counter internally
7. shared_ptr example
typedef std::shared_ptr<Foo> FooSharedPtr;
void doSomething(FooSharedPtr fooPtr) {
fooPtr->doSomething();
}
int main() {
// Foo* foo(new Foo(1)); // Don't do this.
// FooSharedPtr fooPtr1(foo); // The foo pointer is 'exclusively' shared-owned by
// FooSharedPtr fooPtr2(foo); // the two shared pointers that don't know each other.
FooSharedPtr fooPtr1(new Foo(1));
FooSharedPtr fooPtr2(fooPtr1); // foo1 pointer is 'shared owned' by the two sharedPtrs.
FooSharedPtr fooPtr3;
fooPtr3 = fooPtr2; // foo1 pointer is 'shared owned by the 3 sharedPtrs.
fooPtr2.reset(new Foo(2)); // foo1 is shared by fooPtr1, fooPtr3 only.
doSomething(fooPtr3); // foo1 is Shared by fooPtr1, fooPtr2 and the param.
} // Both foo1, foo2 are both destroyed at the program exit.
8. shared_ptr variants
● Custom deleter variant
● Exception-safe way of managing OS
resources, custom memory blocks etc
● e.g.
int main() {
FILE* fp = fopen("/tmp/temp.txt", "rw");
std::shared_ptr<File, std::function<void (File*)> filePtr(fp, [ ] (File* fp) { fclose(fp);});
fseek(filePtr.get(), 42, SEEK_SET);
// Do something else with the file
}
● make_shared variant
● Single memory allocation
● Requires a default constructor for T
● No facility for custom deleter specification
● Sample syntax: std::shared_ptr<Foo> = std::make_shared<Foo>();
9. shared_ptr gotcha
Beware of shared_ptr cyclic references
e.g.
typedef std::shared_ptr<Chicken> ChickenPtr;
typedef std::shared_ptr<Egg> EggPtr;
struct Chicken { EggPtr source_;};
struct Egg { ChickenPtr source_;};
void memoryLeakDemo1() {
{
ChickenPtr chickenPtr(new Chicken());
EggPtr eggPtr(new Egg());
chickenPtr->source_ = eggPtr;
eggPtr->source_ = chickenPtr; // The cycle completes
}
// Memory leak here: Both go the Chicken and egg go out of scope here
// and they are not garbage collected as each have reference to the other.
}
10. weak_ptr
● Observes the shared_ptr without affecting its
lifetime
● Has to do null check on shared_ptr before
using it
typedef std::shared_ptr<Chicken> ChickenPtr;
typedef std::shared_ptr<Egg> EggPtr;
struct Chicken { std::weak_ptr<Egg> source_;};
struct Egg { std::weak_ptr<Chicken> source_;};
void memoryLeakDemo2() {
{
ChickenPtr chickenPtr(new Chicken());
EggPtr eggPtr(new Egg());
chickenPtr->source_ = eggPtr;
eggPtr->source_ = chickenPtr; // No shared_ptr cycle here.
}
// The Chicken and egg are garbage collected here.
}
11. When to use which
● Use unique_ptr when
● by default
● the single ownership usage is clear
● implementing a pImpl idiom
● Use shared_ptr when
● the object needs to be shared
● not sure of the ownership details
● need flexibility in using STL containers
● using a pre-C++11 compiler
● Prefer make_shared variant whenever possible
12. II. Rvalue references
● Core language runtime feature in C++11
● Tackles two problems
● Move semantics
● Perfect forwarding
● Syntactic symbol &&
13. What is an Rvalue(Simple Definition)
● An rvalue can only occur on the right side of an
assignment(C world)
● Some examples
int a = 42;
int b = 43;
// a and b are both l-values:
a = b; // ok
b = a; // ok
a = a * b; // ok
// a * b is an rvalue:
int c = a * b; // ok, rvalue on right hand side of assignment
a * b = 42; // error, rvalue on left hand side of assignment
14. What is an Rvalue(C++ Definition)
● Rvalue: an expression that is not an lvalue
● Lvalue: any expression that refers to a
memory location
● Some examples
int i = 42;
i = 43; // ok, i is an lvalue
int* p = &i; // ok, i is an lvalue
int& foo(); // return value has a memory location.
Foo() = 42; // ok, foo() is an lvalue
int* p1 = &foo(); // ok, foo() is an lvalue
// rvalues:
int foobar(); // return value is a temporary.
int j = 0;
j = foobar(); // ok, foobar() is an rvalue
int* p2 = &foobar(); // error, cannot take the address of an rvalue
j = 42; // ok, 42 is an rvalue
15. Motivation 1
● Eliminate deep copies on temporaries
● Optimize certain pass-by-value cases
● Some container operation optimizations
● Give more flexibility to the programmer
● Solve all the above problems using move
semantics
16. Move Semantics Example 1
● C++98 template swap method
template <typename T>
void swap(T& a, T& b) {
T temp(a); // Two copies of a. what if a is very expensive to build?
a = b; // Two copies of b.
b = temp; // Two copies of a again.
}
● C++11 template swap method uses std::move
● Std::move – Converts an lvalue to rvalue
template <typename T>
void swap(T& a, T& b) {
T temp(std::move(a)); // One copy of a if T has a move constructor.
a = std::move(b); // One copy of b if T has a move assignment operator.
b = std::move(temp); // One of copy of b.
}
17. Example 1 cont...
class SomeT {
public:
...
// Move constructor.
SomeT(SomeT&& other)
: obj_(other.obj_) // Copy the pointer.
{ other.obj_ = null;
other.obj_ = nullptr; // Make sure the other's obj_ is set to NULL
}
// Move assignment operator.
SomeT& operator=(SomeT&& other) {
// steal the member fields.
std::swap(obj_, other.obj_);
}
...
private:
SomeLargeObject* obj_; // Some large object that is expensive to copy.
}
18. Example 1 more code...
// A funtion that returns SomeT by value.
SomeT methodThatReturnsByValue();
int main()
{
SomeT some_t;
...
// Compiler will automatically invoke the move assignment operator
// instead of the copy assignment since it is an rvalue.
some_t = methodThatReturnsByValue();
std::vector<some_t> some_ts;
some_ts.reserve(4); // Lets assume the vector has a capacity of 4.
for (int k = 0; k < 100; ++k) // Should cause some internal vector re-sizing calls.
some_ts.push_back(some_t()); // Efficient since the SomeT has 'move' constructor.
}
19. Rvalue Gotchas
● An rvalue parameter inside a method/function
is an lvalue
● Don't re-use an object that is moved
class Foo {
...
Foo(Foo&& other)
// 1. must explicitly call std::move on bar otherwise invokes bar copy constructor.
: bar(std::move(other.bar))
{
// 2. Shouldn't access other.bar here as it is moved.
}
...
private:
Bar bar; // Implements the move constructor and assignment operator
}
20. Perfect forwarding
● Used to preserve the original argument type
across function calls
● Uses std::forward<T> to preserve the calling
type
● Works only on template functions and auto
situations
● e.g.
// Binds both rvalue and lvalue references
template<typename T>
void f(T&& param);
// auto declarations:
auto&& var = ... ;
21. Need for perfect forwarding
● Template factory methods that need to forward
arguments
● e.g.
Problem:
template<typename T, typename Arg>
std::shared_ptr<T> factory(Arg arg) {
// Passed by value even if the arg is a ref and T has a constructor that takes reference
return std::shared_ptr<T>(new T(arg));
}
Solution:
// Matches both rvalue and lvalue references.
template<typename T>
std::shared_ptr<T> factory(Arg&& arg) {
// preserves the arg type to the T constructor.
return std::shared_ptr<T>(new T(std::forward<Arg>(arg)));
}
22. Rvalue refs: Do you need to care
● Use smart pointers to avoid object copies
● Compiler optimization of temporary copies
● STL Container emplace methods
● Compiler upgrade: More efficient code out-of-
box
23. III. C++11 Thread Api
● Thread-aware memory model
● Mostly compatible with boost thread api
● Support for atomics, mutexes and locks
● std::async, futures, packaged_tasks
and promises
● Conditional variables
● Thread-local storage
● Thread-safe Initialization (objects w/static
storage, std::call_once)
24. Thread class
● RAII idiom to create an TOE
● Works with any callable type
● Beware of the destructor (must call either of
join() or detach() prior)
● Movable but not copyable
● Example syntax:
void printInt(int x) { std::count << “x = “ << x << std::endl; }
int main() {
…
int x = 10;
std::thread t(printInt, x);
… // Beware of any exceptions here.
t.join();
}
25. Async method
● Higher level template method abstraction
● Returns the result via a future object
● Two different implementations with similar API
int funcToRun();
int main() {
std::future<int> f = std::async(funcToRun);
…
int result = f.get(); // get the result now.
}
// funcToRun is executed per a launch policy:
// Default: Implementation chooses one
// std::launch::async: Run asynchronously.
// std::launch::deferred: Run synchronously if f.get() called.
26. packaged_task class
● High level template class abstraction
● Encapsulates a callable type
● Has a method that returns a future
● Can be launched on the same thread or a
different one
● Makes thread-pool implementations easier
27. packaged_task example
#include <iostream>
#include <future>
#include <thread>
int main()
{
std::packaged_task<int()> task([](){return 7;}); // pass lambda
std::future<int> result = task.get_future(); // get a future
std::thread(std::move(task)).detach(); // launch on a thread
std::cout << "Waiting...";
result.wait();
std::cout << "Done!nResult is " << result.get() << 'n';
}
28. Thread-safe initialization
● Singleton pattern support using call_once
void init_resource(Resource* rptr) { rptr = new Resource(); }
std::once_flag resource_flag;
Resource* r_ptr;
void foo() {
std::call_once(resource_flag, init_resource, r_ptr); // thread-safe once initialization
r_ptr->do_something();
}
● Thread-safe local static variable initialization
class MyClass;
MyClass& getInstance() {
static MyClass instance; // Thread-safe in C++11
return instance;
}
29. Thread Api Gotcha
● Arguments passed to std::thread, std::async, and std::call_once
are unconditionally copied to thread private area
std::thread t(f, arg1, arg2, arg3, ...); // copy of f is called with copies of args.
auto fut = std::async(f, arg1, arg2, arg3, ...); // same as above
std::call_once(flag, f, arg1, arg2, arg3, ...); // ditto
● What does this mean?
void processData(const SensorData& d); // fn that takes Pass-by-reference
int main() {
SensorData *pd = new (SensorAddr) SensorData;
std::thread t(processData, *pd); // doesn't work as expected
…
t.join();
}
30. How You Work Around It
● Use Lambdas with by-reference captures
// closure copied, but it holds refs to args
std::thread t([&]{ return f(arg1, arg2, arg3, ...); });
● Arguments wrapped via std::ref and std::cref
// Use ref wrappers to explicitly tell the intent
auto fut = std::async( f, std::ref(arg1), arg2, std::cref(arg3), ...);