You are on page 1of 78

Korszerű programozási technikák

C++11 II. felvonás


constexpr
• C++ban mindig is voltak konstans kifejezések
• Például: 3 + 4

• Mindig ugyanaz az eredmény fordítási és futási időben is


constexpr
• Mi történik?
int get_five() {return 5;}

{…}

int some_value[get_five() + 7];


constexpr
• Mi történik?
int get_five() {return 5;}

{…}

int some_value[get_five() + 7]; //Nem konstans kifejezés

• A get_five() meghívhat még más függvényeket, stb.


• Nincs rá garancia, hogy fordítás-idejű konstans értékkel tér vissza
constexpr
• C++11 bevezette a constexpr kulcsszót, amivel a programozó
garantálhatja a fordítónak, hogy egy függvény vagy objektum
konstruktor fordítás-idejű konstans
• Lehetséges kiszámolni egy függvény vagy változó értékét fordítási
időben => gyorsabb lesz a program
constexpr int get_five() {return 5;}

{…}

int some_value[get_five() + 7];


constexpr
• A constexpr kulcsszó használatnak vannak határai

• Nem lehet virtuális a függvény


• Visszatérési típusa void-tól eltérő
• Minden paramétere literal típusú
constexpr
• A függvénytörzsben az alábbiak szerepelhetnek
• null statements (egyszerű pontosvessző)
• static_assert deklaráció
• typedef és alias deklarációk, amik nem definiálnak osztályt vagy enumot
• using deklaráció
• using direktíva
• Pontosan egy return
constexpr
• Konstruktor esetén
• Minden paramétere literal típusú
• Az osztálynak nincsenek virtuális ősosztályai
• Nincs a konstruktorban try-block

• Minden ősosztályt és nem-statikus adattagot inicializálni kell


• Minden meghívott konstruktornak constexpr konstruktornak kell lennie
LiteralType
• A fordítóban deklarálva vannak a literal típusok
• Skalár
• Referencia
• Literal típusú tömb
• Literal class (minden függvénye constexpr)
constexpr
• C++14-ben, C++17-ben és C++20-ban tovább bővítették a
lehetőségeket

• http://en.cppreference.com/w/cpp/language/constexpr
• http://en.cppreference.com/w/cpp/named_req/LiteralType
constexpr - példák

constexpr int factorial(int n) {


return n <= 1 ? 1 : (n * factorial(n - 1));
}

//if nem szerepelhet constexpr függvényben (C++11)


//C++14-ben már lehet
constexpr - példák
class conststr {
const char* p;
std::size_t sz;

public:
template<std::size_t N>
constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
 
constexpr operator[](std::size_t n) const {
return n < sz ? p[n] : throw std::out_of_range("");
}

constexpr std::size_t size() const {


return sz;
}
};
Move constructor
Move constructor
• C++03-ban az ideiglenes változókat (jobbértékek – rvalues) sosem
szándékoztunk módosíthatóvá tenni

• Nem különböztettük meg őket a const T&-tól

• Azonban vannak esetek amikor érdemes lenne változtatni rajtuk


Move constructor
• C++11-ben bevezettek egy új, nem konstans referencia típust
– rvalue reference –, amit T&&-al jelölünk

• Ez olyan ideiglenes változókra utal, amiken a változtatások


engedélyezettek inicializálásuk után azért, hogy „mozgatást” lehessen
végezni rajtuk
Move constructor
• C++11-ben bevezettek egy új, nem konstans referencia típust
– rvalue reference –, amit T&&-al jelölünk

• Ez olyan ideiglenes változókra utal, amiken a változtatások


engedélyezettek inicializálásuk után azért, hogy „mozgatást” lehessen
végezni rajtuk

string Foo() {return ”Foo”;}


{…}
string bar = Foo();

Balérték - lvalue Jobbérték - rvalue


Move constructor string Foo() {return ”Foo”;}
{…}
string bar = Foo();
Mi történik?

1. Létrejön a ”Foo”-ból egy ideiglenes string a memóriában (foglalás, másolás)

2. A bar változó értéke felveszi az ideiglenes string értékét (memóriafoglalás,


másolás)
Move constructor
• Ne foglaljunk kétszer és ne másoljunk kétszer!

• Mozgassuk át a visszatérési értéket és ne másoljuk

• Az eredeti objektum destruktora ugyan lefut, de az objektumban levő


