Sven Johannsen 29.-30.04.2014 Advanced Developers Conference zu native C++ |
sven@sven-johannsen.de |
Some coding styles generate less trouble
double *fieldX = new double[fieldSize]; double *fieldY = new double[fieldSize]; double *fieldZ = new double[fieldSize]; for (int i = 0; i < fieldSize; ++i) { //... fieldX[i] ... } // ... delete fieldX; delete fieldY; delete fieldZ; |
struct XYZ { double X; double Y; double Z; }; // ... vector<XYZ> field(fieldSize); for (const auto& point : field) { // ... point.X .. } // no delete,... |
A short example for the erase-remove idiom
bool gt4(int i) { return 4 < i; } vector<int> v; v.push_back(0); v.push_back(5); v.push_back(2); v.push_back(3); // use the erase-remove idiom to remove all elements greater than 4 (=5) v.erase(remove_if(v.begin(), v.end(), gt4), v.end()); v = vector<int>(v.begin(), v.end()); // free unused capacity
vector<int> v = { 0, 5, 2, 3, 4, 5, 6, 7, 8, 9 }; // use the erase-remove idiom to remove all elements greater than 4 v.erase(remove_if(begin(v), end(v), [](int i) { return 4 < i; }),end(v)); v.shrink_to_fit(); // free unused capacity
C++98 / C++03
C++11 Language change
|
TR1(2005)
C++11 Standard library
|
Boost Subset 1(2000-...)
Boost Subset 2
|
// containers std::vector<int> v; std::list<int> l; // iterators auto start = v.begin(); auto stop = v.end(); // algorithms int sum = std::accumulate(start, stop, 0);
Problem: Different syntaxes for initializing
struct A { int i; int j; }; // POD struct B { B(int ii, int jj); /* ... */ }; // Class like struct
C++98
A a = { 1, 3 }; B b(1, 3);
C++11 Uniform initialization (N2532)
// Aggregates (e.g. arrays and structs): A a1 = { 1, 3 }; // Initialize members from begin-to-end // Non-aggregates: Invoke a constructor. B b1 = { 1, 3 }; // alternative syntax A a2 { 1, 3 }; B b2 { 1, 3 };
C++98
int arr[] = {1, 2, 3}; vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3);
C++11
Initializer Lists (N2672)
#include <initializer_list> template <class T> // ignoring allocators class vector { // ... vector(initializer_list<T>); // initializer list constructor //... }; vector<int> vec1 = { 1, 2, 3 }; vector<int> vec2 { 11, 22, 33 }; // alternative syntax
class vector { // ... vector(); // default constructor vector(initializer_list<T>); // initializer list constructor vector(size_type n, const T& value); // other constructor //... }; vector<int> vec0 = { }; // vector::vector(); (default constructor) vector<int> vec0 { }; // still default constructor vector<int> vec1 = { 13, 17 }; // vector::vector(initializer_list<T>); vector<int> vec2 { 19, 23 }; // alternative syntax vector<int> vec3(10, -1); // vector::vector(size_type n, const T& value);
All STL containers support uniformed initialization
vector<int> v({ 2, 3, 5, 7, 11, 13, 17 }); list<int> l = { 0, 1, 2, 3, 4, 5, 6 }; map<int, string> m { { 1, "one" }, { 2, "two" } }; valarray<double> v = { 1.0, 0.1, 0.001, 0.0001 }; // same for deque, forward_list, set, string, regex // unordered_map, unordered_set, multi_.. // but not for: queue, priority_queue and stack // aggregate initialization: array<double,4> a = { 1.0, 0.1, 0.001, 0.0001 };
and some algorithms too
Most containers overload some additional member functions for initializer_list<T>.
vector<int> v = { -1, -2, -3 }; // v = -1, -2, -3 v = { 1, 2, 3 }; // operator=() v = 1, 2, 3 v.insert(end(v), { 4, 5, 6 }); // v = 1,2,3,4,5,6 v.assign({ -1, -2, -3 }); // v = -1, -2, -3
(All STL containers = { string
, deque
, forward_list
(insert_after()
), list
, vector
, map
, multimap
, set
, multiset
, unordered_map
, unordered_multimap
, unordered_set
, unordered_multiset
})
#include <initializer_list> ... template<class E> class initializer_list { public: // some typedefs initializer_list() noexcept; // default constructor size_t size() const noexcept; // number of elements const E* begin() const noexcept; // first element const E* end() const noexcept; // one past the last element };
initializer_list<string> strings = { "C++", "is", "cool!" };
template<class T> MyVector<T>::MyVector(initializer_list<T> i_list) { reserve(i_list.size()); for(const auto iter = i_list.begin(); iter != i_list.end(); ++iter) push_back(*elem); // for(const auto& T elem : i_list) // push_back(elem); // for(auto&& T elem : i_list) // push_back(elem); } // usage MyVector<int> v = { 2, 3, 5, 7 };
initializer_list
is not limited to container
void print_some_doubles(initializer_list<double> doubles) { for(double d : doubles) cout << d << " "; } ... print_some_doubles({ 1, 2, 3 });
Examples: std::min
, std::max
, minmax
and some random devices (seed_seq
, discrete_distribution
, ...)
Copy semantic can result in performance issues.
vector<string> v; v.push_back("C++"); v.push_back("Boost"); // Hint: emplace_back beats move semantic
C++11 introduce move semantic into the language to reduce the number of new / delete calls in the case of temporary objects
new
/ delete calls
Temporary objects ("objects without a name"):
a+b+c
)void foo(std::string& s) { string text; ...; text = s; ...; } std::string bar() { string s; ... return s; } foo("Converted to a std::string"); string t = bar(); string s = bar() + " and " + " copied!";
basic_string& operator=(const basic_string& str); // l-value reference basic_string& operator=(basic_string&& str) noexcept; // r-value reference
Designed for objects which uses of dynamic memory, like STL containers
Looks like moving the objects, but only the content is moving
E.g. std::vector:
vector(vector&&);
operator= (vector<T,Allocator>&& );
void push_back(T&& x);
insert(const_iterator position, T&& x);
string text("C++"); vector<string> v; v.push_back(text); // copy "C++" v.push_back("explore the boost library!"); // create a temp. string object
STL overloads many functions for improving the performance.
E.g. 12 different operator+() for the combination of string&
, string&&
and char*
The C++ and the STL "moves" only temporary objects ("objects without a name")
To move non-temporary objects, use std::move()
to mark an object as r-value reference.
vector<int> temp = { 1, 2, 3 }; vector<int> result = { }; result = std::move(temp); // calls operator=(vector<int>&&) //result = static_cast<vector<int>&& >(temp); assert(temp.size() == 0); assert(result.size() == 3); assert(result[0] == 1 && result[1] == 2 && result[2] == 3);
Examples:
ifstream open_file(const string& filename) { ... } unique_ptr<MyDocument> document_factory(Param param) { ... } vector<unique_ptr<MyDocument>> documents; documents.push_back(document_factory(param));
(deque, list, vector, priority_queue)
Construct an element in the container. Forward all parameters to the constructor.
template <class... Args> void emplace_back(Args&&... args); template <class... Args> iterator emplace(const_iterator position, Args&&... args);
Example:
vector<string> field = { " " }; // init. list with one element " " field.emplace_back("C++"); char* text = "Hallo Fortran"; field.emplace(field.begin(), text, text+5); // field == "Hello", " ", "C++"
As efficient as a "C style" array, but with the interface of a STL container
int field[3] = { 1, 2, 3 }; array<int, 3> arr = { 1, 2, 3 }; cout << "size : " << arr.size() << endl; for (auto it = arr.begin(); it != arr.end(); ++it) cout << *it << " "; cout << endl;
template <class T, size_t N> struct array { // some typedefs // no constructor, no operator=() ! void fill(const T& u); iterator begin() noexcept; iterator end() noexcept; // rbegin(), rend(), cbegin(), cend(), crbegin(), crend() constexpr size_type size() noexcept; // max_size, empty reference operator[](size_type n); reference at(size_type n); reference front(); reference back(); T* data() noexcept; // plus const functions };
array<int, 3> arr {}; // zero initialization arr.fill(-11); for (auto i : arr) assert(i == -11); iota(arr.begin(), arr.end(), 1); // { 1, 2, 3 } int i = arr[0]; int j = arr.at(1); int k = arr.back(); // last element assert(i = 1 && j == 2 && k == 3); int l = arr.at(10); // throw an "out of range" exception
4 new hash_maps (associative containers):
Similar to map, multimap, set and unordered_multiset, with different requirements for the key and different storage. The naming tries to avoid breaking existing code.
map<string, int> index = { { "C++", 1 }, { "Boost", 42 } }; unordered_map<string, int> fast_index = { { "C++", 1 }, { "Boost", 42 } };
map<string, int> index; // same as: map<string, int, less<string>> index; // less call operator<()
class Person; // without operator<(); struct PersonLess { bool operator()(const Person& l, const Person& r) { return l.Name() < r.Name(); } }; map<Person, Account, PersonLess> AccountInfo;
unordered_map<string, int> fast_index; // same as: unordered_map<string, int, hash<string>, equal_to<string>> fast_index;
struct PersonHash // Hash function { size_t operator()(const Person& p) { return hash<string>()(p.Name()); } }; struct PersonEquality // for the case of collisions { bool operator()(const Person& l, const Person& r) { return l.Name() == r.Name(); } }; unordered_map<Person, Account, PersonHash, PersonEquality> FastAccountInfo;
Hash functions are available for
// bucket interface size_t bucket_count() const noexcept; size_t bucket_size(size_type n) const; size_t bucket(const key_type& k) const; // hash policy float load_factor() const noexcept; float max_load_factor() const noexcept; void max_load_factor(float z); void rehash(size_t n);
Minimal list implementation, which avoid expensive operations (e.g. back()).
template <class T, class Allocator = allocator<T> > class forward_list { public: // some typedefs explicit forward_list(); // 9 constructors forward_list& operator=(initializer_list<T>); // +3 iterator begin() noexcept; iterator end() noexcept; // + cbegin, ... but no rbegin() bool empty() const noexcept; // no size() void push_front(const T& x); void pop_front(); iterator insert_after(const_iterator position, const T& x); // + 5 // ... void sort(); void reverse() noexcept; };
STL container member functions like insert() need the access to the prior element.
cbegin, cend, crbegin, crend
const_iterator cbegin() const noexcept; const_iterator cend() const noexcept;
Better control of the used iterator type
void foo(const vector<int>& cv, vector<int>& ncv) { // C++98 for (vector<int>::const_iterator it1 = cv.begin(); it != cv.end(); ++it) {} for (vector<int>::const_iterator it2 = ncv.begin(); it != ncv.end(); ++it) {} // C++11 for (auto it = cv.begin(); it1 != cv.end(); ++it) {} // const_iterator for (auto it = ncv.begin(); it2 != ncv.end(); ++it) {} // iterator for (auto it = ncv.cbegin(); it3 != ncv.cend(); ++it) {} // const_iterator
(string, deque, vector)
void shrink_to_fit();
Ask for reducing capacity() to size().
Example:
// free unused capacity with a temp. object vector<int> tmp(v.begin(), v.end()); v.swap(tmp); // free unused capacity v.shrink_to_fit();
(vector, array)
T* data() noexcept; const T* data() const noexcept;
The address of the first element, or NULL.
Example:
vector<BYTE> field = ...; legacy_function(BYTE* raw_data, int size); ... // C++98: address of the first element legacy_function(field.empty() ? NULL : &field[0], field.size()); legacy_function(field.empty() ? NULL : &field.front(), field.size()); // C++11 use data legacy_function(field.data(), field.size());
Element access with range check (throws, if the key is not present).
map<string, int> cont; int val; // C++ 98 val = cont["key"]; //(1) may add a default value to the map auto it = cont.find("key"); if(it != cont.end()) //(2) val = it->second; // C++11 val = map.at("key"); //(3) throws "out_of_range", if key not present
begin() and end() member functions
auto it1 = cont.begin(); auto it2 = cont.end();
e.g. std::vector, std::list, std::map
non member begin() and end() functions
auto it1 = begin(cont); auto it2 = end(cont);
e.g. std::vector, std::list, std::map, double[10]
begin()
and end()
Addition level of abstraction for an iterator access.
// vector<int> cont = { ... }; // int cont[] = { ... }; for(auto it = begin(cont); it != end(cont); ++it) { cout << *it << " "; }
This code runs with any container (if non-member begin() and end() are overloaded).
C++14 will also introduce non-member cbegin
, cend
, rbegin
, rend
, crbegin
and crend
.
E.g.: Range based for loop for non STL containers
template<class T> CArrayIterator<T> begin(const CArray<T>& arr); template<class T> CArrayIterator<T> end(const CArray<T>& arr); ... CArray<int> arr; ... for(int i : arr) { cout << i << " " << endl; } bool sorted = std::is_sorted(begin(arr), end(arr));
STL algorithms are unchanged, because the new C++11 language features (Lambdas, std::function) are designed for the STL algorithms!
void print_func(int i) { cout << i << ' '; } struct PrintFunctor { void operator()(int i); }; function<void(int)> f1 = print_func; function<void(int)> f2 = [](int i) { cout << i << ' '; }; vector<int> v = { 1, 2, 3, 4, 5 }; for_each(begin(v), end(v), print_func); for_each(begin(v), end(v), PrintFunctor()); for_each(begin(v), end(v), [](int i) { cout << i << ' '; }); for_each(begin(v), end(v), f1); for_each(begin(v), end(v), f2);
A-M | N-Z |
---|---|
all_of (is p true for all e in R?) |
is_partitioned (is R partitioned per p?) |
any_of (is p true for any e in R?) |
partition_point (find first e in R where p(e) is false) |
none_of (is p true for no e in R?) |
partition_copy (copy all e in R to 1 of 2 destinations per p(e)) |
find_if_not (find first e in R where p is false) |
is_sorted (is R sorted?) |
copy_if (copy all e in R where p is true) |
is_sorted_until (find first out-of-order e in R) |
copy_n (copy first n elements of R) |
is_heap (do elements in R form a heap?) |
iota (assign all e in R increasing values starting with v) |
is_heap_until (find first out-of-heap-ordered in R) |
minmax (return pair(minVal, maxVal) for given inputs) |
move (like copy, but each e in R is moved) |
minmax_element (return pair(min_element, max_element) for R) |
move_backward (like copy_backward , but each e in R is moved) |
R is a range, e is an element, p is a predicate.
(note: use <numeric>
for iota
)
vector<int> v(10); // 0 0 0 0 0 0 0 0 0 0 iota(begin(v), end(v), 10); // 10 11 12 13 14 15 16 17 18 19 string s; s.resize(26); iota(begin(s), end(s), 'a'); // a b c d e f g h i j k l m n o p q r s t u v w x y z
vector<int> v { 13, 15, 19, 3 }; bool b1 = all_of(begin(v), end(v), [](int i){ return i % 2; }); // true bool b2 = any_of(begin(v), end(v), [](int i){ return i < 0; }); // false bool b3 = none_of(begin(v), end(v), [](int i){ return i < 0; }); // true bool b4 = is_sorted(begin(v), end(v)); // false auto minmax_iter = minmax_element(begin(v), end(v)); // { iterator->min, iterator->max } pair<int,int> minmax_val = minmax(19, 3); assert(*minmax_iter.first == 3 && *minmax_iter.second == 19); assert(minmax_val.first == 3 && minmax_val.second == 19);
STL:
template<typename T> class CArrayIterator { const CArray<typename T>* cont_; int index_ = 0; public: typedef std::input_iterator_tag iterator_category; typedef typename T value_type; typedef int difference_type; typedef typename T* pointer; typedef typename T& reference; CArrayIterator(const CArray<T>& cont, int index) : cont_(&cont), index_(index) {} T operator*() { return (*cont_)[index_]; } CArrayIterator& operator++() { index_++; return *this; } bool operator!=(const CArrayIterator& other) { return cont_ != other.cont_ || index_ != other.index_; } bool operator==(const CArrayIterator& other) { return !(*this != other); } };
template<typename T> CArrayIterator<T> begin(const CArray<T>& cont) { return CArrayIterator<T>(cont, 0); } template<typename T> CArrayIterator<T> end(const CArray<T>& cont) { return CArrayIterator<T>(cont, cont.GetSize()); }
CArray<int> cont; cont.Add(1); cont.Add(3); cont.Add(5); cont.Add(2); for (auto it = begin(cont); it != end(cont); ++it) { cout << *it << ' '; } cout << endl; for (int i : cont) { cout << i << ' '; } cout << endl; for_each(begin(cont), end(cont), [](int i) { cout << i << ' '; }); cout << endl; vector<int> v(begin(cont), end(cont)); for (int i : v) { cout << i << ' '; } cout << endl;
/