C++11 Idioms @ Silicon Valley Code Camp 2012
-
Upload
sumant-tambe -
Category
Technology
-
view
36.623 -
download
0
description
Transcript of C++11 Idioms @ Silicon Valley Code Camp 2012
Sumant Tambe, Ph.D. Senior Software Research Engineer Real-Time Innovations, Inc.
http://cpptruths.blogspot.com
Author
Blogger
Library Developer
Definitions taken from Answers.com and Google.com
This talk is about programming language idioms
» Idioms ˃ Specific grammatical, syntactic, structural
expressions in a given programming language to solve an implementation problem
˃ Often have names—create vocabulary like patterns
˃ Repeat themselves
˃ Often cannot be modularized as libraries
˃ May get promoted as first-class features as a language evolves
» Idioms vs. Patterns ˃ Idioms are patterns specific to a language
˃ Patterns are language independent
» Idioms of Using C++11 Rvalue References
» C++ Truths ˃ Rvalue References In Constructor: When Less Is More
˃ Perfect Forwarding of Parameter Groups in C++11
» A Sense of Design – Boris Kolpackov ˃ Efficient Argument Passing in C++11 (Parts 1, 2, and 3) (with permission)
class Book { // .... private: std::string _title; std::vector<std::string> _authors; std::string _publisher; size_t _pub_year; };
class Book { public: Book(const std::string & title, const std::vector<std::string> & authors, const std::string & pub, const size_t & pub_year); // .... private: std::string _title; std::vector<std::string> _authors; std::string _publisher; size_t _pub_year; };
Good old C++03 constructor
» Other alternatives ˃ An iterator-pair instead of const std::vector<std::string>&
˃ but lets keep it simple
class Book { public: Book(const std::string & title, const std::vector<std::string> & authors, const std::string & pub, const size_t & pub_year) : _title(title), _authors(authors), _publisher(pub), _pub_year(pub_year) {} private: std::string _title; std::vector<std::string> _authors; std::string _publisher; size_t _pub_year; };
Constructor parameters
make copies in *this
Is this class move enabled?
class Book { public: Book(const std::string & title, const std::vector<std::string> & authors, const std::string & pub, const size_t & pub_year); Book (const Book &) = default; // Copy Constructor Book & operator = (const Book &) = default; // Copy Assign Book (Book &&) = default; // Move Constructor Book & operator = (Book &&) = default; // Move Assign ~Book() = default; // Destructor private: std::string _title; std::vector<std::string> _authors; std::string _publisher; size_t _pub_year; };
No need to write these declarations
Compiler will provide them
where appropriate
But, I Just READ About Rvalue References!
class Book { public: Book(const std::string & title, const std::vector<std::string> & authors, const std::string & pub, const size_t & pub_year); // Old c-tor Book(std::string && title, std::vector<std::string> && authors, std::string && pub, size_t && pub_year) : _title(std::move(title)), _authors(std::move(authors)), _publisher(std::move(publisher), _pub_year(std::move(pub_year)) {} // ... Members not shown };
Constructor with C++11 rvalue references
Is it now optimally move enabled?
class Book { public: Book(const std::string & title, const std::vector<std::string> & authors, const std::string & pub, const size_t & pub_year); // Old constructor Book(std::string && title, std::vector<std::string> && authors, std::string && pub, size_t && pub_year) // New constructor : _title(std::move(title)), _authors(std::move(authors)), _publisher(std::move(publisher), _pub_year(std::move(pub_year)) {} }; int main(void) { std::vector<std::string> authors { "A", "B", "C" }; Book b1("Book1", authors, "O’Reilly", 2012); const size_t year = 2012; Book b2("Book1", { "Author" }, "O’Reilly", year); Book b3("Book", { "Author" }, "O’Reilly", 2012); // Calls New Ctor }
Calls old constructor!
Calls old constructor!
Book::Book(std::string && title, std::vector<std::string> && authors, std::string && pub, size_t && pub_year); // New constructor int main(void) { std::vector<std::string> authors { "A", "B", "C" }; Book b1("Book1", authors, "O’Reilly", 2012); const size_t year = 2012; Book b2("Book1", { "Author" }, "O’Reilly", year); Book b3("Book", { "Author" }, "O’Reilly", 2012); }
lvalue
lvalue
No lvalue
» Even one incompatible parameter (lvalue) will cause the compiler to reject the new rvalue-only constructor
title authors pub year
string && vector<string> && string && size_t
string && vector<string> && const string & size_t
string && const vector<string> & string && size_t
string && const vector<string> & const string & size_t
const string & vector<string> && string && size_t
const string & vector<string> && const string & size_t
const string & const vector<string> & string && size_t
const string & const vector<string> & const string & size_t
» Fortunately an optimal solution exists!
» But has a small problem 8 constructors!
˃ Implementations of all the constructors are different!
˃ Each Rvalue reference object must be std::moved
˃ In general exponential number of constructors
class Book { public: Book(std::string title, std::vector<std::string> authors, std::string pub, size_t pub_year) : _title (std::move(title)), _authors (std::move(authors)), _publisher(std::move(pub)), _pub_year (std::move(pub_year)) {} };
» Help the compiler take the best decision ˃ Pass-by-value!
˃ std::move each parameter
˃ Compiler makes no more copies than absolutely necessary
˃ Copy-elision may avoid moves too!
Only one constructor
class Book { public: Book(std::string title, std::vector<std::string> authors, std::string publisher, size_t pub_year) : _title (std::move(title)), _authors (std::move(authors)), _publisher(std::move(publisher)), _pub_year (pub_year) {} }; std::string publisher() { // ... } int main(void) { std::vector<std::string> authors { "A", "B", "C" }; Book b1("Book1", authors, publisher(), 2012); }
ctor + 1 move
Copy-ctor + 1 move
2 moves 2 copies
lvalue
» Move is not free!
˃ Potentially 5 to 15 % performance degradation
˃ Benchmark!
» Many types don’t have efficient move operations
˃ std::array, std::complex, etc.
˃ Many C++03 libraries have not caught up with C++11
+ Compiler will not provide implicit move operations (in most cases)
» Move may be same as copy!
˃ Move construction Use move-ctor if available, otherwise use copy-ctor!!
˃ Move==Copy when strings are small (small string optimization)
» Works best only when you know you are going to make a copy
A sense of design
» matrix is movable (efficiently)
» matrix is copyable too
» operator + makes a copy
˃ Lets try to save it if we can!
matrix operator+ (const matrix& x, const matrix& y) { matrix r (x); r += y; return r; }
matrix operator+ (matrix&& x, const matrix& y) { matrix r (std::move(x)); r += y; return r; } matrix a, b; matrix c = a * 2 + b; matrix d = a + b * 2;
Look ma! No Copy!
Works great!
Oops!
matrix operator+ (const matrix& x, const matrix& y) { matrix r (x); r += y; return r; } matrix operator+ (const matrix& x, matrix&& y) { matrix r (std::move(y)); r += x; return r; } matrix operator+ (matrix&& x, const matrix& y) { matrix r (std::move(x)); r += y; return r; } matrix operator+ (matrix&& x, matrix&& y) { matrix r (std::move(x)); r += y; return r; }
ARE YOU KIDDING
ME?
In general, exponential number of implementations
» Pass-by-value!
matrix operator+ (matrix x, matrix y) { matrix r (std::move(x)); r += y; return r; } matrix a, b; matrix c = a*2 + b*4; matrix d = a + b*4; matrix e = a*2 + b; matrix f = a + b;
Works great!
Two Unnecessary Moves
One Unnecessary Copy and Move
We need a way to detect lvalue/rvalue at runtime
» Pass original type to a forwarded function ˃ Forward lvalues as lvalues
˃ Forward const lvalues as const lvalues
˃ Forward rvalues as rvalues
» Works very well with factory functions
struct X { ... }; void g(X&& t); // A void g(X& t); // B template<typename T> void f(T&& t) { g(std::forward<T>(t)); } int main() { X x; f(x); // Calls B f(X()); // Calls A }
template <class Arg> Blob * createBlob(Arg&& arg) { return new Blob(std::forward<Arg>(arg)); }
» Perfect forwarding can be made to work … But
» First, you must use a template
» Second, you need two template parameters (T and U)
» Then you must restrict them to the specific type you are interested in (i.e., matrix)
˃ Most likely using enable_if
» Finally, you need to ask the right question
˃ IS_RVALUE(T, x)? #define IS_RVALUE(TYPE, VAR) \ (std::is_rvalue_reference<decltype(std::forward<TYPE>((VAR)))>::value) (!std::is_reference<TYPE>::value)
In short, It is too much noise!
Parameter Example Best Case Sub-optimal Case
Const Reference
f(const matix &); Function makes no copy and pass lvalue
Function makes a copy and pass rvalue (missed optimization)
Pass-by-value f(matrix); Function makes a copy and pass rvalue
Function makes no copy and pass lvalue (unnecessary copy)
Const Reference AND Rvalue Reference
f(const matrix &); AND f(matrix &&);
All cases For N parameters, 2N implementations
Perfect Forwarding (T &&)
template <class T> f(T&&);
When you need a template
When you don’t want a template (complex use of enable_if)
Can we do better?
» A type that 1. Binds to lvalues as const reference
2. Binds to rvalues as rvalue reference
3. Determines lvalue/rvalue at run-time
4. Does not force the use of templates
5. Causes no code bloat
6. Is built-in
» Const reference does not satisfy #3
» Perfect forwarding does not satisfy #4
» std::reference_wrapper does not satisfy #2 and #3
template <typename T> struct in { in (const T& l): v_ (l), rv_ (false) {} in (T&& r) : v_ (r), rv_ (true) {} bool lvalue () const { return !rv_; } bool rvalue () const { return rv_; } operator const T& () const { return v_; } const T& get () const { return v_; } T&& rget () const { return std::move (const_cast<T&> (v_)); } T move () const { if (rv_) return rget (); else return v_; } private: const T& v_; bool rv_; };
» No template!
» Simple, self-explanatory
» Can be even shorter!
matrix operator + (in<matrix> m1, in<matrix> m2) { if(m1.rvalue()) { matrix r(m1.rget()); r += m2.get(); return r; } else if(m2.rvalue()) { matrix r(m2.rget()); r += m1.get(); return r; } else { matrix r(m1.get()); r += m2.get(); return r; } }
» Idiom useful only when copies of parameters are made conditionally ˃ See authors blog for more details
» Not clean when ambiguous implicit conversions are involved ˃ See authors blog for more details
» Not well-known yet ˃ May be surprising to some
» More critical eyes needed ˃ Propose in Boost?
» Should be built-in, ideally ˃ Propose a language extension for C++1y?
class Blob { private: std::vector<std::string> _v; };
» Initialize Blob Initializing Blob::_v
» C++11 std::vector has 9 constructors!
» Question: How to write Blob’s constructor(s) so that all the vector’s constructors could be used?
int main(void) { const char * shapes[3] = { "Circle", "Triangle", "Square" }; Blob b1(5, "C++ Truths"); // Initialize Blob::_v with 5 strings Blob b2(shapes, shapes+3); // Initialize Blob::_v with 3 shapes }
class Blob { std::vector<std::string> _v; public: template<class Arg1, class Arg2> Blob(Arg1&& arg1, Arg2&& arg2) : _v(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)) { } }; int main(void) { const char * shapes[3] = { "Circle", "Triangle", "Square" }; Blob b1(5, "C++ Truths"); // OK Blob b2(shapes, shapes+3); // OK }
Perfect forward two parameters
class Blob { std::vector<std::string> _v; public: template<class Arg1, class Arg2> Blob(Arg1&& arg1, Arg2&& arg2) : _v(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)) { } }; int main(void) { Blob b3; // Default constructor. Does not compile! Blob b4(256); // 256 empty strings. Does not compile! }
Perfect forward two parameters
class Blob { std::vector<std::string> _v; public: template<class... Args> Blob(Args&&... args) : _v(std::forward<Args>(args)...) { } }; int main(void) { const char * shapes[3] = { "Circle", "Triangle", "Square" }; Blob b1(5, "C++ Truths"); // OK Blob b2(shapes, shapes+3); // OK Blob b3; // OK Blob b4(256); // OK }
class Blob { std::vector<std::string> _v; std::list<double> _l public: template<class... Args> Blob(Args&&... args) : _v(???), _l(???) { } }; int main(void) { // Initialize Blob::_v with 5 strings and // Blob::_l with { 99.99, 99.99, 99.99 } Blob b1(5, "C++ Truths", 3, 99.99); // Does not compile! }
What parameters are for _v and
what are for _l?
» But std::vector and std::list don’t accept std::tuple as a parameter to constructor
int main(void) { Blob b1(5, "C++ Truths", 3, 99.99); } int main(void) { Blob b1(std::make_tuple(5, "C++ Truths"), std::make_tuple(3, 99.99)); }
» Packing parameters into a tuple is easy
˃ Just call std::make_tuple
» Unpacking is easy too
˃ Use std::get<N>
int main(void) { std::tuple<int, double> t = std::make_tuple(3, 99.99); std::list<int> list (std::get<0>(t), std::get<1>(t)); }
» But the number and types of parameters may vary
» We need a generic way to unpack tuples
˃ This is extraordinarily complicated
» If you have a tuple
t = (v1, v2, v3, …, vN)
» You need
get<0>(t), get<1>(t), get<2>(t), …, get<N-1>(t)
At Compile Time!
» If you have a tuple
t = (v1, v2, v3, …, vN)
» And if you have another type
index_tuple<0, 1, 2, …, N-1>
» Use variadic templates to unpack a tuple
get<0>(t), get<1>(t), get<2>(t), …, get<N-1>(t)
At Compile Time!
Tuple Indices
» We need some way to get index_tuple from a tuple
» E.g., index_tuple<0, 1, 2> from tuple(v1, v2, v3)
template <typename Tuple1, typename Tuple2, unsigned... Indices1, unsigned... Indices2> Blob(Tuple1 tuple1, Tuple2 tuple2, index_tuple<Indices1...>, index_tuple<Indices2...>) : _v(get<Indices1>(tuple1)...), _l(get<Indices2>(tuple2)...) { }
Any number of indices
Size of tuple must match # of indices
» Use the one provided by your compiler!
˃ Not standard!
gcc Clang
Index Builder _Build_index_tuple __make_tuple_indices
Index Tuple _Index_tuple __tuple_indices
» Or write one yourself
˃ ~20 lines of template meta-programming
» Just Google!
class Blob { template <typename Tuple1, typename Tuple2, unsigned... Indices1, unsigned... Indices2> Blob(Tuple1 tuple1, Tuple2 tuple2, index_tuple<Indices1...>, index_tuple<Indices2...>) : _v(get<Indices1>(tuple1)...), _l(get<Indices2>(tuple2)...) { } template <typename Tuple1, typename Tuple2> Blob(Tuple1 tuple1, Tuple2 tuple2) : Blob(tuple1, tuple2, typename make_indices<Tuple1>::type(), typename make_indices<Tuple2>::type()) {} }; int main(void) { Blob b1(std::make_tuple(5, "C++ Truths"), std::make_tuple(3, 99.99)); }
Using Delegated
Constructor
We don’t want users to pass the index_tuple
» Avoid Copies
˃ std::make_tuple makes copies of parameters
» How do we perfect forward parameters through a tuple?
˃ Use std::forward_as_tuple
int main(void) { Blob b1(std::forward_as_tuple(5, "C++ Truths"), std::forward_as_tuple(3, 99.99)); }
std::tuple<int &&, double &&>
class Blob { public: template <typename... Args1, typename... Args2> Blob(std::tuple<Args1...> tuple1, std::tuple<Args2...> tuple2) : Blob(std::move(tuple1), std::move(tuple2), typename make_indices<Args1...>::type(), typename make_indices<Args2...>::type()) {} private: template <typename... Args1, typename... Args2, unsigned... Indices1, unsigned... Indices2> Blob(std::tuple<Args1...> tuple1, std::tuple<Args2...> tuple2, index_tuple<Indices1...>, index_tuple<Indices2...>) : _v(std::forward<Args1>(std::get<Indices1>(tuple1))...), _l(std::forward<Args2>(std::get<Indices2>(tuple2))...) { } };
» It does!
» “Piecewise Construct”; “Emplace Construct”
» In fact, standard library has a type called std::piecewise_construct_t
» The standard library uses this idiom in
˃ std::map
˃ std::unordered_map
˃ std::pair has a piecewise constructor!
» Does it break encapsulation? ˃ Arguably, yes!
» The idiom should be used selectively ˃ Suitable for value-types implemented as structs (e.g., std::pair)
˃ When you know the implementation detail precisely
+ E.g., Auto-generated C++ lasses from DTD, XSD, IDL
» C++ Truths ˃ Rvalue References In Constructor: When Less Is More
˃ Perfect Forwarding of Parameter Groups in C++11
» A Sense of Design ˃ Efficient Argument Passing in C++11 (Parts 1, 2, and 3)
http://cpptruths.blogspot.com/2012/03/rvalue-references-in-constructor-when.html
http://codesynthesis.com/~boris/blog/2012/06/26/efficient-argument-passing-cxx11-part2
http://cpptruths.blogspot.com/2012/06/perfect-forwarding-of-parameter-groups.html