This presentation considers certain specific features of C++11 and additions to STL library (uniform initialization, new containers and methods, move semantics).
Presentation by Taras Protsiv (Software Engineer, GlobalLogic), Kyiv, delivered at GlobalLogic C++ TechTalk in Lviv, September 18, 2014.
More details -
http://www.globallogic.com.ua/press-releases/lviv-cpp-techtalk-coverage
3. Very common code:
for (set<boost::shared_ptr<Thread> >::const_iterator thread =
clientThreads.begin();
thread != clientThreads.end(); thread++)
{/* ... */}
Allow compiler to deduce type:
for (auto thread = clientThreads.begin();
thread != clientThreads.end(); ++thread)
{/* ... */}
4. Simplify iteration for standard containers:
int v[] { 1, -4, 11 };
for (int& i: v) { // for ( auto& i : v )
i += 42;
}
for (int i: v) { // for ( auto i : v )
cout << i << " ";
}
Output:
43 38 53
5. Problem
• C++ offers several ways of initializing an object depending on its
type and the initialization context
string a[] = { "foo", " bar" }; // ok: initialize array variable
vector<string> v = { "foo", " bar" }; // error: initializer list for non-aggregate
vector
void f(string a[]);
f( { "foo", " bar" } ); // syntax error: block as argument
int a = 2; // ``assignment style''
int aa[] = { 2, 3 }; // assignment style with list
complex z(1,2); // ``functional style'' initialization
x = Ptr(y); // ``functional style'' for conversion/cast/construction
int a(1); // variable definition
int b(); // function declaration
int b(foo); // variable definition or function declaration
class Bar;
int foo(Bar()); //function declaration !!!!
6. The C++11 solution is to allow {}-initializer lists for all initialization
• aggregate initialization (POD, arrays, simple class)
class Point {
public:
int x;
int y;
};
struct S {
int x;
struct Foo {
int i;
int j;
int a[3];
} b;
};
Point p1 = { 1, 2 }; // simple case
Point p2{ 1, 2 };
S s1 = { 1, { 2, 3, {4, 5, 6} } };
S s3{1, {2, 3, {4, 5, 6} } }; // same, using direct-list-initialization syntax
int ar[] = {1,2,3}; // ar is int[3]
char cr[3] = {'a'}; // array initialized as {'a', '0', '0'}
int ar2d1[2][2] = {{1, 2}, {3, 4}}; // fully-braced 2D array: {1, 2}
// {3, 4}
7. • instead of () for constructors
class MyClass {
public:
MyClass(double a, std::string str);
MyClass(int k);
MyClass();
}
MyClass m1{1.0, "test"};
MyClass m2{1};
MyClass m3{};
8. • std::initializer_list<T> constructor
#include <initializer_list>
class Summator {
public:
Summator(std::initializer_list<int> il) {
sum = 0;
cout << il.size() << endl;
for (auto i : il) { //for (const int* it = il.begin(); it != il.end(); it++ ) {
cout << i << endl; // cout << *it << endl;
sum += i; // sum += *it;
}
}
int Get() { return sum; }
private:
int sum;
};
Summator s1 { 1, 2, 3, 4, 5};
int i, j, k;
//set i, j, k
Summator s2 { i, j, k};
9. • All STL containers support std::initializer_list<T>
constructor
std::vector<int> v { 1, 2, 3, 4, 5};
std::string s1{'a', 'b', 'c', 'd'};
std::vector<Point> p { {i, j}, {j, i}, {i, i} };
std::map<int, std::string> m {
{ 1, "string 1"},
{ 2, "string 2"},
{ 3, "string 3"},
};
10. Initializer list in range loop
std::vector<int> v { 1, i , 2, j , 3, k} ;
for (int x : {2, 4, 1})
{
std::cout << v[x] << " ";
}
11. Importantly, T{a} constructs the same
value in every context, so that {}-
initialization gives the same result in all
places where it is legal.
T x{a};
T* p = new T{a};
z = T{a}; // use as cast
f({a}); // function argument (of type T).
return {T}; // function return value (function returning T)
12. Priority of constructors
• If the braced-init-list is empty and T is a class type
with a default constructor, value-initialization is
performed (default constructor or 0)
• Otherwise, if T is an aggregate type, aggregate
initialization is performed (POD, array)
• std::initializer_list constructor
• Other constructors
13. Special cases
//two constructors
explicit vector (size_type n); // 1
vector(initializer_list<value_type> l); // 2
std::vector<int> v(100); //1: vector with 100 elements
std::vector<int> v{100}; //2: vector with one element "100"
std::vector<string> d {100}; //1: vector with 100 elements
14. C++ C++11
std::vector<int> v;
v.push_back(1);
v.push_back(i);
v.push_back(2);
v.push_back(j);
std::vector<int> v { 1, i, 2, j};
std::map<int, std::string> m;
mymap.insert(std::pair<int,string>(1,"a"));
mymap.insert(std::pair<int,string>(2,“b"));
mymap.insert(std::pair<int,string>(3,“c“));
std::map<int, std::string> m = {
{1, "a"},
{2, "b"},
{3, "c"}
};
void Foo( std::set<MyClass>& c) {
c.insert(MyClass(1, 1));
c.insert(MyClass(1.0, str));
}
void Foo( std::set<MyClass>& c) {
c.insert( {1,1} );
c.insert( {1.0, str} );
}
Point GetCenterPoint() {
//logic
return Point(1,0);
}
Point GetCenterPoint() {
//logic
return {1, 0};
}
15. Temporary and non-temporary object
• C++ works with temporary and non-temporary
objects in the same way.
std::vector<int> GetV();
int main()
{
std::vector<int> v= GetV(); //functions return temporary object
std::vector<string> sv;
sv.push_back("string"); // temporary object std::string
std::vector<Point> vp;
vp.push_back({1,0}); //temporary object Point
}
16. C++11 introduce rvalue reference ( T&&)
• rvalue references enable compiler to distinguish an
lvalue from an rvalue. ( separate temporary object and
non-temporary object)
• Move constructor and move assignment operator
In some cases compiler generates automatically
• Provide performance benefits to existing code
without needing to make any changes outside the
standard library.
• All STL containers support move semantic
18. Compile the same code with –std=c++11
std::vector<int> GetV();
int main()
{
std::vector<int> v= GetV(); //optimized (copy only pointer)
std::vector<string> sv;
sv.push_back("string"); // optimized
std::vector<Point> vp;
vp.push_back({1,0}); //no optimizing (point is simple struct)
}
19. std::move
• Force compiler to work with object as with
temporary object ( cast to rvalue )
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str);
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now have unpredictable data.
v.push_back(std::move(str));
20. class Buffer {
char* _data;
size_t _size;
// ...
// copy the content from lvalue
Buffer(Buffer const& lvalue)
: _data(nullptr), _size(0)
{
// perform deep copy memcpy...
}
// take the internals from rvalue
Buffer(Buffer&& rvalue)
: _data(nullptr), _size(0)
{
swap(_size, rvalue._size);
swap(_data, rvalue._data);
// it is vital to keep rvalue
// in a consistent state
}
// ...
Buffer getData() { /*...*/ }
~Buffer()
{
delete _data;
}
// ...
Buffer someData;
// ... initialize someData
Buffer someDataCopy(someData);
// make a copy
Buffer anotherData(getData());
// move temporary
21. Emplace – series of STL container’s methods for
inserting a new element to container. (emplace_back,
emplace_front, emplace)
• The element is constructed in-place, i.e. no copy operations are
performed.
• The constructor of the new element is called with exactly the
same arguments as supplied to emplace
23. T& std::map::at(const key_type& k)
const T& std::map::at(const key_type& k) const
• Returns a reference to the mapped value of the
element identified with key k.
• If k does not match the key of any element in the
container, the function throws an out_of_range
exception.
24. T* std::vector::data();
const T* std::vector::data() const;
• Returns pointer to the underlying array serving as
element storage.
26. unordered_set<T>, unordered_map<T>,
unordered_multiset<T>,
unordered_multimap<T>
• variations of hash tables
• Search, insertion, and removal have average
constant-time complexity.
#include <unordered_map>
struct my_data {};
unordered_map<string, my_data, hash<string>> data_table;
27. std::tuple is a fixed-size collection of heterogeneous
values. It is a generalization of std::pair.
tuple<int, std::string, int> t1(1, "abc", 1);
int a = get<0>(t1);
string b = get<1>(t1);
string b2 = get<string>(t1); // since C++14
int a = get<int>(t1); // error
tuple <int, const char*, int> t0 (5, "test",3 );
t1 = t0;
get<0>(t1) = 0;
cout << ( t0 > t1) << endl; // true
map<tuple <int, const char*, int>, double> m;
28. make_tuple creates a tuple object of the
type defined by the argument types
auto t1 = make_tuple(10, "Test", 3.14);
int n = 1;
auto t2 = make_tuple(ref(n), n);
n = 7;
cout << "The value of t2 is " << "("
<< get<0>(t2) << ", " << get<1>(t2) << ")n";
// The value of t2 is (7, 1)
tuple_cat сonstructs a tuple that is a
concatenation of all tuples in args
tuple<int, std::string, float> t1(10, "Test", 3.14);
auto t2 = tuple_cat(t1, make_pair("Foo", "bar"), t1);
29. tuple_size<tuple_type>::value obtains
the size of tuple at compile time
tuple_element obtains the type of the
specified element
template <class... Args>
struct type_list
{
template <std::size_t N>
using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};
int main()
{
type_list<int, char, bool>::type<2> x = true;
}
tuple_size< tuple<int,double,float> >::value
30. std::tie creates a tuple of lvalue references to its
arguments or instances of std::ignore.
struct S {
int n;
std::string s;
float d;
bool operator<(const S& rhs) const {
// compares n to rhs.n,
// then s to rhs.s,
// then d to rhs.d
return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
}
};
int main() {
std::set<S> set_of_s; // S is LessThanComparable
S value{42, "Test", 3.14};
bool inserted;
// unpacks the return value of insert into iter and inserted
tie(ignore, inserted) = set_of_s.insert(value);
// pair<set<S>::iterator, bool> = set_of_s.insert(value);
}
Consider a buffer class:
Old good copy constructor – makes a deep copy.
Move constructor – takes resources from its argument:
Special syntax for identify rvalue
Must keep rvalue object in the consistent state