ADRIAN BULAT Enthusiastic Computer Science Researcher Delusional Programmer
ADRIAN BULAT Enthusiastic Computer Science Researcher & Delusional Programmer Ph. D Student at University of Nottingham adrian. bulat@nottingham. ac. uk https: //www. adrianbulat. com
C(++)ontent - Implicit variable declaration and return (auto), or how to become a lazy programmer in C++ - Range based loops - Initializers for containers - Lambda functions - How smart are smart pointers - Other small features and syntax sugar
Why C++ ? A glance into the past New standards are/will be released Bjarne Stroustrup Creates C++ while working on his Ph. D thesis. 2017 1970 1979 1983 Denis Ritchie Creates C language at Bell Labs 1989 1998 C++ becomes C++ 2003 2011 C++ 2. 0 is released 2014
Casting in C++(reminder) >> dynamic_cast – poate fi folosit doar cu pointeri sau referinte catre clase >> static_cast – poate efectua atat upcast cat si downcast. Nu efectueaza verificari. Folosit deobicei pentru a inversa o conversie implicita. >> reinterpret_cast – converteste orice tip de pointer catre orice alt tip. Este o simpla copie binara de la un pointer la altul. Nimic nu e verificat. >> const_cast – manipuleaza daca obiectul reprezentat de un pointer este const sau nu. const char * c = "sample text"; print ( const_cast<char *> (c) );
auto keyword >> Permite deducerea automata a tipului variabilei auto x = 10; //C++11, explicitly return type float f() { return foo() * 42; } auto f() -> float { return foo() * 42; } //C++14 auto f() { return foo() * 42; } //Multiple return auto g() { while( something() ) { if( expr ) return foo() * 42; } return bar. baz(84); }
auto keyword Simiplitate și eficiență std: : map<std: : wstring, int>> Student. Grades; Student. Grades[L"Dorel"][L"Physics"] = 3; Student. Grades[L"Dorel"][L"Chemistry"] = 6; Student. Grades[L"Mirel"][L"Physics"] = 7; Student. Grades[L"Mirel"][L"Chemistry"] = 8; for (std: : map<std: : wstring, int>>: : iterator outer. Map_Iter = Student. Grades. begin(); outer. Map_Iter != Student. Grades. end(); ++outer. Map_Iter) { //Print out the student name std: : wcout << outer. Map_Iter->first << std: : endl; }
auto keyword for (std: : map<std: : wstring, int>>: : iterator outer. Map_Iter = Student. Grades. begin(); outer. Map_Iter != Student. Grades. end(); ++outer. Map_Iter) { //Print out the student name std: : wcout << outer. Map_Iter->first << std: : endl; } for (auto outer. Map_Iter = Student. Grades. begin(); outer. Map_Iter != Student. Grades. end(); ++outer. Map_Iter) { //Print out the student name std: : wcout << outer. Map_Iter->first << std: : endl; } for (auto const &outer_iter : Student. Grades) { std: : wcout << outer_iter. first << std: : endl; }
auto keyword Forțează inițializarea variabilelor auto a; // does not compile int a; // ok for the compiler Performanță (evitarea conversiilor implicite) int sum; double a=2. 0, b=3. 2; sum = a+b; // implicit conversion occurs auto a=2. 0, b=3. 2; auto sum = a+b;
auto keyword >> Utilizari ambiguie auto foo = std: : make_shared<Foo>(); // clear auto foo = blablabla(); // unclear const size_t max_size = 100; for ( auto x = max_size; x > 0; --x ) {} auto ptr = condition ? new class 1 : new class 2; int& foo(); auto bar = foo(); // int& or int? >> Codul poate deveni ilizibil rapid >> Incurajeaza aparitia programatorilor lenesi
auto keyword auto flags = DWORD { FILE_FLAG_NO_BUFFERING }; auto numbers = std: : vector<int> {1, 2, 3}; auto event_handle = HANDLE {}; auto is_prime(auto n); auto read_async(auto h, auto count); auto foo(auto, auto);
decltype keyword >> Similar cu “typeof” int x = 3; decltype(x) y = x; // same thing as auto y = x; uint 16_t id_ = 65535; decltype(auto) id() { return id_; } auto myid = id(); // before string look_up_a_string_1() { return lookup 1(); } String& look_up_a_string_2() { return lookup 1(); } // now decltype(auto) look_up_a_string_1() { return lookup 1(); } decltype(auto) look_up_a_string_2() { return lookup 1(); }
Range-for statement >> Foloseste un iterator ce este cache-uit >> Utilizeaza pre-incrimentare >> Dereferentiaza iteratorul o singura data for (auto const &outer_iter : Student. Grades) { std: : wcout << outer_iter. first << std: : endl; } void f(vector<double>& v) { for (auto x : v) std: : cout << x << 'n'; for (auto& x : v) ++x; // using a reference so we can edit it } for (const auto x : { {1, 2, 3, 5, 8, 13, 21, 34} }) cout << x << 'n';
Uniform initializer statement – initializer_list<T> C++98 std: : vector<int> ints; ints. push_back(10); ints. push_back(20); ints. push_back(30); C++1 y std: : vector<int> ints = {10, 20, 30} ; >> Nu mai sunt doar pentru vectori! >> Mecanizmul este implimentat via std: : initializer_list<T> void f(initializer_list<int>); f({1, 2}); f{1, 2}; // error: function call ( ) missing
Uniform initializer statement C++98 string a[] = { "foo", " bar" }; std: : vector<string> v = { "foo", "bar" }; void f(string a[]); f({ "foo", "bar" }}); int a = 2; // "assignment style" int aa[] = { 2, 3 }; // assignment style with list complex z(1, 2); // "functional style" initialization x = Ptr(y); // "f. style" for conversion/cast/construction int a(1); int b(foo); >> Poate genera confuzii si e mai greu de retinut!
Uniform initializer statement >> C++1 y – permite utilizarea {} pentru toate cazurile de initializare >> Conversii prin aproximare nu sunt premise! void test(double val, int val 2) { int x 2 = val; char c 2 = val 2; int x 3 {val}; char c 3 {val 2}; char c 4 {24}; char c 5 {264}; int x 4 {2. 0}; // error: no double to int value conversion (good) } >> Recomandata spre a fi folosita mereu exceptie fiind cand auto este utilizat auto a {10}; // a is an initializer_list<int> auto b = 10; // b is an int
lambdas >> Reprezinta un mecanizm de specificare a functiilor obiect vector<int> v = {50, -10, 20, -30}; std: : sort(v. begin(), v. end()); // the default sort std: : sort(v. begin(), v. end(), [](int a, int b) {{ return abs(a)<abs(b); }}); void f(vector<Record>& v){ vector<int> indices(v. size()); int count = 0; generate(indices. begin(), indices. end(), [&count]() {return count++; }); std: : sort(indices. begin(), indices. end(), [&](int a, int b){ return v[a]. name<v[b]. name; }); } [&] – lista de “capturare”, toate variabilele locale sunt trimise prin referentiere [&v] – doar v va fi modificat, [] – nici o variabila nu va fi capturata echivalent pentru captura prin valoare putem folosi: [=] si [=v]
lambdas >> Capturare prin valoare vs referinta int i = 0; auto foo = [i](){ cout << i << endl; }; auto bar = [&i](){ cout << i << endl; }; i = 10; foo(); bar(); 0 10 >> Tipul unei expresii lambda >> Nu este std: : function void (*foo)(bool, int); foo = [](bool, int){};
lambdas >> lambda “mutabile” int i = 1; [&i](){ i = 1; }; // ok, 'i' is captured by-reference. [i](){ i = 1; }; // ERROR: assignment of read-only variable 'i'. [i]() mutable { i = 1; }; // ok. >> implicit, operatorul () este cont >> se comporta ca si clasele! >> Dimensiunea unei expresii lambda auto f 1 = [](){}; std: : array<char, 100> ar; auto f 2 = [&ar](){}; auto f 3 = [ar](){};
Rvalues and Lvalues (reminder) >> Lvalues sunt valori/expresii a caror adresa poate fi obinuta. >> Rvalues - restul int x; x = 42; x + 2 = 42; x++ = 42; int& foo(); int* goo(); --foo(); ++(*goo()); int x; int& get. Ref () { return x; } get. Ref() = 4;
Rvalues and Lvalues - who cares?
Rvalues and Lvalues vector<int> double. Values (const vector<int>& v) { vector<int> new_values; new_values. reserve(v. size()); for (auto itr = v. begin(), end_itr = v. end(); itr != end_itr; ++itr ) { new_values. push_back( 2 * *itr ); } return new_values; } int main() { vector<int> v; for ( int i = 0; i < 100; i++ ) { v. push_back( i ); } v = double. Values( v ); }
Rvalues and Lvalues C++98 const string& name = get. Name(); // ok string& name = get. Name(); // NOT ok C++1 y const string&& name = get. Name(); // ok string&& name = get. Name(); // also ok! print. Reference (const String& str){ cout << str; } print. Reference (String&& str){ cout << str; } string me( "adrian" ); print. Reference( me ); print. Reference( get. Name() );
Rvalues and Lvalues class Array. Wrapper { public: Array. Wrapper (int n) : _p_vals( new int[ n ] ) , _size( n ) {} ~Array. Wrapper () { delete [] _p_vals; } private: int *_p_vals; int _size; };
Rvalues and Lvalues // copy constructor Array. Wrapper (const Array. Wrapper& other) : _p_vals( new int[ other. _size ] ) , _size( other. _size ) { for ( int i = 0; i < _size; ++i ) { _p_vals[ i ] = other. _p_vals[ i ]; } // move constructor Array. Wrapper (Array. Wrapper&& other) : _p_vals( other. _p_vals ) , _size( other. _size ) { other. _p_vals = NULL; other. _size = 0; }
Rvalues and Lvalues std: : move( other. _a_cool_object )
Constexpr constexpr int multiply (int x, int y) { return x * y; } // the compiler may evaluate this at compile time const int val = multiply( 10, 10 ); constexpr int get. Default. Array. Size (int multiplier) { return 10 * multiplier; } int my_array[ get. Default. Array. Size( 3 ) ];
smart pointers >> Obiecte ce se comporta ca si pointerii nativi, care isi dealocheaza automat memoria la momentul potrivit. >> unique_ptr, shared_ptr, weak_ptr >> conceptul de ownership >> Cum accesam pointerii smart? #include <memory>
smart pointers – shared_ptr and weak_ptr >> Toti pointerii partajeaza ownershipul obiectului in cauza >> Oricare din pointeri poate pastra obiectul A B ptr ptr ptr X 1 X 2 ap ap X 3 ap ap
smart pointers – shared_ptr and weak_ptr Restrictii: >> Pot fi folositi pentru a referi doar la obiecte create cu “new” ce pot fi deallocate cu “delete”. >> A nu se folosi impreuna cu pointerii native pentru a evita probleme precum doubla dealocare. >> Existenta unui singur obiect manager pentru fiecare obiect manageriat
smart pointers – shared_ptr class Thing { public: void defrangulate(); }; ostream& operator<< (ostream&, const Thing&); shared_ptr<Thing> find_some_thing(); shared_ptr<Thing> do_something_with(shared_ptr<Thing> p);
smart pointers – shared_ptr void foo() { shared_ptr<Thing> p 1(new Thing); shared_ptr<Thing> p 2 = p 1; shared_ptr<Thing> p 3(new Thing); p 1 = find_some_thing(); do_something_with(p 2); p 3 ->defrangulate(); cout << *p 2 << endl; p 1. reset(); p 2 = nullptr; }
smart pointers – shared_ptr Thing * bad_idea() { shared_ptr<Thing> sp; // an empty smart pointer Thing * raw_ptr = new Thing; sp = raw_ptr; // disallowed - compiler error !!! return raw_ptr; // danger } shared_ptr<Thing> better_idea() { shared_ptr<Thing> sp(new Thing); return sp; } Thing * raw_ptr = sp. get(); // you must want it, but why?
smart pointers – shared_ptr >> Conversii shared_ptr<Base> base_ptr (new Base); shared_ptr<Derived> derived_ptr; // Casting derived_ptr = static_pointer_cast<Derived>(base_ptr); shared_ptr<Thing> p(new Thing); shared_ptr<Thing> p(make_shared<Thing>());
smart pointers – weak_ptr >> Nu il “tin in viata” pe obiect, ci doar il “observa” >> Pot fi folositi doar pentru a verifica daca un obiect mai exista si pot oferi un shared_ptr catre el. >> Sunt “idiot proff” Nu ofera nici un operator caracterstic iar functia get() nu exista. >> Poate fi reset folosind reset(), dar nu si prin atribuire cu nullptr shared_ptr<Thing> sp(new Thing); weak_ptr<Thing> wp 1(sp); // construct wp 1 from a shared_ptr weak_ptr<Thing> wp 2; // an empty weak_ptr - to nothing wp 2 = sp; // wp 2 now points to the new Thing weak_ptr<Thing> wp 3 (wp 2); // construct wp 3 from a weak_ptr<Thing> wp 4 = wp 2; // wp 4 now points to the new Thing
smart pointers – weak_ptr shared_ptr<Thing> sp 2 = wp 2. lock(); // get shared_ptr >> Verificarea existentei void do_it(weak_ptr<Thing> wp){ shared_ptr<Thing> sp = wp. lock(); // get shared_ptr if(sp) sp->defrangulate(); do something else cout << "The Thing is gone!" << endl; } bool is_it_there(weak_ptr<Thing> wp) { if(wp. expired()) { cout << "The Thing is gone!" << endl; return false; } return true; }
smart pointers – unique_ptr >> similar cu shared_ptr >> asigura unicitatea (nu pot fi copiati sau atribuiti) unique_ptr<Thing> p 1 (new Thing); // p 1 owns the Thing unique_ptr<Thing> p 2(p 1); // error - copy construction is not allowed. unique_ptr<Thing> p 3; // an empty unique_ptr; p 3 = p 1; // error, copy assignment is not allowed >> pentru a fi folosit ca argument al unei functii trebuie trimis prin referinta
smart pointers – unique_ptr >> Transferul de “ownership” //create a Thing and return a unique_ptr to it: unique_ptr<Thing> create_Thing() { unique_ptr<Thing> local_ptr(new Thing); return local_ptr; // local_ptr will surrender ownership } unique_ptr<Thing> p 1(new Thing); // p 1 owns the Thing unique_ptr<Thing> p 2; // p 2 owns nothing // invoke move assignment explicitly p 2 = std: : move(p 1); // now p 2 owns it, p 1 owns nothing
smart pointers in trouble int main() { Aircraft* my. Aircraft = new Aircraft("F-16"); shared_ptr p. Aircraft(my. Aircraft); cout << p. Aircraft. use_count() << endl; shared_ptr p. Aircraft 2(my. Aircraft); cout << p. Aircraft 2. use_count() << endl; return 0; }
smart pointers in trouble void Start. Job() { shared_ptr p. Aircraft(new Aircraft("F-16")); Aircraft* my. Aircraft = p. Aircraft. get(); delete my. Aircraft; }
smart pointers in trouble void Start. Job() { shared_ptr pp. Aircraft(new Aircraft[3]); } void Start. Job() { shared_ptr pp. Aircraft(new Aircraft[3], [](Aircraft* p) { {delete[] p; }); }
smart pointers in trouble int main() { unique_ptr my. Aircraft = make_unique("F-22"); Aircraft* raw. Ptr = my. Aircraft. release(); return 0; }
Syntax sugar >> Digit separator auto million = 1'000; >> [[deprecated]] atribute >> User defined literals Bignum operator"" x(const char* p) { return Bignum(p); } void f(Bignum); f(1234374754987474874759875497524877072);
Bibliography - Using C++11’s Smart Pointers, David Kieras, University of Michigan - C++11 - the new ISO C++ standard, Bjarne Stroustrup (http: //www. stroustrup. com/C++11 FAQ. html) - Top 10 dumb mistakes to avoid with C++ 11 smart pointers, Deb Haldar
Multumesc! adrian. bulat@nottingham. ac. uk https: //www. adrianbulat. com
- Slides: 45