Modern C++ API Design
Modern C++ API Design
A Refresher on Rvalue-References
How to use Rvalue-References in API Design
How to use those APIs in Type Design
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
A reference to an rvalue
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
What is an rvalue?
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
int GetInt();
int foo = GetInt();
GetInt() = foo;
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
int& GetInt();
int foo = GetInt();
GetInt() = foo;
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
void f() {
GetStrings(); // <- ???
}
Confidential + Proprietary
Refresher: rvalue refs
void f() {
AcceptStrings(GetStrings());
}
Confidential + Proprietary
Refresher: rvalue refs
void f() {
std::vector<std::string> strings = GetStrings();
}
Confidential + Proprietary
Refresher: rvalue refs
void f() {
std::vector<std::string> strings = GetStrings();
auto more_strings = strings;
}
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
What is std::move?
A cast to rvalue-reference.
Confidential + Proprietary
Refresher: rvalue refs
What is std::move?
“A name eraser”
Confidential + Proprietary
Refresher: rvalue refs
void f() {
std::vector<std::string> strings = GetStrings();
auto more_strings = std::move(strings);
}
Confidential + Proprietary
Refresher: rvalue refs
void ZeroNamesIsATemporary() {
AcceptsStrings(GetStrings());
}
void OneNameIsAMove() {
std::vector<std::string> strings = GetStrings();
}
void TwoNamesIsACopy() {
std::vector<std::string> strings = GetStrings();
auto copy = strings;
}
void AndStdMoveMakesANameNotCount() {
std::vector<std::string> strings = GetStrings();
auto not_a_copy = std::move(strings);
}
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
class Foo {
public:
Foo(const Foo&); // copy c'tor
Foo(Foo&&) noexcept; // move c'tor
Confidential + Proprietary
Refresher: rvalue refs
Foo::Foo(Foo&& other)
: member_(std::move(other.member_)) noexcept {}
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
template <typename T, typename... Args>
typename memory_internal::MakeUniqueResult<T>::scalar make_unique(
Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
Confidential + Proprietary
Refresher: rvalue refs
Confidential + Proprietary
Refresher: rvalue refs
void f() {
Foo f;
f.Print();
std::move(f).Print();
}
Confidential + Proprietary
A Talk in Three Parts
A Refresher on Rvalue-References
How to use Rvalue-References in API Design
How to use those APIs in Type Design
Confidential + Proprietary
Good Uses for Rvalue-Refs
Optimization: const-ref + rvalue-ref overload set
Confidential + Proprietary
Good Uses for Rvalue-Refs
Optimization: Ref qualified member function overload set
Confidential + Proprietary
Good Uses for Rvalue-Refs
Ref qualified member function - Rvalue ref qualified means “steal”
std::stringbuf buf;
buf << "Hello World!";
return buf.str();
Confidential + Proprietary
Good Uses for Rvalue-Refs
Ref qualified member function - Rvalue ref qualified means “steal”
std::stringbuf buf;
buf << "Hello World!";
return std::move(buf).str();
Confidential + Proprietary
Good Uses for Rvalue-Refs
Or rvalue ref qualified means ”do once”.
std::mfunction<int(std::string)> GetCallable();
void f() {
GetCallable()("Hello World!");
}
Confidential + Proprietary
Good Uses for Rvalue-Refs
Or rvalue ref qualified means ”do once”.
void f(std::mfunction<int(std::string)> c) {
std::move(c)("Hello World!");
}
Confidential + Proprietary
Good Uses for Rvalue-Refs
As a parameter, when not an overload set: “maybe move”.
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “because optimization”
or
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, in a deleted member of an overload set, to “prevent passing
temporaries.”
Foo f("Hello");
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, in a deleted member of an overload set, to “prevent passing
temporaries.”
{
std::string hello = "Hello";
Foo f(hello);
}
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, in a deleted member of an overload set, to “prevent passing
temporaries.”
{
std::string hello = "Hello";
auto f = make_unique<Foo>(hello);
}
Confidential + Proprietary
C++ 11 and on: New Type Designs
Other move-semantics designs:
Confidential + Proprietary
A Talk in Three Parts
A Refresher on Rvalue-References
How to use Rvalue-References in API Design
How to use those APIs in Type Design
Confidential + Proprietary
Properties of types
● Invariants
● Thread safety
● Comparable
● Ordered
● Copyable
● Mutable
● Movable
Confidential + Proprietary
Properties of types - Invariants
Type design is really "What invariants are there on the data members of a T?"
Confidential + Proprietary
Properties of types - Invariants
Invariants also involve the state model for your type (if any).
● Prefer factory functions or c’tors that throw, rather than T::Init() methods.
● Avoid distinct moved-from states.
Confidential + Proprietary
Properties of types - Thread Safety
Which operations are safe to call upon a T concurrently?
● thread-safe:
○ Concurrent const and non-const operations are OK
● thread-compatible:
○ Concurrent const operations are OK.
○ Any non-const operation requires all operations to synchronize
● thread-unsafe:
○ Not even const operations can be invoked concurrently
Confidential + Proprietary
Properties of types - Comparability
Are operators == and != defined?
Confidential + Proprietary
Types - Logical State
There may be a difference between the data members and the logical state of a type.
std::string a = "abc";
std::string b;
b.reserve(1000);
b.push_back('a');
b.push_back('b');
b.push_back('c');
assert(a == b);
Confidential + Proprietary
Properties of types - Ordering
Is there a partial or total order for objects of type T?
Which of the operators ==, !=, <, >, <=, and >= are defined?
Confidential + Proprietary
Properties of types - Ordering
Don’t define Ordering just to put something in a map. If you need a sort order for
storage, that’s a property of the storage, not the type.
Confidential + Proprietary
Properties of types - Copyable
Given a T, can you duplicate its logical state into a new T?
Confidential + Proprietary
Properties of types - Mutable
Given a T, can you modify its logical state? In particular, can you modify its state via
operator=?
Confidential + Proprietary
Properties of types - Movable
Given a T, can you move its logical state into a new T?
Confidential + Proprietary
Properties of types - Movable
Given a T, can you move its logical state into a new T?
T Foo();
T a = Foo();
Confidential + Proprietary
Regular Types
AKA “value” types - “do what ints do”
● Thread-compatible
● Comparable and ordered
● Copyable, assignable, movable
● Moved-from state?
Example: std::string
Confidential + Proprietary
Structs
Types with no data invariants
Example: std::pair
Confidential + Proprietary
Non-Copyable / Business logic types
These are usually blocks of business logic that hold accessors / handles / streams
and perform some business-logic permutations.
● Non-copyable
● Usually non-movable
● Incomparable / unordered
Confidential + Proprietary
Immutable Types
In situations where an object is shared across many threads concurrently, it may be
preferable for all objects of that type to be immutable (after construction).
● Potentially copyable
● Immutable
● Not movable
Confidential + Proprietary
Reference types
Non-owning, lightweight types that may become invalid because of external
changes.
Confidential + Proprietary
Move-only types
If your type needs to uniquely represent some resource, move-only semantics may
be a good model.
● non-copyable
● Data invariants are guaranteed
Example: std::unique_ptr
Confidential + Proprietary
What’s Next?
● Google Style Guide
● Abseil Tip of the Week
● Updated Core Guidelines?
Confidential + Proprietary
A Talk in Three Parts
Questions?
Confidential + Proprietary