dinamikus adattagok nem szabadulnak fel
template <class T>
class Foo {
private:
T * tarolo;
int meret;
int akt_db;
public:
Foo() {
tarolo = new T[5];
meret = 5;
akt_db = 0;
}
Foo(Foo &&other) {
tarolo = other.tarolo;
other.tarolo = nullptr;
meret = other.meret;
akt_db = other.akt_db;
}
~Foo() {
delete [] tarolo;
tarolo = nullptr;
}
};
Move constructor
template <class T>
Foo<T> getFoo() {
Foo<T> f;
return f;
}

int main() {
Foo<int> f1 = getFoo<int>();
}
Move constructor
Foo<int> f; 5 3 12 11 4
Move constructor
Foo<int> f; 5 3 12 11 4 f

f1 = getFoo<int>(); f1
Move constructor
Foo<int> f; 5 3 12 11 4 f

f1 = getFoo<int>(); f1
Move constructor
Foo<int> f; 5 3 12 11 4 f

f1 = getFoo<int>(); f1
Move constructor
• Ha visszatérési értékként olyan változóba tároljuk, ami már létezik,
akkor az operator= fut le
• Létezik move operator= is
• Szintén jobbérték referenciát kap
• Ha van kifejtett másoló konstruktor, akkor a default move konstruktor
deleted
• Ha van kifejtett move konstruktor, akkor a default másoló konstruktor
deleted
• Az aktuális változó tartalmát fel kell szabadítani
Move constructor
Foo<T>& operator= (Foo<T> &&right) {
if (&right == this)
return *this;

delete [] tarolo;
tarolo = right.tarolo;
right.tarolo = nullptr;
akt_db = right.akt_db;
meret = right.meret;
return *this;
}
Lambda függvények
Függvény pointerek
• C-ben, C++-ban mindig is léteztek
bool foo(int a) {
return a < 0;
}

...

bool (*pf)(int) = &foo;


cout << pf(-4) << endl;
Függvény pointerek
• C++11-ben kicsit egyszerűbb keret van hozzá
• #include <functional>
bool foo(int a) {
return a < 0;
}

...

std::function<bool(int)> pf = &foo;
cout << pf(-4) << endl;
Lambda függvények és kifejezések
• C++11-ben létrehozhatunk névtelen függvényeket is (lambda
függvények)

• Általános célú függvények esetén is nagyon jó

• #include <functional>
Lambda függvények és kifejezések

[](int x, int y) -> int {return x + y; }


Lambda függvények és kifejezések

[](int x, int y) -> int {return x + y; }

capture-list
(később bővebben)
Lambda függvények és kifejezések

[](int x, int y) -> int {return x + y; }

capture-list
(később bővebben) Paraméterei a lambda
függvénynek
Lambda függvények és kifejezések

[](int x, int y) -> int {return x + y; }

capture-list
(később bővebben) Paraméterei a lambda
Visszatérési típus
függvénynek
Lambda függvények és kifejezések

[](int x, int y) -> int {return x + y; }

Függvénytörzs
capture-list
(később bővebben) Paraméterei a lambda
Visszatérési típus
függvénynek
Lambda függvények és kifejezések

auto add = [](int x, int y) -> int {return x + y; }


int res = add(5, 4);
Lambda függvények és kifejezések

auto add = [](int x, int y) -> int {return x + y; }


int res = add(5, 4);

//Generikusan ugyanez

auto add = [](auto x, auto y) { return x + y; }


auto res = add(sring(”alma”), string(”fa”));
A visszatérési típust a
fordító kitalálja
Lambda függvények és kifejezések
• Lambda függvény paraméterként is átadható
bool stringCheck(string s, std::function<bool (char)> condition) {
for(unsigned i = 0; i < s.size(); i++)
{
if(condition(s[i])) return false;
}
return true;
}

bool lowerCase = stringCheck(”almaFa”,


[](char i) -> bool {return i >= ’A’ && i <= ’Z’ ? true : false; });

bool onlyLetter = stringCheck(”kajak”,


[](char i) -> bool {return i >= ’0’ && i <= ’9’ ? true : false; });
Lambda függvények és kifejezések
• Capture-list: Segítségével a függvényből hivatkozhatunk olyan
változókra, amelyek ott léteznek, ahol megírtuk a lambdakifejezést

• [] semmit nem kapunk el


• [=] a függvény megkapja a lambda kifejezés környezetében levő összes
változó másolatát
• [&] a változókat referencia szerint adjuk át
• [a] az ’a’ nevű változót adjuk át érték szerint
• [=,&a] minden változót érték szerint adunk át, de az ’a’ változót
referenciaként
• [this] befoglaló objektum címét adjuk át
Lambda függvények és kifejezések
• Mi történik?

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[=]() {c[a] += b;};
Lambda függvények és kifejezések

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[=]() {c[a] += b;}; X
c must be modifiable lvalue.
Lambda függvények és kifejezések
• Mi történik?

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[&]() {c[a] += b;};
Lambda függvények és kifejezések

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[&]() {c[a] += b;}; OK
Lambda függvények és kifejezések
• Mi történik?

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[&a, b, c]() {b *= c[a];};
Lambda függvények és kifejezések

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[&a, b, c]() {b *= c[a];}; X b must be a modifiable lvalue
Lambda függvények és kifejezések
• Mi történik?

