Modern C++ API Design
Rvalue references and modern type design
Confidential + Proprietary 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
Refresher: rvalue refs
What is an rvalue ref?
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue ref?
A reference to an rvalue
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
Something you could only have on the right side of
an assignment.
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue?
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
What is an rvalue?
int& GetInt();
int foo = GetInt();
GetInt() = foo;
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue ref, informally?
Confidential + Proprietary
Refresher: rvalue refs
What is an rvalue ref, informally?
(Usually) A value without a name, that you couldn’t
print in a debugger.
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
What is an rvalue ref, informally?
(Sometimes) An lvalue that was std::move’ed.
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
What’s a move c’tor/move assignment op?
Confidential + Proprietary
Refresher: rvalue refs
What’s a move c’tor/move assignment op?
How a type implements move semantics.
Confidential + Proprietary
Refresher: rvalue refs
class Foo {
public:
Foo(const Foo&); // copy c'tor
Foo(Foo&&) noexcept; // move c'tor
Foo& operator= (const Foo&); // copy
Foo& operator= (Foo&&) noexcept; // move
};
Confidential + Proprietary
Refresher: rvalue refs
What’s a move c’tor/move assignment op?
Move is a source-mutating copy
Confidential + Proprietary
Refresher: rvalue refs
Foo::Foo(Foo&& other)
: member_(std::move(other.member_)) noexcept {}
Foo& Foo::operator= (Foo&& other) noexcept {
member_ = std::move(other.member_);
return *this;
}
Confidential + Proprietary
Refresher: rvalue refs
What’s a forwarding reference?
Confidential + Proprietary
Refresher: rvalue refs
What’s a forwarding reference?
How you express in templates “take whatever
category this was and keep it the same.”
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
What’s reference qualification?
Confidential + Proprietary
Refresher: rvalue refs
What’s reference qualification?
Like const-qualification on a method: restrict calls
to a method based on the reference-category of
the object.
Confidential + Proprietary
Refresher: rvalue refs
class Foo {
public:
void Print() & { cout << "lvalue" << endl; }
void Print() && { cout << "rvalue" << endl; }
};
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
void std::vector<T>::push_back(const T&);
void std::vector<T>::push_back(T&&);
These are everywhere in the standard
Confidential + Proprietary
Good Uses for Rvalue-Refs
Optimization: Ref qualified member function overload set
T& std::optional<T>::value() &;
const T& std::optional<T>::value() const &;
T&& std::optional<T>::value() &&;
const T&& std::optional<T>::value() const &&;
Translation: no matter the const-ness or reference category of the optional, give
me the same version of the underlying T.
Confidential + Proprietary
Good Uses for Rvalue-Refs
Ref qualified member function - Rvalue ref qualified means “steal”
std::string std::stringbuf::str() const;
std::string std::stringbuf::str() &&;
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::string std::stringbuf::str() const;
std::string std::stringbuf::str() &&;
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”.
Consider a call-once, move-only Callable:
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”.
Consider a call-once, move-only Callable:
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”.
The proposed RCU type (wg21.link/P0561) has
bool try_update(const snapshot_ptr<T>& expected,
std::unique_ptr<T>&& desired);
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
void Expensive(std::string&& big);
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
void Expensive(std::string&& big);
std::string my_data = GetData();
Expensive(std::move(my_data));
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “disallow copies”
void Expensive(std::string&& big);
std::string my_data = GetData();
Expensive(std::move(my_data));
Expensive(std::move(my_data));
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, when not an overload set: “because optimization”
void Cheap(std::string s);
or
void Cheap(const std::string& s);
void Cheap(std::string&& s);
Confidential + Proprietary
Bad Uses for Rvalue-Refs
As a parameter, in a deleted member of an overload set, to “prevent passing
temporaries.”
Foo(const std::string& s);
Foo(std::string&& s) = delete;
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.”
Foo(const std::string& s);
Foo(std::string&& s) = delete;
{
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.”
Foo(const std::string& s);
Foo(std::string&& s) = delete;
{
std::string hello = "Hello";
auto f = make_unique<Foo>(hello);
}
Confidential + Proprietary
C++ 11 and on: New Type Designs
Other move-semantics designs:
● move-only types/unique ownership: std::unique_ptr
Types that are less-Regular (std::string_view)
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?"
std::vector has invariants like:
● capacity >= size
● data[i] is a valid T for all i in [0, size)
● data is a valid / non-null pointer with an allocation of capacity
Confidential + Proprietary
Properties of types - Invariants
Invariants also involve the state model for your type (if any).
Avoid adding states if possible.
● 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.
Ordering depends on the logical state of the type.
Confidential + Proprietary
Properties of types - Copyable
Given a T, can you duplicate its logical state into a new T?
There are two important constraints for copyable types:
● If it is copy-assignable (operator=) it should be copy-constructible (a copy
constructor). In most cases the reverse is also true.
● The logical state is what is copied.
T a = b;
assert(a == b);
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?
std::is_move_constructible is equivalent to the following being well-formed:
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.
Good for parameters, lightweight representations.
Tricky semantics: careful review of type design strongly suggested.
Example: std::string_view, gsl::span/absl::Span
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