Skip to content

Latest commit

 

History

History
173 lines (127 loc) · 6.77 KB

2015-08-18.md

File metadata and controls

173 lines (127 loc) · 6.77 KB

C++

Focus on defaults

  • It's not dumbing the language down. It's about only do things otherwise when you have a good reason to do so. Don't optimize prematurely & don't overthink.
  • Optimal by default v.s. good(but not optimal) performance by default
  • Clear code is more important than optimal code. You don't always need to open the hood.

Range For

  • If you need to traverse every element in the collection, prefer range-for with auto & instead of iterators
  • for (e: c) are coming!

When to use smart pointers

  • Unique pointers are fairly cheap. Don't use owning *, new or delete. Use make_unique by default and make_shared if necessary, and you don't need to delete anymore.

When not to use smart pointers

  • Non-owning references/pointers(lifetime controlled by callers) are still great!

    void f(widget &w) {
      use(w);
    }
    
    void g(widget *w) {
      if (w) use(*w);
    }

    In modern C++, you pass them through *smart_pointer or smart_pointer.get(), but there's nothing wrong with the old pattern!

  • If you are not transferring ownership, just use the good old references/pointers for parameters, because the callee shouldn't care about if that thing is shared or unique anyway.

  • Anti-pattern: when you don't use the smart pointer properly:

    void f(refcnt_ptr<T> &w) {
      use(*w);  // why on earth do you pass that reference when it will be counted?
    }
    void f(refcnt_ptr<T> w) {
      use(*w);  // pass-by-value?!
      // You'll be incrementing the reference with every call, doesn't it hurt?
    }
  • Anti-pattern: copy reference counted pointers in loops

    refcnt_ptr<T> w = ...;
    for (...) {
      auto w2 = w;  // WTF?!
      use(w2);
    }
  • FB RocksDB improve 4x by changing pass-by-value shared_ptr to raw pointers/references

Ownership

  • Reference counted smart pointers are about managing owned object's lifetime. Only copy/assign them when you need to manipulate the ownership!
  • People say smart pointers/vectors hurt performance because they are bitten by code that pass-by-value or copy/assign in loops with them, when they don't even intend to manipulate their ownerships.

How to use unique_ptr

  • It's OK to return unique_ptr in factories(remember to use make_unique for optimization)
  • It't OK to pass unique_ptr by value when you are about to consumes them.
  • If you will or might change where a unique_ptr points to, it's OK to pass it by reference.
  • It's usually not right to pass const unique_ptr<widget> &. You should just pass a const widget &

How to use shared_ptr

  • It't OK to eturn a shared_ptr in factories(remember to use make_shared for optimization) when you know it will be shared.
  • If you want to share a shared_ptr(add one more reference count) to a function, you can pass it by value.
  • If you will or might change where a unique_ptr points to, it's OK to pass it by reference.
  • If you might share a shared_ptr, it's OK to pass const shared_ptr<widget> &

Pitfalls

Don't dereference a non-local, possibly aliased reference-counted pointer, because it could be released elsewhere.

shared_ptr<widget> g_p; // global or aliased local

void f(widget &w) {
  g();
  use(w);
}

void g() {
  g_p = ... // reseat it
}

void my_code() {
  f(*g_p); // WARNING! g() might reseat `g_p`, releasing it, and then f() will dereference it with `use(w)`
  // or
  g_p->foo();
}

How to do it properly? Pin it with an unaliased local copy so you can be sure it won't be released when dereferencing it. When you need to dereference a global smart pointer, copy it locally first.

void my_code() {
  auto pin = g_p;  // +1 ref count!
  f(*pin);  // now pin is local
  // or
  pin->foo();
}

Principles

  1. Never pass smart pointers(by value/reference) unless you want to manipulate its ownership
  2. Use unique_ptr for ownership whenever possible, even when you don't know if it will be shared
  • free, safe, declarative, no ref count headaches
  1. Else use make_shared up front when you know it will be shared. Don't dereference global shared_ptr, make a local copy before you do that.

Don't be scared by auto

  • A good IDE(even emacs) can help you see the actual type when you are coding, so don't be afraid of losing track of the types.
  • auto makes you focus on interfaces, not implementations
  • Templates/temporaries already ignore actual types
  • With deduction, you always get the exact right type without accidentally changing modifiers/constness or getting implicit conversions
  • Less typing LOL
  • No accidental narrowing on literals(e.g. 42.0f, 42ul)
  • The signatures will be on the right. Most of the times auto force you to put any possible conversions explicitly on the right hand side. If you put it on the left hand side(usually it's what you do when you don't use auto), people will keep NOT seeing it.
  • = doesn't always mean copy/assignment in C++. That's a quirk in the standard -- it could mean initialization.
  • If you do auto x = something, something will be moved instead of copied. But if it's not movable, there could be an error(and remember, containers are expensive to move).

So, no implicit conversions/temporaries, no narrowing, no uninitialized variables!

RVO and Move semantics

In C++ 98

  • Cheap costs(e.g. a few ints) to copy: return-by-value and pass-by-value
  • Moderate costs(e.g. ~1KB contiguous)/unknown costs to copy: rely on RVO, pass by const references
  • Expensive to copy(e.g. vectors, BigPOD[]): use references all the way through

Modern C++

  • For cheap costs/impossible to copy(e.g. unique_ptr, return-by-value and pass-by-value(will trigger move semantics if necessary)
  • For moderate costs/cheap to move/unknown costs(e.g. vector, array<vector>, BigPOD), rely on RVO, pass by const references
  • Expensive to move(e.g. BigPOD[], array<BigPOD>), use references all the way through

So in more cases you just need RVO and const references.

  • If you want to optimize for rvalues, add an overload for &&
  • For constructors, pass-by-value and then move might still be a good option, but usually just stick to const &
  • T && is forwarding reference(because it doesn't care about T is const or whatnot)

Multiple return values

C++ 98: pairs

pair<set<string>::iterator, bool> result = func();
if (result.second) use(result.first);

C++ 11 with backward compatibility

auto result = func();
if (result.second) use(result.first);

C++ 11 new syntax

// declare iterator and success earlier...don't forget to initialize them!
tie(iterator, success) = func();
if (success) use(iterator)