int a = 3, b = 5;
vector<int> c{ 2,3,5,6,7,1,2,3 };
[&a, &b, c]() {b *= c[a];}; OK
Lambda függvények és kifejezések
• Lambda függvények további lehetőségekkel bővültek

• Részletek: http://en.cppreference.com/w/cpp/language/lambda
Példa
#include <iostream>
#include <function>

int main() {
[out = std::ref(std::cout << "Hello")]() {out.get() <<
"World\n";}();
}
Multithreading
Bevezető
Multithreading
• Az std::thread osztály segítségével létrehozhatunk szálakat,
amiknek paraméterül megadjuk a végrehajtandó függvényt

• A megadott függvény általában void visszatérési típusú, mivel a


thread nem kezeli a visszatérési értéket, akkor sem, ha van

• #include <thread>

• mutex-ek segítségével megoldhatjuk a kölcsönös kizárást


Multithreading void hello() {
cout << ”H” << endl;
cout << ”E” << endl;
cout << ”L” << endl;
cout << ”L” << endl;
cout << ”O” << endl;
}

Mit ír ki? void bye(int i) {


for(int j = 0; j < i; j++)
cout << j << endl;
}

thread t1(hello);
thread t2(bye, 5);
Multithreading
Fork-Join
• Először is, az előbbi kód hiányos…

main thread

Fork, a fő szálon kívül 2-t indítunk


Fork-Join
• Először is, az előbbi kód hiányos…

main thread

Fork, a fő szálon kívül 2-t indítunk


Fork-Join
• Először is, az előbbi kód hiányos…

main thread

Fork, a fő szálon kívül 2-t indítunk


Join, vissza kell ”csatolni” őket
a fő szálhoz miután végeztek
Fork-Join
• Először is, az előbbi kód hiányos…

main thread

Fork, a fő szálon kívül 2-t indítunk


Join, vissza kell ”csatolni” őket
a fő szálhoz miután végeztek
void hello() {
Multithreading cout << ”H” << endl;
cout << ”E” << endl;
cout << ”L” << endl;
cout << ”L” << endl;
cout << ”O” << endl;
}

void bye(int i) {
Mit ír ki? for(int j = 0; j < i; j++)
cout << j << endl;
}

thread t1(hello);
thread t2(bye, 5);
t1.join();
t2.join();
void hello() {
Multithreading cout << ”H” << endl;
cout << ”E” << endl;
cout << ”L” << endl;
cout << ”L” << endl;
cout << ”O” << endl;
}

void bye(int i) {
Mit ír ki? for(int j = 0; j < i; j++)
cout << j << endl;
Mind a két szál a cout-ra }
írogat, a kimenet attól
függ, hogy milyen gyorsan …
végeznek
thread t1(hello);
thread t2(bye, 5);
t1.join();
t2.join();
Ha túl sok a szál
• Elméletileg a programban akárhány szál lehet
• Fizikailag a processzor csak megadott számú szálat képes
párhuzamosan futtatni
• Ha ennél több szál van, akkor ezek között váltogatás, ütemezés
történik
unsigned int n = std::thread::hardware_concurrency();
std::cout << n << " concurrent threads are supported.\n";
Kölcsönös kizárás
• Ha egy változóhoz több szál hozzáfér, akkor át kell gondolni annak a
változónak a használatát
Kölcsönös kizárás
• Ha egy változóhoz több szál hozzáfér, akkor át kell gondolni annak a
változónak a használatát

• Ha read-only akkor már át is gondoltuk


Kölcsönös kizárás
• Ha egy változóhoz több szál hozzáfér, akkor át kell gondolni annak a
változónak a használatát

• Ha read-only akkor már át is gondoltuk

• Ha írható is, akkor biztosítani kell, hogy minden szál az aktuális


értékével számoljon
Mutex
• A szálak megpróbálják lefoglalni a mutexet

• Amelyik szál először lefoglalja az használhatja

• Ha végzett, fel kell szabadítani a mutexet, így más szálak is


hozzáférhetnek
Mutex std::mutex m;
• lock(): addig vár, amíg void foo(int & a) {
szabad nem lesz a mutex, és m.lock();
akkor lefoglalja a+=2;
m.unlock();
}
• unlock(): felszabadítja a …
lefoglalt mutexet int a = 5;
thread t1(foo, a);
thread t2(foo, a);
t1.join();
t2.join();
Mutex std::mutex m;
• try_lock(): Megnézi, hogy void foo(int & a) {
a mutex foglalt-e if(m.try_lock()) {
• Ha nem, lefoglalja és visszatér a+=2;
igazzal m.unlock();
• Ha foglalt, akkor visszatér }
hamissal és nem blokkol }

int a = 5;
thread t1(foo, a);
thread t2(foo, a);
t1.join();
t2.join();
Thread_local
• Statikus változóból csak egy példány van a memóriában

• Minden szál ugyan azt a változót látja

• A thread_local kulcsszóval minden szál saját példányt kap belőle

• A thread_local változó a szál létrehozásánál jön létre és futása


végén megszűnik
class A
{
Mit ír ki?
public static int a;
};
int A::a = 5;

void foo()
{
cout << ++A::a << endl;
}

int main()
{
thread t1(foo); t1.join();
thread t2(foo); t2.join();
thread t3(foo); t3.join();
}
class A
{
Mit ír ki?
public static int a;
}; 6
int A::a = 5;
7
void foo()
{ 8
cout << ++A::a << endl;
}

int main()
{
thread t1(foo); t1.join();
thread t2(foo); t2.join();
thread t3(foo); t3.join();
}
class A
{
Mit ír ki?
public static thread_local int a;
};
int A::a = 5;

void foo()
{
cout << ++A::a << endl;
}

int main()
{
thread t1(foo); t1.join();
thread t2(foo); t2.join();
thread t3(foo); t3.join();
}
class A
{
Mit ír ki?
public static thread_local int a;
}; 6
int A::a = 5; 6
void foo() 6
{
cout << ++A::a << endl;
}

int main()
{
thread t1(foo); t1.join();
thread t2(foo); t2.join();
thread t3(foo); t3.join();
}
Párhuzamosítás
• Megkülönböztetünk

• Adatpárhuzamosítást:
Az adatot szétosztjuk a szálak között a gyorsabb feldolgozás érdekében

• Számításpárhuzamosítást:
Az összetett műveletet szétszedjük részekre, minden szál a sajátját számolja ki
int foo = sqrt(bar) + sin(a) / arcsin(b);
Szál késleltetése
• C-ben is lehetett használni sleep(), usleep(), stb. függvényeket,
de ezek rendszerfüggőek
• C++11-ben van erre megfelelő megoldás a chrono névtér
használatával
• Azt a szálat altatja, amit meghívtunk
#include <chrono>

std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::this_thread::sleep_for(std::chrono::seconds(2));
Gyakorló feladat
Gyakorló feladat
• Készítsünk el a DegToRad és RadToDeg függvényeket, amelyek a
szögek átváltását végzik fok és radián között (double értékek)

• Mindkét függvény úgy működjön, hogy konstans értékekre a fordító


számolja ki az eredményt a program helyett
Gyakorló feladat
• Készítsünk egy FixStorage osztályt (egy része már készen van), amelyet
csak inicializáló lista segítségével lehet létrehozni
• A lista elemeit egy megfelelő méretű tömbben tárolja el

• Készítsük el a move konstruktort és a move = operátort az osztályhoz


Gyakorló feladat
• A megírt interpolate függvényt alapul véve készítsünk egy olyan
verziót interpolateFunc néven, amely még egy float->float függvényt
is kap paraméterül, amit alkalmazva a q paraméterre, lineáristól eltérő
interpoláció is lehetséges

• A kódban megadott helyre készítsünk két float->float lambda


függvényt, az egyik emelje négyzetre a paramétert, a másik vonjon
gyököt
Gyakorló feladat
• A kódban beolvasunk egy számot
• A megadott fibo függvényt használva határozzuk meg az ennyiedik
Fibonacci számot
• Mivel ez sok időt is elvehet, egy külön szálon fusson a függvény
• Egy másik szálon futtassunk egy függvényt, ami másodpercenként
kiírja, hogy a számolás még tart, egészen addig, amíg a fibo függvény
futása le nem áll
Tippek a szálkezeléses feladathoz
• Emlékeztető:
• „A megadott függvény általában void visszatérési típusú, mivel a thread
nem kezeli a visszatérési értéket, akkor sem, ha van”
• Javaslat: lambda függvény, capture list
• A kiszámolt értéket a meglévő res változóba mentsük el
• Használjuk a meglévő _mutex változót annak ellenőrzésére, hogy a
fibo függvény fut-e még

You might also like