www.ksi.mff.cuni.cz
Download
Report
Transcript www.ksi.mff.cuni.cz
Programování v C++
David Bednárek
www.ksi.mff.cuni.cz/~bednarek
Pravidla studia
NPRG041
2/2
Z,Zk
Zápis na cvičení
Elektronický
zápis do jednotlivých skupin
is.cuni.cz/studium
• Zápis předmětů a rozvrhu - zápis
• Grupíček - výsledky
Zapsáni musejí být všichni
Do 18.10.
Kapacita laboratoře je omezena, skupiny nelze přeplňovat
Zvláštní skupina pro repetenty
• Repetenti kontaktují cvičícího do 18.10.
Udělit
zápočet může jen cvičící,
ke kterému je student zapsán
Kdo nebude do 18.10. zapsán, zápočet v tomto šk. roce nedostane
Udělení zápočtu
Základní
podmínky společné všem skupinám
Úspěšné složení zápočtového testu
• 1. a 2. pokusy ve zkouškovém období ... 3. pokusy v dubnu
• 2-3 hodiny v laboratoři, společně pro všechny skupiny
Vypracování zápočtového programu
• Dohoda o tématu - do listopadu
• Předvedení cvičícímu do 31.3.2014
• Doladění a odevzdání do 23.5.2014
Další
podmínky udělení zápočtu určuje cvičící
Cvičící může podmínky individuálně upravit,
pokud se s ním student na začátku semestru dohodne
Přiměřená účast na cvičeních
Úspěšné odevzdání domácího úkolu
Zkouška
Zkouška
bude provedena formou abc-testu
Vlastnosti a pravidla jazyka C++
Používání knihoven C++ (kontejnery, algoritmy, iostream)
Typické konstrukce objektového programování
Run-time/static polymorphism
Termíny
Ve zkouškovém období ZS
Během výuky v LS
Pravidla pro budoucí neúspěšné
Zkouška
Pokud letos složíte zkoušku se známkou výborně nebo velmi dobře
a nedostanete zápočet, bude vám příští rok uznána
• Tento mechanismus je implementován zkoušejícími, nikoliv studijním
oddělěním
Zápočet
Pokud nedostanete zápočet, budete příští rok opakovat ty části,
které jste letos nesplnili
• Podmínky splněné letos se automaticky uznávají
• V příštím roce se musíte na začátku semestru přihlásit v SISu k
některému z cvičících a dohodnout se s ním na konkrétních
podmínkách
Historie C++
Historie C++
inspirace
BCPL
B
C
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
nadmnožina
téměř nadmnožina
významná změna
K&R C
C with classes
(Kernigan & Ritchie 1978)
(Stroustrup 1979)
The C++ programming language
(Stroustrup 1985)
C++98
(ISO/IEC 14882 1998)
šablony
C++03
(ISO/IEC 14882 2003)
C++TR1
(ISO/IEC 19768 2007)
paralelismus
C++11
C++14
(ISO/IEC 14882 2011)
(2014+)
Historie C++ a C
inspirace
BCPL
B
C
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
nadmnožina
téměř nadmnožina
významná změna
K&R C
C with classes
(Kernigan & Ritchie 1978)
(Stroustrup 1979)
The C++ programming language
(Stroustrup 1985)
ANSI C
(ANSI X3J11 1989)
C++98
C99
(ISO/IEC 14882 1998)
šablony
(ISO/IEC 9899 1999)
C++03
(ISO/IEC 14882 2003)
C++TR1
(ISO/IEC 19768 2007)
C11
(ISO/IEC 9899 2011)
paralelismus
C++11
C++14
(ISO/IEC 14882 2011)
(2014+)
Historie C++ - Objective-C
BCPL
B
C
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
inspirace
nadmnožina
téměř nadmnožina
významná změna
K&R C
Objective-C
C with classes
(Kernigan & Ritchie 1978)
(Stroustrup 1979)
(Cox & Love 1981)
The C++ programming language
Object-Oriented Programing
(Cox 1986)
(Stroustrup 1985)
ANSI C
(ANSI X3J11 1989)
C++98
C99
(ISO/IEC 14882 1998)
šablony
(ISO/IEC 9899 1999)
C++03
Objective-C 2.0
(ISO/IEC 14882 2003)
(Apple 2006)
C++TR1
(ISO/IEC 19768 2007)
Objective-C++
(Apple 2010)
C11
(ISO/IEC 9899 2011)
paralelismus
C++11
C++14
(ISO/IEC 14882 2011)
(2014+)
Historie C++ - významné příbuzné jazyky
BCPL
B
C
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
inspirace
nadmnožina
téměř nadmnožina
významná změna
K&R C
Objective-C
C with classes
(Kernigan & Ritchie 1978)
(Stroustrup 1979)
(Cox & Love 1981)
The C++ programming language
Object-Oriented Programing
(Cox 1986)
(Stroustrup 1985)
ANSI C
(ANSI X3J11 1989)
Java
(Sun 1995)
C++98
C99
(ISO/IEC 9899 1999)
(ISO/IEC 14882 1998)
šablony
C#
(Microsoft 2002)
Objective-C 2.0
C++03
(ISO/IEC 14882 2003)
(Apple 2006)
C++/CLI
(Microsoft 2005)
C++TR1
(ISO/IEC 19768 2007)
Objective-C++
(Apple 2010)
C11
(ISO/IEC 9899 2011)
paralelismus
C++11
C++14
(ISO/IEC 14882 2011)
(2014+)
Historie C++ - použití C v jádrech OS
BCPL
B
C
Unix
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
1973
inspirace
nadmnožina
téměř nadmnožina
významná změna
K&R C
C with classes
(Kernigan & Ritchie 1978)
Objective-C
(Stroustrup 1979)
(Cox & Love 1981)
MacOS
The C++ programming language
1984
Object-Oriented Programing
(Cox 1986)
(Stroustrup 1985)
ANSI C
(ANSI X3J11 1989)
Linux
Java
1991
Windows NT
(Sun 1995)
1993
C++98
C99
OS-X
(ISO/IEC 9899 1999)
2000
(ISO/IEC 14882 1998)
šablony
C#
(Microsoft 2002)
Objective-C 2.0
C++03
(ISO/IEC 14882 2003)
(Apple 2006)
C++/CLI
(Microsoft 2005)
C++TR1
(ISO/IEC 19768 2007)
Objective-C++
(Apple 2010)
C11
(ISO/IEC 9899 2011)
paralelismus
C++11
C++14
(ISO/IEC 14882 2011)
(2014+)
Literatura
Literatura
Pro začátečníky - před C++11
Bruce Eckel:
Thinking in C++ (2000)
Myslíme v jazyku C++ (Grada 2000)
Miroslav Virius:
Pasti a propasti jazyka C++ (Computer Press 2005)
Programování v C++ (ČVUT 2001)
Andrew Koenig, Barbara E. Moo:
Accelerated C++ (2000)
Stanley B. Lippman:
Essential C++ (2000)
Literatura
Pro středně pokročilé - před C++11
Andrei Alexandrescu, Herb Sutter:
C++ Coding Standards (2005)
Scott Meyers:
Effective C++ (1998)
More Effective C++ (1996)
Effective STL (2001)
Herb Sutter:
Exceptional C++ (2000)
More Exceptional C++ (2002)
Exceptional C++ Style (2004)
Nicolai M. Josuttis:
Object-Oriented Programming in C++ (2002)
The C++ Standard Library (1999)
Literatura
Až si budete myslet, že všechno umíte - před C++11
Andrei Alexandrescu:
Modern C++ Design (2001)
Moderní programování v C++ (Computer Press 2004)
David Vandevoorde, Nicolai M. Josuttis:
C++ Templates (2003)
Literatura
C++11
Scott Meyers: Overview of the New C++ (C++11)
360 slajdů z přednášek
Vysvětluje motivaci k novým vlastnostem
Bjarne
Stroustrup:
The C++ Programming Language - Fourth Edition
Addison-Wesley. ISBN 978-0321563842. May 2013
Učebnice celého C++
Zatím jediná učebnice obsahující C++11
Je lepší C++ nebo Java/C#?
Je lepší C++ nebo Java/C#?
Špatná otázka
Co programovat v C++
Pro které oblasti je C++ lepší než Java/C#?
Důraz na výkon
C++ umožňuje programovat způsobem, který neubírá na výkonu
• Když budete programovat v C++ stejným stylem jako v Java/C#,
dostanete přibližně stejný výkon
Spolupráce
s hardware
C++ nechystá na programátora nepříjemná překvapení (GC etc.)
Embedded assembler, spojování s jinými jazyky
Spolupráce
s OS
Všechny významné OS mají v C jádro a tudíž i rozhraní OS
Většina systémových aplikací je v C nebo C++
Nativní knihovny jazyků Java/C# jsou implementovány v C/C++
Generické
programování
Mechanismus šablon v C++ je silnější než v C/C++
Způsob implementace šablon v C++ neubírá na výkonu
Co programovat v C++
Programování orientované na výkon
Numerické výpočty
Převládající jazyky: FORTRAN, C
Pomalý posun směrem k C++
Databázové
systémy
Převládající jazyky: C/C++
• Existují i databázové systémy v Javě
• Spolehlivé srovnání výkonu neexistuje
Proč
je Java/C# pomalejší?
Garbage collection
• GC způsobuje mj. problémy s využitím cache
• Programování bez GC je pracnější, ale dává lepší výsledky
Chybí pokročilé metody optimalizace v překladačích
• Vektorizace, transformace cyklů, ...
Existují i situace, kdy je Java/C# rychlejší
• Překladače Javy/C# mají jednodušší úlohu
Co programovat v C++
Proč C++ a ne C
Stávající aplikace/knihovny/OS jsou často v C
Programování
v C++ je pohodlnější než v C
Menší pravděpodobnost chyb
Šablony, operátory, zapouzdření, ...
Při troše šikovnosti stejný výkon jako v C
Moduly
psané v C++ a C lze spojovat
extern "C" void do_something_in_C( int x);
void my_Cplusplus_function( int x)
{
do_something_in_C( x);
}
extern "C" void call_me_from_C( int y) { /* C++ code here */ }
Co neprogramovat v C++
Co raději neprogramovat v C++
Interaktivní
aplikace s GUI
C++ nemá standardizované rozhraní na GUI
Nativní rozhraní GUI v OS je většinou archaické C
Knihovny pro GUI jsou archaické, nepřenositelné nebo obojí
• Qt, GTK+, wxWidgets...
Garbage Collection při programování GUI citelně chybí
Pokud je zároveň zapotřebí výkon, nic jiného než C++ nezbývá
Aplikace
skládané z mnoha cizích součástí
Standard C++ poskytuje nedostatečné služby OS apod.
Cizí knihovny obvykle doplňují chybějící části vlastní tvorbou
Různé implementace chybějících částí mohou být v konfliktu
Proč C++
Proč (stále ještě) učíme C++?
• Většina řadových programátorů v C++ programovat nebude
MFF
chce vychovávat elitu
Programování OS, databází, překladačů
Vědecké výpočty vyžadující výkon
Hry, robotika,...
Údržba rozsáhlých a historických softwarových systémů
Porozumíte-li
tomu, jak funguje C++, budete lépe rozumět
jiným programovacím jazykům
architektuře počítačů a operačních systémů
překladačům
Zvládnutí
C++ je odznakem zdatnosti matfyzáka
Hello, World!
#include <iostream>
int main( int argc, char * * argv)
{
std::cout << "Hello, world!"
<< std::endl;
return 0;
}
Vstupní
bod programu
Dědictví jazyka C
• Žádné třídy ani metody
Globální funkce main
Parametry
programu
Z příkazové řádky
• Děleno na kousky
Archaické datové typy
• Ukazatel na ukazatel
• Logicky pole polí
std
- namespace knihoven
cout - standardní výstup
globální proměnná
<<
- výstup do streamu
přetížený operátor
endl
- oddělovač řádek
globální proměnná
Hello, World!
Dělení do modulů
Rozhraní
modulů je nutno
opsat do zvláštního souboru
.hpp - hlavičkový soubor
Definující
i používající modul
tento soubor inkluduje
// world.hpp
#ifndef WORLD_HPP_
#define WORLD_HPP_
void world();
#endif
textová direktiva #include
// main.cpp
#include "world.hpp"
int main( int argc, char * * argv)
{
world();
return 0;
}
// world.cpp
#include "world.hpp"
#include <iostream>
void world()
{
std::cout << "Hello, world!"
<< std::endl;
}
Hello, World!
// world.hpp
#ifndef WORLD_HPP_
#define WORLD_HPP_
#include <vector>
#include <string>
typedef std::vector< std::string> t_arg;
void world( const t_arg & arg);
#endif
// main.cpp
#include "world.hpp"
int main( int argc, char * * argv)
{
world( t_arg( argv + 1, argv + argc));
return 0;
}
// world.cpp
#include "world.hpp"
#include <iostream>
void world( const t_arg & arg)
{
if ( arg.empty() )
{
std::cout << "Hello, world!"
<< std::endl;
}
}
Architektura
Překladače / interpretry
CPU
CPU
CPU rozumí pouze binárním kódu
CPU
01010000 01110100
11010111 10010110
00100010 10110001
1940... – programování ve strojovém kódu
CPU
01010000 01110100
11010111 10010110
00100010 10110001
1940... – programování ve strojovém kódu
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
CPU
01010000 01110100
11010111 10010110
00100010 10110001
1940... – programování ve strojovém kódu
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
X
CPU
01010000 01110100
11010111 10010110
00100010 10110001
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
1950... – assembler
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
PRINT NOGEN
BEGIN
BEGIN
REGS
SR R2,R2
SR R3,R3
LOOP
AR R2,R3
LA R3,1(R0,R3)
C R3,=F'10'
BNE LOOP
CVD
R2,DBL
ED
RESULT,DBL+6
WTO
RESULT
RETURN
LTORG
RESULT
DC
X'40202120'
DBL
DC
D'0'
END
BEGIN
CPU
assembler
X
CPU
01010000 01110100
11010111 10010110
00100010 10110001
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
1950... – operační systém
PRINT NOGEN
BEGIN
BEGIN
REGS
SR R2,R2
SR R3,R3
LOOP
AR R2,R3
LA R3,1(R0,R3)
C R3,=F'10'
BNE LOOP
CVD
R2,DBL
ED
RESULT,DBL+6
WTO
RESULT
RETURN
LTORG
RESULT
DC
X'40202120'
DBL
DC
D'0'
END
BEGIN
CPU
assembler
X
CPU
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
loader
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
1950... – operační systém
PRINT NOGEN
BEGIN
BEGIN
REGS
SR R2,R2
SR R3,R3
LOOP
AR R2,R3
LA R3,1(R0,R3)
C R3,=F'10'
BNE LOOP
CVD
R2,DBL
ED
RESULT,DBL+6
WTO
RESULT
RETURN
LTORG
RESULT
DC
X'40202120'
DBL
DC
D'0'
END
BEGIN
X
CPU
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
loader
CPU
assembler
myprog.exe
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
1950... – překladač
X X
X
X
X
X
XXXX
X
X
X
X
X
X
XX
X X
XX
01010000 01110100
11010111 10010110
00100010 10110001
X
X
XX
X
X
X
X
X
X
X
X
X
myprog.exe
XX
X
X
X
X
X
X
X
X
X
loader
X
X X
XX
X
X
X XXX X
X
X
X
X
XX XXXX
X
XXXX
X
XX
XXX
X
X
X
operační
systém
X
X
X
X
X
CPU
překladač Fortran
X
X
X
X
X
X
XX
X
X
X
X
X
X
READ INPUT TAPE 5, 501, IA, IB, IC
501 FORMAT (3I5)
IF (IA) 777, 777, 701
701 IF (IB) 777, 777, 702
702 IF (IC) 777, 777, 703
703 IF (IA+IB-IC) 777,777,704
704 IF (IA+IC-IB) 777,777,705
705 IF (IB+IC-IA) 777,777,799
777 STOP 1
799 S = FLOATF (IA + IB + IC) / 2.0
AREA = SQRT( S * (S - FLOATF(IA))
* (S - FLOATF(IB)) *
+ (S - FLOATF(IC)))
WRITE OUTPUT TAPE 6, 601, IA, IB, IC
, AREA
STOP
END
X
X
CPU
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
X
X
X
X
X
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
CPU
1970... – překladač C
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
loader
#include <stdio.h>
int main(int,char**)
{
printf(
"Hello, world!\n");
}
CPU
překladač C
myprog.exe
Hello,
world!
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
CPU
1980... – překladač C++
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
loader
#include <iostream>
int main(int,char**)
{
std::cout <<
"Hello, world!\n";
}
CPU
překladač C++
myprog.exe
Hello,
world!
1960... – interpret(er)
CPU
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
10 INPUT "What is your name: ", U$
20 PRINT "Hello "; U$
30 INPUT "How do you want: ", N
40 S$ = ""
50 FOR I = 1 TO N
60 S$ = S$ + "*"
70 NEXT I
80 PRINT S$
90 INPUT "Do you want? ", A$
100 IF LEN(A$) = 0 THEN 90
110 A$ = LEFT$(A$, 1)
120 IF A$ = "Y" THEN 30
130 PRINT "Goodbye ";U$
140 END
interpret
operační
systém
X
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
Interpretace s mezikódem
10 INPUT "What is your name: ", U$
20 PRINT "Hello "; U$
30 INPUT "How do you want: ", N
40 S$ = ""
50 FOR I = 1 TO N
60 S$ = S$ + "*"
70 NEXT I
80 PRINT S$
90 INPUT "Do you want? ", A$
100 IF LEN(A$) = 0 THEN 90
110 A$ = LEFT$(A$, 1)
120 IF A$ = "Y" THEN 30
130 PRINT "Goodbye ";U$
140 END
CPU
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
04FBC41E
77AB2000
1AE04E33
překladač
interpret
operační
systém
X
X
X
X
X
X
X
X
X
XX
X XX XX
XX
X
XX
X
X X
X
X X
X
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
X
X
X
X
X
X
X
XX
X
X
X
XX
X
XXX
X
X
X
X
X
X
X
interpretovaný mezikód (bytecode)
CPU
myprog.class
překladač
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
04FBC41E
77AB2000
1AE04E33
Hello,
world!
CPU
public class
HelloWorld {
public static void
main(String[] args)
{ System.out.println(
"Hello, world!");
}
}
interpret
operační
systém
JIT překladač
CPU
myprog.class
překladač
13 0 1 0
10 0 8 0
9 0 13 0
8 0 16 0
7 0 2 0 0 0 0 12 0
15 0
6 0 2 0 7 0 9 0
6 0 2 0 8 0 8 0
10 0 4 0 0 2 0 7 0
7 0 1 0 2 0 0 5 0 1 0
8 0 0 8 0 7 0
10 0 1 0 0 0 2 0 8 0
11 0 4 0 9 0
13 0 0 2 0 0 0 0 6 0
7 0 0 0 0 8 0 5 0 0
3 0 17 0 0 0 5 0
20 10 0
JIT překladač
CPU
public class
HelloWorld {
public static void
main(String[] args)
{ System.out.println(
"Hello, world!");
}
}
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
Hello,
world!
Srovnání JIT/non-JIT
CPU
překladač
myprog.class
JIT překladač
CPU
public class
HelloWorld {
public static void
main(String[] args)
{ System.out.println(
"Hello, world!");
}
}
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
int main(int,char**)
{
std::cout <<
"Hello, world!\n";
}
CPU
#include <iostream>
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
CPU
překladač
myprog.exe
loader
Srovnání JIT/non-JIT
JIT (Java, C#, C++/CLI)
non-JIT (C++)
Distribuuje se bytecode (.class, .exe)
Distribuuje se (někdy) jako binární instrukce
(.exe)
Distribuce závislá na jazyku a překladači
Distribuce závislá na procesoru a OS
Překladač zná přesně cílovou architekturu,
může pozorovat chování programu
Překladač má dost času na překlad
Dynamické spojování…?
překladač
překladač
myprog.class
mylib.class
JIT překladač
CPU
}
public
class
}
HelloWorld
{
public static void
main(String[] args)
{ mylib.doit();
}
}
01010000 01110100
11010111 10010110
00100010 10110001
#include <iostream>
int main()
{ int main()
{
doit();
} std::cout <<
"Hello, world!\n";
}
CPU
operační
systém
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
překladač
myprog.exe
překladač
mylib.dll
loader
Dynamické spojování
překladač
překladač
myprog.class
mylib.class
JIT překladač
CPU
import acme.mylib;
public
class
public
class
HelloWorld
HelloWorld
{ {
public
static
void
public
static
void
main(String[]
args)
main(String[]
args)
{ System.out.println(
{ mylib.doit();
} "Hello, world!");
} }
}
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
// mylib.hpp
CPU
#include "mylib.hpp"
#include <iostream>
int main()
{ int main()
{
doit();
} std::cout <<
"Hello, world!\n";
}
void doit();
01010000 01110100
11010111 10010110
00100010 10110001
operační
systém
“překladač”
myprog.exe
“překladač”
mylib.dll
loader
Překlad jednoduchého programu - statické spojování
// iostream
// iostream
iostream.obj
#include <fstream>
#include <fstream>
namespace std {
namespace std {
extern ofstream
extern ofstream
cout, cerr;
cout, cerr;
};
};
// myprog.cpp
#include <iostream>
int main()
{
std::cout <<
"Hello, world!\n";
}
msvcrt.lib
Kompilátor
myprog.obj
Linker
myprog.exe
Oddělený překlad a spojování modulů
Standardní
Standardní .obj
Standardní
.lib
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
.obj
Linker
Spustitelný
soubor .exe
Spojování modulů
myprog.cpp
#include "bee.hpp"
int main(int,char**)
{
return B( 7);
}
myprog.obj
Kompilátor
0000: 01010000 ???????? 11010111
export main(int,argv**)
import B(int)
bee.hpp
myprog.exe
#ifndef bee_hpp
#define bee_hpp
int B( int q);
#endif
Linker
0000: 01010000 00001100 11010111
1100: 10010110 00100010 10110001
bee.cpp
#include "bee.hpp"
int B( int q)
{
return q+1;
}
bee.obj
Kompilátor
0000: 10010110 00100010 10110001
export B(int)
make
Standardní
Standardní .obj
Standardní
.lib
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
Make
makefile
.obj
Linker
Spustitelný
soubor .exe
Integrované prostředí
Standardní
Standardní .obj
Standardní
.lib
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
Editor
.obj
Linker
Spustitelný
soubor .exe
Debugger
projekt
Statické knihovny
Standardní
Standardní .obj
Standardní
.lib
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
.obj
Knihovní
Knihovna
.hpp
Knihovní
.cpp
Linker
Kompilátor
Přeložené
.obj
Spustitelný
soubor .exe
.lib
Librarian
Dynamické knihovny (Microsoft)
Standardní
Standardní .obj
Standardní
.lib
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
.obj
Knihovní
Knihovna
.hpp
Knihovní
.cpp
Linker
Kompilátor
Přeložené
.obj
Linker
Spustitelný
soubor .exe
.lib
Knihovna
.dll
Dynamické knihovny (GNU)
Standardní
Standardní
.o
Standardní
.a
Uživatelské
.hpp
Uživatelské
.cpp
Kompilátor
Přeložené
.o
Linker
Kompilátor
Přeložené
.o
Librarian
Spustitelný
soubor
Knihovní
.hpp
Knihovní
.cpp
Knihovna
.so
Deklarace a definice
Deklarace a definice
Deklarace
Zápis sdělující, že věc (typ/proměnná/funkce/...) existuje
Identifikátor
Základní vlastnosti věci
Umožňuje překladači přeložit kód, který na věc odkazuje
• V některých případech je k tomu zapotřebí i definice
Definice
Zápis, který určuje všechny vlastnosti věci
Obsah třídy, inicializace proměnné, kód funkce
Umožňuje překladači vygenerovat kód a data, která věc reprezentují
za běhu
Každá
definice je i deklarace
Deklarace umožňují (některá) použití věci bez definice
Oddělený překlad modulů
Vyřešení cyklických závislostí
Zmenšení objemu překládaného zdrojového kódu
Deklarace a definice
One-definition rule #1:
Jedna překladová jednotka...
• (modul, tj. jedno .cpp včetně inkludovaných hpp)
...
smí obsahovat nejvýše jednu definici věci
One-definition rule #2:
Program...
• (tj. .exe včetně připojených .dll)
...
smí obsahovat nejvýše jednu definici proměnné nebo
non-inline funkce
Definice třídy, typu či inline funkce se v různých modulech opakovat
smějí (typicky vložením téhož .hpp souboru)
• Nejsou-li opakované definice totožné, nebo nesouhlasí-li definice
s deklarací, program je nekorektní
Diagnostika na úrovni programu není normou požadována a
překladače/linkery ji dělají jen v jednoduchých případech
Deklarace a definice tříd a typů
Deklarace
Definice
Třída
class A;
class A {
...
};
Struktura
struct A;
struct A {
...
};
Unie (v C++
prakticky
nepoužitelné)
union A;
union A {
...
};
Pojmenovaný
typ
typedef
typedef
typedef
typedef
typedef
typedef
typedef
typedef
typedef
A A2;
A * AP;
std::shared_ptr< A> AS;
A AA[ 10];
A AF();
AF * AFP1;
A (* AFP2)();
std::vector< A> AV;
AV::iterator AVI;
Deklarace a definice proměnných
Deklarace
Definice
Globální
proměnná
extern int x, y, z;
int x;
int y = 729;
int z( 729);
Statická položka
třídy
class A {
static int x, y, z;
};
int A::x;
int A::y = 729;
int A::z( 729);
Statická
konstantní
položka třídy
class A {
static const int x = 729;
};
Statická lokální
proměnná
void f() {
static int x;
static int y = 7, z( 7);
}
Nestatická
položka třídy
class A {
int x, y;
};
Nestatická
lokální
proměnná
void f() {
int x;
int y = 7, z( 7);
};
Deklarace a definice funkcí
non-inline
Deklarace (.hpp nebo .cpp)
Definice (.cpp)
Globální funkce
int f( int, int);
int f( int p, int q)
{ return p + q;}
Statická metoda
class A {
static int f( int p);
};
int A::f( int p)
{ return p + 1;
}
Nestatická
metoda
class A {
int f( int p);
};
int A::f( int p)
{ return p + 1;
}
Virtuální metoda
class A {
int A::f( int)
virtual int f( int p); { return 0;
};
}
inline
Deklarace (.hpp nebo .cpp)
inline int f( int p, int q)
{ return p + q;
}
Globální inline
funkce
Nestatická inline
metoda (a)
Nestatická inline
metoda (b)
Definice (.hpp nebo .cpp)
class A {
int f( int p);
};
inline int A::f( int p)
{ return p + 1;
}
class A {
int f( int p) { return p+1;}
};
Umístění dat
Umístění dat
Statická
alokace = 1 instance na proces
Globální proměnná
Statická položka třídy
Statická lokální proměnná
[C++11]
thread_local objekty = 1 instance na vlákno
Zásobníková alokace = 1 instance na každé vyvolání
Lokální proměnná
Parametr předávaný hodnotou
Návratová hodnota funkce
Pomocná proměnná při výpočtu výrazu
Dynamická
alokace = řízeno programem
Dynamicky alokovaná data (new/delete)
Inicializace
Číselné
typy, ukazatele
Statická alokace
• Inicializováno nulou
Zásobníková nebo dynamická alokace
• Neinicializováno!
Třídy
/ struktury
Inicializováno konstruktorem
• Lze určit parametry pro konstruktor
Není-li konstruktor, platí tato pravidla pro jednotlivé části
Parametr
předávaný hodnotou / návratová hodnota funkce
Inicializován copy-constructorem
• Není-li definován, je generován překladačem
Okamžik inicializace a destrukce
Inicializace
Destrukce
Globální proměnná
Před vstupem do main
Po výstupu z main
Statická položka třídy
Před vstupem do main
Po výstupu z main
Statická lokální
proměnná
Při prvním průchodu řízení
deklarací
Po výstupu z main
Lokální proměnná
V okamžiku průchodu řízení
deklarací
Při výstupu z bloku
Parametr předávaný
hodnotou
Před voláním funkce
Před návratem z funkce
Návratová hodnota
funkce
V příkazu return
Po návratu z funkce
Pomocná proměnná
při výpočtu výrazu
Když je vypočtena její hodnota
Na konci příkazu (v deklaraci: na
konci bloku)
Dynamicky alokovaná
data
Při volání new
Při volání delete
Nejdůležitější datové typy
Vybrané číselné typy
bool
false, true
char
znak základní sady (např. ASCII, 8 bit)
std::wchar_t
znak rozšířené sady (např. Unicode, 16/32 bit)
int
celé číslo se znaménkem (obvykle 32 bit)
unsigned
celé číslo bez znaménka (obvykle 32 bit)
long long
extra velké celé číslo se znaménkem (64 bit)
unsigned long long
extra velké celé číslo bez znaménka (64 bit)
std::size_t
dostatečně velké číslo pro velikost čehokoliv (32/64 bit)
double
“reálné” číslo (Intel: 64 bit)
long double
přesnější “reálné” číslo (Intel: 80 bit)
std::complex<double>
komplexní číslo dané přesnosti
Další důležité typy
std::string
řetězec (nad char)
std::wstring
řetězec (nad std::wchar_t)
std::istream
vstupní proud (nad char)
std::wistream
vstupní proud (nad std::wchar_t)
std::ostream
výstupní proud (nad char)
std::wostream
výstupní proud (nad std::wchar_t)
struct T { … }
struktura
std::pair<T1,T2>
uspořádaná dvojice s prvky typu T1 a T2
std::vector<T>
pole prvků typu T
std::list<T>
seznam prvků typu T
std::map<K,T>
asociativní pole prvků typu T indexované typem K
std::multimap<K,T>
asociativní pole s opakováním
Složené typy v C++
Složené typy jsou iluze poskytovaná překladačem
Souvislý úsek paměti dělený na elementární typy
• sizeof(T) = velikost tohoto úseku
• Zarovnání může vynutit nevyužití některých míst
Veškerá manipulace je překladačem rozložena na manipulaci s
elementárními typy
• V jednoduchých případech (POD) lze kopírovat jako blok bajtů
Pole:
T a[N]
N-tice stejných typů
N musí být překladači známá konstanta
Třída
(class nebo struct)
Pojmenované položky různých typů
• Ve složitějších případech režijní informace
Dědičnost implementována vložením předka
• Virtuální dědičnost vyžaduje nepřímé odkazy na předky
Ukazatel vs. hodnota
Reference, ukazatelé, iterátory
Reference
Konstrukce jazyka C++
Použití syntakticky shodné s hodnotou (r.a)
T &
const T &
Ukazatel
Konstrukce jazyka C/C++
Zvláštní operátory pro přístup k hodnotě (*p, p->a)
Ukazatelová aritmetika pro přístup k sousedům v polích
Manuální alokace/dealokace objektů
T *
const T *
Chytré ukazatele
Třída ve standardních knihovnách C++
Zvláštní operátory pro přístup k hodnotě (*p, p->a)
Automatická dealokace při zániku odkazů
std::shared_ptr< T>
std::unique_ptr< T>
Iterátory
Třídy reprezentující odkazy na prvky kontejneru typu K
Zvláštní operátory pro přístup k hodnotě (*p, p->a)
Napodobenina ukazatelové aritmetiky
K::iterator
K::const_iterator
Referenční semantika C#/Java vs. C++
Referenční typy (C#,Java)
Ukazatele na objekty (C++)
class T {
public int a;
}
class T {
public:
int a;
};
class test {
static void f( T z)
{
z.a = 3;
}
static void g()
{
T x = new T();
//vznik
void f( T * z)
{
z->a = 3;
}
void g()
{
T * x = new T;
//vznik
x.a = 1;
x->a = 1;
T y = x;
//druhý odkaz
T * y = x;
//druhý odkaz
y.a = 2;
// x.a == 2
y->a = 2;
// x->a == 2
f( x);
// x.a == 3
f( x);
// x->a == 3
//zrušení zařídí garbage collector
delete x;
//zánik je nutno vyvolat ručně
}
}
}
Referenční semantika C#/Java vs. C++
Referenční typy (C#,Java)
Chytré ukazatele na objekty (C++)
class T {
public int a;
}
class T {
public:
int a;
};
class test {
static void f( T z)
{
z.a = 3;
}
static void g()
{
T x = new T();
//vznik
void f( T * z)
{
z->a = 3;
}
void g()
{
std::shared_ptr< T> x = new T;
//vznik
x.a = 1;
x->a = 1;
T y = x;
//druhý odkaz
std::shared_ptr< T> y = x;
//druhý odkaz
y.a = 2;
// x.a == 2
y->a = 2;
// x->a == 2
f( x);
// x.a == 3
f( x);
// x->a == 3
//zrušení zařídí garbage collector
}
}
//zrušení při zániku posledního odkazu
}
Hodnotová semantika C#/Java vs. C++
Hodnotové typy (C#)
Objekty (C++)
struct T {
int a;
}
class T {
public:
int a;
};
class test {
static void f( T z)
{
z.a = 3;
}
static void g()
{
T x;
//vznik
void f( T z)
{
z.a = 3;
}
void g()
{
T x;
//vznik
x.a = 1;
x.a = 1;
T y = x;
//kopie
T y = x;
//kopie
y.a = 2;
// x.a == 1
y.a = 2;
// x.a == 1
f( x);
// x.a == 1
f( x);
// x.a == 1
//zrušení v okamžiku zániku proměnné
}
}
//zrušení v okamžiku zániku proměnné
}
Hodnotová semantika C#/Java vs. C++
Předání odkazem (C#)
(hodnotové typy)
Předání odkazem (C++)
struct T {
int a;
}
class T {
public:
int a;
};
class test {
static void f( ref T z)
{
z.a = 3;
}
static void g()
{
T x;
//vznik
void f( T & z)
{
z.a = 3;
}
void g()
{
T x;
x.a = 1;
x.a = 1;
f( ref x);
// x.a == 3
}
}
f( x);
// x.a == 3
}
Referenční semantika C#/Java vs. C++
Předání odkazem (C#)
Referenční typy
Předání odkazem (C++)
Reference na ukazatel
Užíváno řídce – nebezpečí chyb
class T {
public int a;
}
class T {
public:
int a;
};
class test {
static void f( ref T z)
{
z = new T();
//vznik
}
static void g()
{
T x = new T();
//vznik
f( ref x);
// x je nyní jiný objekt
void f( T * & z)
{
delete z;
//zánik je nutno vyvolat ručně
z = new T;
//vznik
}
void g()
{
T * x = new T;
//vznik
f( x);
// *x je nyní jiný objekt
//zrušení zařídí garbage collector
}
delete x;
//zánik je nutno vyvolat ručně
}
}
Dynamická alokace
my_class * p = 0;
void f1()
{
p = new my_class( 20, 30);
}
void f2()
{
delete p;
}
Dynamická
alokace pole
std::vector je lepší
void f2( std::size_t n)
{
int * q = new int[ n];
q[ n-1] = p->m();
// ...
delete[] q;
}
Dynamickou
alokaci je
nutné použít, pokud
Rozsah života objektu se
nekryje s vyvoláním
funkce, nebo
Alokovaný objekt je třída
s dědičností zařazená do
datové struktury
Ostatní
případy lze
většinou nahradit použitím
kontejnerů
Kontejnery skrývají
dynamickou alokaci uvnitř
Chytré ukazatele
void f1()
{
std::unique_ptr< my_class> p
= new my_class( 20, 30);
std::unique_ptr< my_class> q
= std::move( p); // nuluje p
q.reset( new my_class( 10, 20));
// dealokuje původní objekt
} // dealokuje druhý objekt
void f2()
{
std::shared_ptr< my_class> p
= new my_class( 20, 30);
std::shared_ptr< my_class> q
= p;
q.reset( new my_class( 10, 20));
} // dealokuje oba objekty
Chytré
ukazatele řeší
dealokaci samy
C++11
unique_ptr
vždy jen jediný odkaz
• zajistí překladač
shared_ptr
počítání odkazů
• režie za běhu
Slabší a pomalejší než
Garbage Collection
• problém: cyklické
struktury
Reference
Reference
Hodnotové
typy
(C++,C#,...)
Referenční
typy (C#,Java)
Lokálně
existující
objekty (C++)
Reference na
objekty (C++)
Ukazatele na
objekty (C++)
f(int y);
class T {...}
class T {...}
class T {...}
class T {...}
void g()
{
int x;
//vznik
void g()
{
T x = new T;
//vznik
void g()
{
T x;
//vznik
void g()
{
T x;
//vznik
void g()
{
T * x = new T;
//vznik
int y = z;
//kopie
T y = x;
//druhý odkaz
T y = x;
//kopie
T & y = x;
//odkaz
T * y = x;
//druhý odkaz
y.f();
y.f();
y.f();
y->f();
}
//zrušení při
//výstupu z bloku
}
//zrušení zařídí
//garbage
//collector
}
//zánik při
//výstupu z bloku
}
delete y;
//zánik
//zánik při
//výstupu z bloku
x->f();
//chyba
}
Reference a ukazatelé
Ukazatel
(*) a reference (&)
Z hlediska implementace ekvivalentní - ukazatel i reference na
objekt jsou reprezentovány adresou tohoto objektu
Ukazatel umí navíc:
• Přesměrování jinam
• Speciální hodnotu – nulový ukazatel
• Ukazatelovou aritmetiku – posouvání na sousední objekty v poli
• Dealokaci – operator delete
Referenci je možno použít jenom jedním z následujících způsobů:
• Typ proměnné
• Typ položky třídy
• Typ parametru funkce
• Typ návratové hodnoty funkce
Odlišná syntaxe použití:
• Objekt a reference na objekt se syntakticky neliší
• Ukazatel se dereferencuje operátorem *
• Ukazatel na objekt se získá operátorem &
Reference a ukazatelé
Pravidla pro začátečníky
Kdy použít referenci: T &
Výstupní parametr
Návratová hodnota funkce zpřístupňující objekt
• T & vector<T>::at(size_t i)
Kdy
použít konstantní referenci: const T &
Obvykle pouze kvůli rychlosti
Parametr typu struktura/třída
Návratová hodnota funkce zpřístupňující objekt ke čtení
• const T & vector<T>::at(size_t i) const
Kdy
použít ukazatel (T *)
Je-li objekt dynamicky alokován
Je-li nutná schopnost přesměrování, null, nebo aritmetika
Nelze-li referenci správně namířit v okamžiku inicializace
Kdy
použít konstantní ukazatel (const T *)
Sekundární odkaz na objekt, schopný pouze čtení
Reference a ukazatelé
Pravidla pro pokročilejší
Vlastník dynamicky alokovaného objektu
je zodpovědný za jeho zrušení - musí použít ukazatel “T *”
nelze-li jednoznačně určit vlastníka, použijte “shared_ptr<T>”
Uživatel
objektu
Pokud je životnost pozorovatele kratší než životnost objektu
• lze použít referenci – “T &” nebo “const T &”
Pokud je životnost delší než životnost objektu nebo jinak
komplikovaná
• je nutné použít ukazatel – “T *” nebo “const T *”
Reference
Novinky
související s existencí reference
Inicializaci reference nelze nahradit přiřazením
• Třídy obsahující referenci musí mít konstruktor
Nelze rozlišit skutečné parametry předávané hodnotou a odkazem
Návratová hodnota funkce může být l-hodnota
a.at( i) = x;
Zvýšené nebezpečí nekorektních konstrukcí
int & f()
{
int x;
return x; // funkce vrátí referenci na neexistující objekt
}
Vracení odkazem
Funkce jako add nemůže vracet referenci
add vrací hodnotu různou od všech svých parametrů
hodnotu parametrů nesmí měnit
reference nemá na co ukazovat
Špatné
řešení č. 1: Lokální proměnná
Complex & add( const Complex & a, const Complex & b)
{
Complex r( a.Re + b.Re, a.Im + b.Im);
return r;
}
BĚHOVÁ
CHYBA: r zaniká při návratu z funkce
Vracení odkazem
Funkce jako add nemůže vracet referenci
add vrací hodnotu různou od všech svých parametrů
hodnotu parametrů nesmí měnit
reference nemá na co ukazovat
Špatné
řešení č. 2: Dynamická alokace
Complex & add( const Complex & a, const Complex & b)
{
Complex * r = new Complex( a.Re + b.Re, a.Im + b.Im);
return * r;
}
PROBLÉM:
kdo to odalokuje ?
Vracení odkazem
Funkce jako add nemůže vracet referenci
add vrací hodnotu různou od všech svých parametrů
hodnotu parametrů nesmí měnit
reference nemá na co ukazovat
Špatné
řešení č. 3: Globální proměnná
Complex g;
Complex & add( const Complex & a, const Complex & b)
{
g = Complex( a.Re + b.Re, a.Im + b.Im);
return g;
}
CHYBA:
globální proměnná je sdílená
Complex a, b, c, d, e = add( add( a, b), add( c, d));
Vracení odkazem
Funkce jako add musí vracet hodnotou
add vrací hodnotu různou od všech svých parametrů
hodnotu parametrů nesmí měnit
reference nemá na co ukazovat
Správné
řešení
Complex add( const Complex & a, const Complex & b)
{
Complex r( a.Re + b.Re, a.Im + b.Im);
return r;
}
Zkrácený (ekvivalentní) zápis
return Complex( a.Re + b.Re, a.Im + b.Im);
Vracení odkazem
Funkce jako add musí vracet hodnotou
Complex add( const Complex & a, const Complex & b)
{
Complex r( a.Re + b.Re, a.Im + b.Im);
return r;
}
Data
se při vracení z funkce (několikrát) kopírují
z = add( x, y);
plnění proměnné r [constructor]
kopie ven z funkce [copy-constructor]
přiřazení [operator=]
[C++11] rvalue reference mohou některá kopírování usnadnit
Řešení
bez kopírování existuje
za cenu dynamické alokace
u malých dat (Complex, string) se nevyplatí
Vracení odkazem
Řešení bez kopírování
class ComplexBody
{
public:
ComplexBody( double r, double i)
: re( r), im( i) {}
double re, im;
};
class Complex
{
public:
Complex( double r, double i)
: b( new ComplexBody( r, i)) {}
double re() const { return b->re; }
double im() const { return b->im; }
private:
std::shared_ptr< ComplexBody> b;
};
Complex add(
const Complex & a, const Complex & b)
{
return Complex(
a.re() + b.re(), a.im() + b.im());
return r;
}
Ekvivalent garbage-collection
ComplexBody je sdíleno několika
instancemi Complex a zaniká s
posledním z nich
Garbage-collection metodou markand-sweep bývá rychlejší než počítání
odkazů (shared_ptr)
Pro malé třídy (Complex) je kopírování
levnější než dynamická alokace
Reference a ukazatelé
Pravidla pro vracení hodnot odkazem
Pokud hodnota, kterou funkce vrací, existuje v nějakém
objektu i po návratu z funkce, lze vrátit odkaz na tento
objekt (konstantní) referencí
T & vector<T>::back();
const T & vector<T>::back() const;
T & T::operator+=(const T & b);
T & T::operator++();// prefixová verze ++ vrací novou hodnotu
Pokud
se hodnota, kterou funkce vrací, nově spočítala a
není nikde uložena, funkce musí vracet hodnotou
T operator+( const T & a, const T & b);
T T::operator++(int);// postfixová verze ++ vrací starou hodnotu
STL
Standard Template Library
STL
Kontejnery
Prefabrikáty základních datových struktur
Šablony parametrizované typem ukládaného objektu
Všechny
kontejnery pracují s kopiemi vkládaných hodnot
Typ hodnot musí mít alespoň copy-constructor a destruktor
Některé druhy kontejnerů či operací s nimi vyžadují i operator= nebo
konstruktor bez parametrů
Hodnoty
se přidávají a odebírají metodami odpovídajícími
druhu kontejneru
K hodnotám je možno přistupovat pomocí iterátoru, který
reprezentuje inteligentní ukazatel dovnitř kontejneru
Prostřednictvím iterátoru je možno měnit uložené hodnoty
STL – Příklad
#include <deque>
typedef std::deque< int> my_deque;
my_deque the_deque;
the_deque.push_back( 1);
the_deque.push_back( 2);
the_deque.push_back( 3);
int x = the_deque.front(); // 1
the_deque.pop_front();
my_deque::iterator ib = the_deque.begin();
my_deque::iterator ie = the_deque.end();
for ( my_deque::iterator it = ib; it != ie; ++it)
{
*it = *it + 3;
}
int y = the_deque.back(); // 6
the_deque.pop_back()
int z = the_deque.back(); // 5
STL – Kontejnery
Sekvenční kontejnery
• Seřazeny podle vzrůstající režie
[C++11] array< T, N> - pole se staticky danou velikostí
vector< T> - pole prvků s přidáváním zprava
basic_string< T> - vektor s terminátorem
• string = basic_string< char> - řetězec (ASCII)
• wstring = basic_string< wchar_t> - řetězec (Unicode)
deque< T> - fronta s přidáváním a odebíráním z obou stran
[C++11] forward_list< T> - jednosměrně vázaný seznam
list< T> - obousměrně vázaný seznam
Odvozené
kontejnery
• queue< T> - fronta (maskovaná deque)
• priority_queue< T> - prioritní fronta (vylepšený vector)
• stack< T> - zásobník (maskovaný vector)
STL – Kontejnery
Pomocné metody kontejneru
Test
prázdnosti
bool empty() const
Počet
prvků
size_t size() const
nepoužívat pro testy prázdnosti
STL – Kontejnery
Metody kontejneru, vracející iterátory
Odkaz na začátek kontejneru - první platný prvek
iterator begin()
const_iterator begin() const
Odkaz za konec kontejneru
iterator end()
const_iterator end() const
- za poslední platný prvek
a const_iterator jsou typy definované uvnitř
kontejneru, zvané iterátory
iterator
přístupné konstrukcemi jako vector< int>::iterator
vlastnosti iterátorů jsou mírně závislé na druhu kontejneru
kontejneru obsahujícího typ T je třída s operátory
definovanými tak, aby se chovala podobně jako "T *"
"(ukazatel na typ T) resp. "const T *"
Iterátor
Vytváří se tak iluze, že kontejner je pole
Kategorie iterátorů
Norma
C++ definuje 5 kategorií
iterátorů
random_access
bidirectional
forward
output
input
Kategorie
určuje, které
syntaktické konstrukce musí
iterátor umožňovat
Pro
iterátor I jsou definovány tyto operace:
output
*I = x
input
*I /* pouze pro čtení */
random_access,
bidirectional, forward
*I /* čtení i zápis */
všechny
kategorie
++I, I++
random_access,
bidirectional, forward, input
I1 == I2, I1 != I2
vector,
basic_string a deque
random_access
list
bidirectional
forward_list
forward
random_access,
bidirectional
--I, I-random_access
I += n, I + n, n + I
I -= n, I - n, I1 - I2
I[ n]
I1 < I2, I1 > I2, I1 <= I2, I1 >= I2
STL – Iterátory
Operátory definované na iterátorech
přístup k prvku, na který iterátor ukazuje
T & iterator::operator *() const
const T & const_iterator::operator *() const
posouvání
iterátoru směrem ke konci
jednosměrný iterátor
iterator & iterator::operator++()
posouvání
iterátoru směrem k začátku
obousměrný iterátor
iterator & iterator::operator--()
libovolný
posun
iterátor s přímým přístupem
iterator operator+( iterator, int)
iterator operator-( iterator, int)
STL – Kontejnery
Metody kontejneru pro vkládání
iterator insert( iterator p, T x)
vsune (kopii) x před prvek, na který ukazuje iterátor p
• vrací iterátor ukazující na vložený prvek
void insert( iterator p, int n, T x)
vsune n kopií x před prvek, na který ukazuje iterátor p
template< typename other_iterator>
void insert( iterator p, other_iterator b, other_iterator e)
před prvek, na který ukazuje iterátor p, vsune kopii posloupnosti
prvků ohraničené iterátory b a e
• Tato posloupnost nesmí být uvnitř téhož kontejneru
• Tato posloupnost může být součástí jiného druhu kontejneru
je-li p == end(), vkládání připojuje na konec kontejneru
všechny dříve existující iterátory odkazující na tento kontejner jsou
po použití insert neplatné, včetně p
• výjimka: kontejnery list a forward_list iterátory mířící na původní
prvky nebo end() zachovávají
STL – Kontejnery
Konstruktory kontejneru
K()
Vytvoří prázdný kontejner (array: kontejner dané velikosti)
K( int n, T x = T())
Vytvoří kontejner s n kopiemi hodnoty x
• Má-li typ T konstruktor bez parametrů, není třeba udávat x
template< typename other_iterator>
K( other_iterator b, other_iterator e)
Vytvoří kontejner naplněný kopií posloupnosti prvků ohraničené
iterátory b a e
• Tato posloupnost může být součástí jiného druhu kontejneru
STL – Kontejnery
Původní vkládací metody kopírují vkládané prvky
• Zbytečná práce, nemožnost použití některých typů (unique_ptr)
[C++11] move
Metody insert a push_back/front mají move varianty
iterator insert( iterator p, T && x)
Překladač ji použije, je-li parametrem pomocná proměnná...
k.insert( it, a + b); // operator nebo funkce vracejici hodnotou
k.insert( it, T( x, y, z));
// bezejmenny objekt
... nebo pokud je použito std::move
k.insert( it, std::move( a));
• Move-semantika: Proměnná a bude (může být) vyprázdněna
Move-semantika poskytuje úsporu času (a prostoru), pokud typ T
• obsahuje dynamicky alokovaná data
• je na move semantiku připraven (má move-konstruktory)
std::vector< std::string> k;
std::string a = "...";
k.push_back( a + ".kzr");
STL – Kontejnery
Původní vkládací metody kopírují vkládané prvky
• Zbytečná práce, nemožnost použití některých typů (unique_ptr)
[C++11] emplace
Metody insert a push_back/front mají emplace varianty
iterator emplace( iterator p, T1 && x1, ..., Tn && xn);
void emplace_back( T1 && x1, ..., Tn && xn);
void emplace_front( T1 && x1, ..., Tn && xn);
Do kontejneru je přidán nový prvek inicializovaný konstruktorem s
parametry x1, ..., xn
std::vector< std::vector< int> > k;
k.emplace_back( 100, 0);
Šetří kopírování vkládaného prvku oproti původnímu zápisu
k.push_back( std::vector< int>( 100, 0));
Šetří i v případě nepřipraveného typu bez move-semantiky
• V případě vector< int> by to kopírování ušetřila sama move-semantika
• Poznámka: rvalue reference v hlavičce emplace funkcí dovolují i lvalue
operandy pomocí skládání referencí a funkce std::forward
STL – Kontejnery
Metody kontejneru pro odebírání
iterator erase( iterator p)
vyjme prvek, na který ukazuje iterátor p
• p nesmí být rovno end()
• vrací iterátor ukazující na prvek za vyjmutým prvkem (nebo end())
iterator erase( iterator p, iterator e)
vyjme všechny prvky mezi p a e, včetně p a vyjma e
• p nesmí být rovno end()
• vrací iterátor odkazující na prvek e (původní iterátor e nebude platný)
všechny iterátory odkazující na tento kontejner jsou po použití erase
neplatné, včetně p a e
• výjimka: kontejnery list a forward_list iterátory mířící na
nesmazané prvky zachovávají
void clear()
{ erase( begin(), end()); }
STL – Kontejnery
Odvozené funkce manipulace s konci kontejneru
Přidání jednotlivého prvku
void push_front( T x)
{ return insert( begin(), x); }
• list, deque
void push_back( T x)
{ return insert( end(), x); }
• list, deque, vector
Odebrání
jednotlivého prvku
void pop_front()
{ return erase( begin()); }
• list, deque
void pop_back()
{ return erase( --end()); }
• list, deque, vector
STL – Kontejnery
Odvozené funkce kontejneru pro přístup k prvkům
Prvky na koncích
• list, deque, vector
• podmínka: ! empty()
T & front()
const T & front() const
obě provádějí
{ return * begin(); }
T & back()
const T & back() const
obě provádějí
{ auto it = end(); --it; return * it; }
• [C++11] auto umožňuje deklaraci proměnné bez uvedení typu
• typ si odvodí překladač z inicializace, v tomto případě K::[const_]iterator
STL – Kontejnery
Odvozené funkce kontejneru pro přístup k prvkům
Prvky uprostřed
deque, vector, string
podmínka: n < size()
• at: porušení podmínky vyvolá výjimku
• operator[]: porušení podmínky způsobí nedefinované chování
T & at( int n)
T & operator[]( int n)
const T & at( int n) const
const T & operator[]( int n) const
všechny provádějí
return * ( begin() + n);
STL - Kontejnery
složitost
operace
na kontejneru
s n prvky
list
deque
vector
basic_string
přídání /
odebrání
jednoho prvku
na začátku
push_front
pop_front
konstantní
konstantní
funkce
neexistuje
funkce
neexistuje
přídání /
odebrání
jednoho prvku
na i-té pozici
insert
erase
konstantní
min( i, n - i)
n-i
n-i
přídání /
odebrání m
prvků na i-té
pozici
insert
erase
m
přesuny mezi
seznamy
(splice) jsou
konstantní
m +min( i, n - i)
m+n-i
m+n-i
přídání /
odebrání
jednoho prvku
na konci
push_back
pop_back
konstantní
konstantní
konstantní
funkce
neexistuje
nalezení i-tého
prvku
begin() + i
funkce
neexistuje
konstantní
konstantní
konstantní
paměťová
náročnost
kontejneru s
prvky velikosti
s
(s + K) * n
K řádově 16 B
q*s*n
q kolem 1.2
q*s*n
q kolem 1.2
q*s*n
q kolem 1.2
Asociativní kontejnery
STL - Kontejnery
Asociativní kontejnery
Uspořádané
(samovyvažující se stromy)
set<T> - množina
multiset<T> - množina s opakováním
map<K,T> - asociativní pole, tj. parciální zobrazení K -> T
multimap<K,T> - relace s rychlým vyhledáváním podle klíče K
[C++11]
Hashované
[C++11] unordered_set<T> - množina
[C++11] unordered_multiset<T> - množina s opakováním
[C++11] unordered_map<K,T> - asociativní pole, tj. parciální
zobrazení K -> T
[C++11] unordered_multimap<K,T> - relace s rychlým vyhledáváním
podle klíče K
pair<A,B> - pomocná šablona uspořádané dvojice
• s položkami first, second
STL - Kontejnery
Uspořádané
kontejnery vyžadují uspořádání na klíčích
Klíčem se rozumí první parametr šablony kontejneru
Uspořádání se obvykle definuje operátorem < definovaným na typu
klíče
• Pozor na konstrukce typu set< char *>
Uspořádání lze rovněž zadat přídavným parametrem šablony
Definované
uspořádání nemusí být antisymetrická relace
pokud platí
! (x < y) && ! (y < x)
pak jsou prvky x a y považovány za ekvivalentní
STL - Kontejnery
Uspořádané
kontejnery vyžadují uspořádání na klíčích
• Vystačí si s operací <
V nejjednodušším případě to funguje samo
std::map< std::string, int> mapa;
Pokud typ uspořádání nemá, lze jej definovat obecně
bool operator<( const Klic & a, const Klic & b) { return ...; }
std::map< Klic, int> mapa;
Pokud obecná definice uspořádání nevyhovuje, lze definovat
uspořádání funktorem pouze pro daný typ kontejneru
struct Usporadani {
bool operator()( const Klic & a, const Klic & b) const { return ...; }
};
std::map< Klic, int, Usporadani> mapa;
Pokud různé instance kontejneru mají mít různé uspořádání, lze do
funktoru doplnit datové položky
struct Usporadani { Usporadani( bool a); /*...*/ bool ascending; };
std::map< Klic, int, Usporadani> mapa( Usporadani( true));
STL - Kontejnery
Uspořádání
na klíčích – implementace
• Knihovny definují funktor std::less< K>
template< typename K>
class less { public:
bool operator()( const K & a, const K & b) const { return a < b; }
};
• Šablona kontejneru má typový parametr - typ funktoru
template< typename K, typename T, typename L = std::less< K> >
class map { public:
•
Konstruktor kontejneru dostává hodnotu funktoru
explicit map( const L & c = L())
: cmp_( c)
{ /*...*/ }
/*...*/
private:
•
Kontejner drží jednu instanci funktoru
•
Metody kontejneru volají operátor () na instanci funktoru
L cmp_;
iterator find_( /*...*/) { /*...*/ if ( cmp_( x, y) ) /*...*/ }
};
STL - Kontejnery
Uspořádání
na klíčích – hashující kontejnery
Kontejner vyžaduje funktory pro hashování a pro porovnání
template< typename K>
class hash { public:
std::size_t operator()( const K & a) const { /*...*/ }
};
template< typename K>
class equal_to { public:
bool operator()( const K & a, const K & b) const { return a == b; }
};
• Šablona kontejneru má dva další parametry
template< typename K, typename T,
typename H = std::hash< K>, typename E = std::equal_to< K> >
class unordered_map;
•
Konstruktor kontejneru dostává hodnoty funktorů
explicit unordered_map( std::size_t initial_bucket_count = /*...*/,
const H & h = L(), const E & e = E());
STL - Kontejnery
Asociativní kontejnery – procházení
Kontejnery lze procházet iterátory
Uspořádané kontejnery jsou prezentovány v uspořádání podle klíčů
• Iterátor je bidirectional
Hashující kontejnery jsou prezentovány v implementačnědefinovaném pořadí
• Iterátor je forward
Metody begin() a end() a operátory iterátorů mají stejný význam,
jako u sekvenčních kontejnerů
Kontejnery
[unordered_][multi]map< K, T> obsahují
uspořádané dvojice - typ std::pair< const K, T>
Klíč (it->first) nelze modifikovat, hodnotu (it->second) ano
Procházení
celého asociativního kontejneru se užívá
málokdy
Iterátory se častěji získávají vyhledáváním
STL - Kontejnery
Asociativní kontejnery – vyhledávání
iterator
iterator
iterator
iterator
iterator
iterator
iterator
iterator
set::find( T x)
multiset::find( T x)
map::find( K x)
multimap::find( K x)
unordered_set::find( T x)
unordered_multiset::find( T x)
unordered_map::find( K x)
unordered_multimap::find( K x)
pokud v kontejneru existuje prvek s klíčem ekvivalentním x:
• vrací iterátor ukazující na první takový prvek
• multiset, multimap: další takové prvky jsou dostupné operací ++
jinak vrací end()
složitost
operace
uspořadné kontejnery: O( log( size()))
hashující kontejnery: průměrně O(1), nejhůře O( size())
STL - Kontejnery
Uspořádané kontejnery - intervalové dotazy
iterator
iterator
iterator
iterator
set::lower_bound( T x)
multiset::lower_bound( T x)
map::lower_bound( K x)
multimap::lower_bound( K x)
vrací první prvek jehož klíč není menší než x, případně end()
iterator
iterator
iterator
iterator
set::upper_bound( T x)
multiset::upper_bound( T x)
map::upper_bound( K x)
multimap::upper_bound( K x)
vrací první prvek jehož klíč je větší než x, případně end()
dvojici
takto získaných iterátorů lze využít v jiných funkcích
téhož i jiného kontejneru
operace mají logaritmickou složitost
STL - Kontejnery
Asociativní kontejnery – vyhledávání s opakováním
pair<iterator,iterator>
pair<iterator,iterator>
pair<iterator,iterator>
pair<iterator,iterator>
multiset::equal_range( T x)
multimap::equal_range( K x)
unordered_multiset::equal_range( T x)
unordered_multimap::equal_range( K x)
vrací polouzavřený interval [first,second) obsahující prvky s daným
klíčem
• pokud prvky neexistují, vrací prázdný interval na místě, kde by byly
• odpovídá std::make_pair( lower_bound( x), upper_bound( x))
existuje i pro kontejnery bez opakování
pair<iterator,iterator>
pair<iterator,iterator>
pair<iterator,iterator>
pair<iterator,iterator>
složitost
set::equal_range( T x)
map::equal_range( K x)
unordered_set::equal_range( T x)
unordered_map::equal_range( K x)
operace
uspořadné kontejnery: O( log( size()))
hashující kontejnery: průměrně O(1), nejhůře O( size())
STL - Kontejnery
Asociativní kontejnery - vkládání
set a map
pair< iterator, bool> set::insert( T x)
pair< iterator, bool> unordered_set::insert( T x)
pair< iterator, bool> map::insert( pair< const K, T> x)
pair< iterator, bool> unordered_map::insert( pair< const K, T> x)
pokud
prvek ekvivalentní x (resp. x.first) v kontejneru není:
kopie x se vloží do kontejneru
vrací pair< iterator, bool>( p, true) kde p je iterátor ukazující na
vložený prvek
pokud
prvek ekvivalentní x (resp. x.first) v kontejneru již je:
vrací pair< iterator, bool>( p, false) kde p je iterátor ukazující na
existující prvek ekvivalentní x
[C++11]
existuje též move-verze insert a emplace
složitost operace
uspořadné kontejnery: O( log( size()))
hashující kontejnery: průměrně O(1), nejhůře O( size())
STL - Kontejnery
Asociativní kontejnery - vkládání
multiset a multimap
iterator
iterator
iterator
iterator
multiset::insert( T x)
unordered_multiset::insert( T x)
multimap::insert( pair< const K, T> x)
unordered_multimap::insert( pair< const K, T> x)
kopie x se vloží do kontejneru
vrací iterátor ukazující na vložený prvek
[C++11]
existuje též move-verze insert a emplace
složitost operace
uspořadné kontejnery: O( log( size()))
hashující kontejnery: průměrně O(1), nejhůře O( size())
STL - Kontejnery
Asociativní kontejnery - odebírání
podle klíče
size_type [unordered_][multi]set::erase( T x)
size_type [unordered_][multi]map::erase( K x)
odebere všechny prvky s klíčem ekvivalentním zadanému x
vrací počet odebraných prvků
složitost
operace pro N odebraných prvků
uspořadné kontejnery: O( log( size()) + N)
hashující kontejnery: průměrně O(N), nejhůře O( size())
po
operaci budou iterátory na odebrané prvky neplatné
asociativní kontejnery neinvalidují iterátory při insert/erase
vyjma odebraných prvků
STL - Kontejnery
Asociativní kontejnery - odebírání
podle iterátoru - jeden prvek
void erase( iterator p)
odebere prvek na který odkazuje iterátor p
• p nesmí být rovno end()
operace má konstantní složitost
• rozumí se amortizovaná/průměrná složitost
podle iterátorů - interval
void erase( iterator p, iterator e)
odebere všechny prvky mezi p a e, včetně p a vyjma e
složitost operace pro N odebraných prvků
• uspořadné kontejnery: O( log( size()) + N)
• hashující kontejnery: průměrně O(N), nejhůře O( size())
po
operaci budou iterátory na odebrané prvky neplatné
STL - Kontejnery
map/unordered_map – at/operator [ ]
T & [unordered_]map::at( K x)
Vrátí referenci na hodnotovou (second) složku prvku s klíčem
ekvivalentním x
Pokud takový prvek neexistuje, vyvolá výjimku
T & [unordered_]map::operator[]( K x)
{ return (*((insert(make_pair( x, T()))).first)).second; }
Vrátí referenci na hodnotovou (second) složku prvku s klíčem
ekvivalentním x
Pokud takový prvek neexistuje, vytvoří jej
• Jeho hodnotová složka bude T()
Tento operátor je možno používat pro vkládání a přepisování prvků
kontejneru
• Kontejner [unordered_]map se chová jako asociativní pole (PHP)
• Pozor: U sekvenčních kontejnerů automatické přidání nefunguje
Pozor: Pro čtení nemusí být vhodné – přidává neexistující prvky
Algoritmy
STL – Algoritmy
Šablona funkce for_each
<algorithm>
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function f);
first
a last jsou iterátory, určující procházený úsek nějakého
kontejneru (všetně first, mimo last)
f je buďto
globální funkce (ukazatel na funkci), nebo
funktor, tj. třída obsahující operator()
f (případně metoda operator()) je zavolána na
každý prvek v zadaném intervalu
Funkce
prvek se předává jako * iterator, což může být odkazem
funkce f tedy může modifikovat prvky seznamu
STL – Algoritmy
Šablona funkce for_each
<algorithm>
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function f)
{
for (; first != last; ++first)
f( * first);
return f;
}
Takto napsanou šablonu lze zkompilovat pro jakékoliv f, na které lze
aplikovat operátor (), tedy jak pro funkci, tak pro funktor
STL – Algoritmy
Použití funkce for_each
void my_function( double & x)
{
x += 1;
}
void increment( list< double> & c)
{
for_each( c.begin(), c.end(), my_function);
}
[C++11] Lambda
Nová syntaktická konstrukce generující funktor
void increment( list< double> & c)
{
for_each( c.begin(), c.end(), []( double & x){ x += 1;});
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double v;
void operator()( double & x) const { x += v; }
my_functor( double p) : v( p) {}
};
void add( list< double> & c, double value)
{
for_each( c.begin(), c.end(), my_functor( value));
}
[C++11] Lambda
void add( list< double> & c, double value)
{
for_each( c.begin(), c.end(), [=]( double & x){ x += value;});
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x) { s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
my_functor f;
f = for_each( c.begin(), c.end(), f);
return f.s;
}
[C++11] Lambda
double sum( const list< double> & c)
{
double s = 0.0;
for_each( c.begin(), c.end(), [&]( const double & x){ s += x;});
return s;
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x)
{ s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
my_functor f;
for_each( c.begin(), c.end(), f);
return f.s;
}
Pozor: f se předává hodnotou - tato implementace vždy vrací 0.0
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x)
{ s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
return for_each( c.begin(), c.end(), my_functor()).s;
}
Lambda
Lambda výrazy
Motivace
class ftor {
public:
ftor(int a, int b) : a_(a),b_(b) { }
bool operator()(int x) const { return x*a_<b_; }
private:
int a_, b_;
};
typedef std::vector<int> v_t; v_t v;
v_t::iterator vi=remove_if(v.begin(), v.end(), ftor(m, n));
Řešení
std::vector<int> v;
auto vi=remove_if(v.begin(), v.end(),
[=](int x){ return x*m<n; });
C++11
Lambda výrazy
Lambda výraz
[ capture ]( params ) mutable -> rettype { body }
Deklaruje
třídu ve tvaru
class ftor {
public:
ftor( TList ... plist) : vlist( plist) ... { }
rettype operator()( params ) const { body }
private:
TList ... vlist;
};
vlist je určen proměnnými použitými v body
TList je určen jejich typy a upraven podle capture
operator() je const pokud není uvedeno mutable
Lambda výraz
ftor( vlist ...)
je nahrazen vytvořením objektu
C++11
Lambda výrazy – návratový typ a typ funkce
Návratový typ operátoru
Explicitně definovaný návratový typ
[]() -> int { … }
Automaticky určen pro tělo lambda funkce ve tvaru
[]() { return V; }
Jinak void
C++11
Lambda výrazy – capture
Capture
[ capture ]( params ) mutable -> rettype { body }
C++11
Způsob zpřístupnění vnějších entit
Určuje typy datových položek a konstruktoru funktoru
Explicitní
capture
Programátor vyjmenuje všechny vnější entity v capture
[a,&b,c,&d]
• entity označené & předány odkazem, ostatní hodnotou
Implicitní
capture
Překladač sám určí vnější entity, capture určuje způsob předání
[=]
[=,&b,&d]
• předání hodnotou, vyjmenované výjimky odkazem
[&]
[&,a,c]
• předání odkazem, vyjmenované výjimky hodnotou
Lambda výrazy – příklad
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable {
auto m2 = [a, b, &c]() mutable {
std::cout << a << b << c;
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2();
};
a = 2; b = 2; c = 2;
m1();
std::cout << a << b << c;
Co to vypíše?
123234
C++11
Další algoritmy
STL – Algoritmy
Šablona funkce find
<algorithm>
template<class InputIterator, class T>
InputIterator find(
InputIterator first,
InputIterator last,
const T & value)
{
for (; first != last; ++first)
if ( * first == value )
break;
return first;
}
STL – Algoritmy
Šablona funkce find_if
<algorithm>
template<class InputIterator, class Predicate>
InputIterator find_if(
InputIterator first,
InputIterator last,
Predicate pred)
{
for (; first != last; ++first)
if ( pred( * first) )
break;
return first;
}
Predikát pred může být funkce nebo funktor
STL – Algoritmy
Přehled algoritmů
Průchod kontejnerem
for_each
Čtení
kontejnerů
find, find_if - první prvek s danou vlastností
find_end - poslední výskyt druhé sekvence v první
find_first_of - první výskyt některého prvku druhé sekvence v první
adjacent_find - první prvek ekvivalentní sousednímu
count, count_if - počet prvků s danou vlastností
mismatch - první odlišnost dvou sekvencí
equal - test shody dvou sekvencí
search - první výskyt druhé sekvence v první
search_n - první n-násobný výskyt dané hodnoty
STL – Algoritmy
Přehled algoritmů
Swap
swap - výměna obsahu dvou objektů
• Pomocí parciální/explicitní specializace bývá implementována rychleji,
než kopírování
Modifikace
kontejnerů výměnou prvků
swap_ranges - výměna obsahu dvou sekvencí (volá swap)
iter_swap - výměna obsahu dvou jednoprvkových sekvencí
Modifikace
kontejnerů permutací (voláním swap)
partition, stable_partition - přemístění prvků s danou vlastností
dopředu
random_shuffle - náhodná permutace dle zadaného náhodného
generátoru
reverse - otočení posloupnosti
rotate, rotate_copy - rotace prvků
STL – Algoritmy
Přehled algoritmů
Modifikace kontejnerů přiřazením
copy, copy_backward - kopie první sekvence do druhé
transform - aplikace zadané unární/binární operace na každý prvek
první/první a druhé sekvence a zapsání výsledku do druhé/třetí
sekvence
replace, replace_if - nahrazení prvků s danou vlastností jinou
hodnotou
replace_copy, replace_copy_if - kopie s nahrazením
fill, fill_n - naplnění sekvence danou hodnotou
generate, generate_n - naplnění sekvence z daného generátoru
Modifikace
kontejnerů odebráním
remove, remove_if - smazání prvků s danou vlastností
unique, unique_copy - smazání opakujících se sousedních prvků
• vhodné pro setříděné nebo asociativní kontejnery
Pozor:
Tyto funkce neprodlužují ani nezkracují kontejner
STL – Algoritmy
Přehled algoritmů
Pozor: Algoritmy neprodlužují ani nezkracují kontejner
vector< int> a, b;
a.push_back( 1); a.push_back( 2); a.push_back( 3);
copy( a.begin(), a.end(), b.begin());
Pro
// ilegální použití
tyto účely existují "vkládající iterátory"
<iterator> obsahuje tyto funkce vracející iterátory
• back_inserter( K) - iterátor vkládající na konec kontejneru K
• front_inserter( K) - iterátor vkládající na začátek kontejneru K
• inserter( K, I) - iterátor vkládající před iterátor I do kontejneru K
tyto iterátory jsou pouze výstupní
• lze je použít jako cíl ve funkcích typu copy
copy( a.begin(), a.end(), back_inserter( b));
STL – Algoritmy
Přehled algoritmů
min, max - minimum a maximum ze dvou hodnot
Třídění
a spol.
sort, stable_sort - třídění
partial_sort, partial_sort_copy, nth_element - polotovary třídění
push_heap, pop_heap, make_heap, sort_heap - operace na haldě
min_element, max_element
lexicographical_compare
next_permutation, prev_permutation
Operace
na setříděných kontejnerech
lower_bound, upper_bound, equal_range - hledání prvku
binary_search - test na přítomnost prvku
includes - test podmnožinovosti
merge, inplace_merge - sjednocení s opakováním
set_union, set_intersection - sjednocení, průnik
set_difference, set_symmetric_difference - množinový rozdíl
Konstruktory a destruktory
Constructors and Destructors
Konstruktory a destruktory
Konstruktor třídy XXX je metoda se jménem XXX
Typ návratové hodnoty se neurčuje
Konstruktorů může být více, liší se parametry
Nesmí být virtuální
Konstruktor
je volán vždy, když vzniká objekt typu XXX
Parametry se zadávají při vzniku objektu
Některé z konstruktorů mají speciální význam
Některé z konstruktorů může generovat sám kompilátor
Konstruktor
nelze vyvolat přímo
Destruktor třídy je metoda se jménem ~XXX
Nesmí mít parametry ani návratovou hodnotu
Může být virtuální
Destruktor
je volán vždy, když zaniká objekt typu XXX
Destruktor může generovat sám kompilátor
Destruktor
lze vyvolat přímo pouze speciální syntaxí
Speciální metody tříd
Konstruktor bez parametrů (default constructor)
XXX();
Používán u proměnných bez inicializace
Kompilátor se jej pokusí vygenerovat, je-li to třeba a pokud třída nemá
vůbec žádný konstruktor:
• Položky, které nejsou třídami, nejsou generovaným konstruktorem
inicializovány
• Generovaný konstruktor volá konstruktor bez parametrů na všechny
předky a položky
• To nemusí jít např. pro neexistenci takového konstruktoru
Destruktor
~XXX();
Používán při zániku objektu
Kompilátor se jej pokusí vygenerovat, je-li to třeba a třída jej nemá
• To nemusí jít kvůli ochraně přístupu
Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na
předka, musí být destruktor v tomto předku deklarován jako virtuální
virtual ~XXX();
copy/move
Speciální metody tříd – C++11
copy/move
Speciální metody tříd
Copy constructor
T( const T & x);
Move constructor
T( T && x);
Copy assignment operator
T & operator=( const T & x);
Move assignment
T & operator=( T && x);
operator
C++11
copy/move
Překladačem definované chování (default)
Copy constructor
T( const T & x) = default;
aplikuje copy constructor na složky
Move constructor
T( T && x) = default;
aplikuje move constructor na složky
Copy assignment operator
T & operator=( const T & x) = default;
aplikuje copy assignment operator na složky
Move assignment operator
T & operator=( T && x) = default;
aplikuje move assignment operator na složky
default
umožňuje vynutit defaultní chování
C++11
copy/move
Podmínky automatického defaultu
Copy
C++11
constructor/assignment operator
pokud není explicitně deklarován move constructor ani assignment
operator
• budoucí normy pravděpodobně zakážou automatický default i v
případě přítomnosti druhé copy metody nebo destruktoru
Move
constructor/assignment operator
pokud není deklarována žádná ze 4 copy/move metod ani destruktor
copy/move
Nejběžnější kombinace
Neškodná třída
Nedeklaruje žádnou copy/move metodu ani destruktor
Neobsahuje složky vyžadující zvláštní péči (ukazatele)
Složky
vyžadující zvláštní péči
Překladačem generované chování (default) nevyhovuje
Bez podpory move (před C++11)
T( const T & x);
T & operator=( const T & x);
~T();
Plná podpora copy/move
T( const T & x);
T( T && x);
T & operator=( const T & x);
T & operator=( T && x);
~T();
C++11
copy/move
Další kombinace
Nekopírovatelná třída
C++11
Např. dynamicky alokované živé objekty v simulacích
T( const T & x) = delete;
T & operator=( const T & x) = delete;
delete zakazuje generování překladačem
Destruktor může ale nemusí být nutný
Přesouvatelná
nekopírovatelná třída
Např. unikátní vlastník jiného objektu (viz std::unique_ptr< U>)
T( T && x);
T & operator=( T && x);
~T();
Pravidla jazyka zakazují generování copy metod překladačem
Destruktor typicky bývá nutný
Konverze
Speciální metody tříd
Konverzní konstruktory
class XXX {
XXX( YYY);
};
Zobecnění kopírovacího konstruktoru
Definuje uživatelskou konverzi typu YYY na XXX
Je-li tento speciální efekt nežádoucí, lze jej zrušit:
explicit XXX( YYY);
Konverzní operátory
class XXX {
operator YYY() const;
};
Definuje uživatelskou konverzi typu XXX na YYY
Vrací typ YYY hodnotou (tedy s použitím kopírovacího konstruktoru
YYY, pokud je YYY třída)
Kompilátor vždy použije nejvýše jednu uživatelskou konverzi
Konstruktory a spol.
Typické situace
Konstruktory a spol.
POD: Plain-Old-Data
Položky jsou veřejné
Inicializace je v zodpovědnosti uživatele
class T {
public:
std::string x_;
};
Často se používá struct
struct T {
std::string x_;
};
Konstruktory a spol.
Všechny položky jsou neškodné
Položky mají svoje konstruktory
Třída nemusí mít žádný konstruktor
class T {
public:
// ...
const std::string & get_x() const { return x_; }
void set_x( const std::string & s) { x_ = s; }
private:
std::string x_;
};
Konstruktory a spol.
Všechny položky jsou neškodné
Položky mají svoje konstruktory
Konstruktor se hodí pro pohodlnou inicializaci
V takovém případě je (většinou) nutný i konstruktor bez parametrů
Konstruktory s jedním parametrem označeny explicit
class T {
public:
T() {}
explicit T( const std::string & s) : x_( s) {}
T( const std::string & s, const std::string & t)
: x_( s), y_( t)
{}
// ... metody ...
private:
std::string x_, y_;
};
Konstruktory a spol.
Některé položky jsou mírně nebezpečné
Některé položky nemají vhodné konstruktory
Číselné typy včetně bool, char
Konstruktor
je nutný pro inicializaci
V takovém případě je (většinou) nutný i konstruktor bez parametrů
Konstruktory s jedním parametrem označeny explicit
class T {
public:
T() : x_( 0), y_( 0) {}
explicit T( int s) : x_( s), y_( 0) {}
T( int s, int t)
: x_( s), y_( t)
{}
// ... metody ...
private:
int x_, y_;
};
Konstruktory a spol.
Některé položky jsou hodně nebezpečné
Některé položky nemají použitelnou semantiku kopírování
Ukazatele (E *) na dynamicky alokovaná data
Semantika definovaná překladačem nevyhovuje
Je
nutný copy constructor, operator= a destruktor
Je nutný i jiný konstruktor, např. bez parametrů
class T {
public:
T() : p_( new Data) {}
T( const T & x) : p_( new Data( * x.p_)) {}
T & operator=( const T & x) { T tmp( x); swap( tmp); return * this;}
~T() { delete p_; }
void swap( T & y) { std::swap( p_, y.p_); }
private:
Data * p_;
};
Konstruktory a spol.
Některé položky jsou hodně nebezpečné
Je nutný copy/move constructor/operator= a destruktor
Je nutný i jiný konstruktor, např. bez parametrů
C++11
class T {
public:
T() : p_( new Data) {}
T( const T & x) : p_( new Data( * x.p_)) {}
T( T && x) : p_( x.p_) { x.p_ = 0; }
T & operator=( const T & x) { T tmp( x); swap( tmp); return * this;}
T & operator=( T && x)
{ T tmp( std::move( x)); swap( tmp); return * this;}
~T() { delete p_; }
void swap( T & y) { std::swap( p_, y.p_); }
private:
Data * p_;
};
Konstruktory a spol.
Některé položky jsou hodně nebezpečné
Třída se zakázaným kopírováním
Ale schopná přesunu
class T {
public:
T() : p_( new Data) {}
T( const T & x) = delete;
T( T && x) : p_( x.p_) { x.p_ = 0; }
T & operator=( const T & x) = delete;
T & operator=( T && x)
{ T tmp( std::move( x)); swap( tmp); return * this;}
~T() { delete p_; }
void swap( T & y) { std::swap( p_, y.p_); }
private:
Data * p_;
};
C++11
Konstruktory a spol.
Použití unique_ptr
Třída se zakázaným kopírováním
Ale schopná přesunu
class T {
public:
T() : p_( new Data) {}
private:
std::unique_ptr< Data> p_;
};
C++11
Konstruktory a spol.
Použití unique_ptr
Třída s povoleným kopírováním
C++11
class T {
public:
T() : p_( new Data) {}
T( const T & x) : p_( new Data( * x.p_)) {}
T( T && x) = default;
T & operator=( const T & x) { T tmp( x); swap( tmp); return * this;}
T & operator=( T && x) = default;
void swap( T & y) { std::swap( p_, y.p_); }
private:
std::unique_ptr< Data> p_;
};
Konstruktory a spol.
Abstraktní třída
Se zákazem kopírování/přesouvání
class T {
protected:
T() {}
T( const T & x) = delete;
T & operator=( const T & x) = delete;
public:
virtual ~T() {}
};
C++11
Konstruktory a spol.
Abstraktní třída
Se podporou klonování
class T {
protected:
T() {}
T( const T & x) = default;
T & operator=( const T & x) = delete;
public:
virtual ~T() {}
virtual T * clone() const = 0;
};
C++11
Vznik a zánik objektů
Vznik a zánik objektů
Lokální proměnné
Pro každý lokální objekt je při jeho vzniku, to jest při průchodu řízení
jeho deklarací, vyvolán specifikovaný konstruktor.
Při zániku lokálního objektu, to jest při opuštění bloku s jeho
deklarací (jakýmkoli způsobem včetně příkazů return, break,
continue a goto nebo následkem výjimky) je vyvolán jeho destruktor.
Deklarace lokálního objektu může být kdekoliv uvnitř těla funkce
nebo kteréhokoliv složeného příkazu. Rozsah platnosti objektu je od
místa deklarace po konec nejbližšího složeného příkazu.
Skoky, které by vstupovaly do bloku a obcházely přitom deklaraci
objektu, jsou zakázány.
void f()
{
XXX a, b;
XXX c( 1), d( a);
XXX e = 1, f = a;
XXX g( 1, 2, 3);
XXX h{ 1, 2, 3};
}
//
//
//
//
//
konstruktor bez parametrů
konstruktory XXX( int), XXX( XXX)
(skoro) ekvivalentní zápis
konstruktor XXX( int, int, int)
nová jednotná syntaxe C++11
Vznik a zánik objektů
Parametry předávané hodnotou
Předání parametru je realizováno voláním kopírovacího konstruktoru
Tento konstruktor je volán na místě volání funkce před jejím zavoláním
Kompilátor dokáže tento konstruktor vytvořit vlastními prostředky
Destruktor objektu je vyvolán před návratem z funkce
Pokud je skutečný parametr jiného typu, před voláním kopírovacího
konstruktoru dojde ke konverzi
• Tato konverze může zahrnovat vznik pomocného objektu a tedy volání dalšího
konstruktoru (a destruktoru)
void f( XXX a)
{
}
void g()
{ XXX b;
f( b); // konstruktor XXX( const XXX &)
f( 1); // konstruktor XXX( int) – po něm se volá XXX( const XXX &)
}
Vznik a zánik objektů
Globální proměnné
Pro každý globální objekt (a statickou položku třídy) je vyvolán
konstruktor (pokud je neprázdný) před vstupem řízení do funkce
main
Po jejím opuštění (nebo po zavolání funkce exit) je pro každý
globální objekt vyvolán destruktor.
V rámci jednoho překládaného modulu jsou globální proměnné
inicializovány v pořadí zápisu a destruovány v opačném pořadí.
Pořadí inicializací mezi moduly není definováno
XXX a( 1), b, c( 2, 3, 4);
XXX d( b); // b již je inicializováno
class C {
static XXX h;
// statická datová položka - deklarace
};
XXX C::h( 1, 2, 3); // statická datová položka - definice
Vznik a zánik objektů
Lokální statické proměnné
Konstruktory lokálních statických proměnných se volají v okamžiku
prvního vstupu do funkce
Destruktory jsou volány po výstupu z main v opačném pořadí
f()
{ static XXX e( 1);
}
// lokální statická proměnná
Typické použití - singleton
std::ostream & log_file()
{
static std::ofstream x( "file.log");
return x;
}
// lokální statická proměnná
Vznik a zánik objektů
Dynamicky alokované objekty
Pro dynamickou alokaci slouží dvojice operátorů new a delete.
Operátor new alokuje potřebnou oblast paměti pro objekt
specifikovaného typu a vyvolává konstruktor podle zadaných
parametrů. Vrací ukazatel na tento objekt.
Pokud se z důvodu nedostatku paměti alokace nezdaří:
• Vyvolá se výjimka std::bad_alloc
Operátor delete vyvolává destruktor objektu a poté dealokuje paměť
zabranou objektem.
• Je odolný proti nulovým ukazatelům
XXX * p, * q;
p = new XXX;
// konstruktor bez parametrů
q = new XXX( 1, p); // XXX( int, XXX *)
/* ... */
delete p;
delete q;
Vznik a zánik objektů
Dynamická
alokace polí
Vyvolává pouze konstruktory bez parametrů
int n;
XXX * p;
p = new XXX[ n];
// pole n objektů typu XXX – konstruktory XXX()
XXX * q;
q = new XXX *[ n]; // pole n ukazatelů na XXX – žádné konstruktory
/* ... */
delete[] p;
delete[] q;
Vznik a zánik objektů
Dočasné objekty
Užití jména třídy jako jména funkce v operátoru volání
způsobí:
Vyhrazení místa pro tuto třídu na zásobníku, tedy mezi okolními
lokálními proměnnými
Vyvolání konstruktoru s patřičnými parametry na tomto objektu
Použití tohoto objektu jako hodnoty v okolním výraze
Vyvolání destruktoru nejpozději na konci příkazu
XXX a, b;
a = XXX();
b = XXX( 1, 2);
// konstruktor bez parametrů + kopie + destruktor
// konstruktor s parametry + kopie + destruktor
Speciální
případ této syntaxe je chápán jako typová
konverze (function-style cast)
Konstruktor s jedním parametrem je nazýván konverzní konstruktor
Dědičnost a virtuální funkce
Dědičnost
class Base { /* ... */ };
class Derived : public Base { /* ... */ }
Třída
Derived je odvozena z třídy Base
Obsahuje všechny datové položky i metody třídy Base
Může k nim doplnit další
•Není vhodné novými zakrývat staré, vyjma virtuálních
Může změnit chování metod, které jsou v Base deklarovány jako
virtuální
class Base {
virtual void f() { /* ... */ }
};
class Derived : public Base {
virtual void f() { /* ... */ }
};
Virtuální funkce
class Base {
virtual void f() { /* ... */ }
virtual void f( int) { /* ... */ }
virtual void g() = 0; // čistě virtuální funkce bez těla
virtual void h() const { /* ... */ }
void j() { /* ... */ }
};
Třída obsahující čistě virtuální funkci nemůže být samostatně
instanciována.
class Derived : public Base {
virtual void f() { /* ... */ }
virtual void f( int) const { /* ... */ }
virtual void g() { /* ... */ }
virtual void h() { /* toto není nové tělo pro Base::h */ }
virtual void j() { /* toto není nové tělo pro Base::j */ }
};
Mechanismus redefinování virtuálních funkcí je vázán na jméno i
parametry včetně „const“
Je možné redefinovat i tělo privátní funkce
Virtuální funkce
class Base {
virtual void f() { /* ... */ }
};
class Derived : public Base {
virtual void f() { /* ... */ }
};
Mechanismus
virtuálních funkcí se uplatní
pouze v přítomnosti ukazatelů nebo referencí
Base * p = new Derived;
p->f();
// volá Derived::f
V jiné situaci není virtuálnost funkcí užitečná
Derived d;
d.f();
// volá Derived::f i kdyby nebyla virtuální
Base b = d; // slicing = kopie části objektu
b.f();
// volá Base::f ikdyž je virtuální
Slicing
je specifikum jazyka C++
Názvosloví
Abstraktní třída
Definice v C++: Třída obsahující alespoň jednu čistě
virtuální funkci
Běžná definice: Třída, která sama nebude instanciována
Představuje rozhraní, které mají z ní odvozené třídy
(potomci) implementovat
Konkrétní třída
Třída, určená k samostatné instanciaci
Implementuje rozhraní, předepsané abstraktní třídou, ze
které je odvozena
Dědičnost a destruktor
class Base {
public:
virtual ~Base() {}
};
Base * p = new Derived;
delete p;
Pokud
class Derived : public Base {
public:
virtual ~Derived() { /* ... */ }
};
je objekt destruován
operátorem delete aplikovaným
na ukazatel na předka, musí
být destruktor v tomto předku
deklarován jako virtuální
Odvozené pravidlo:
Každá abstraktní třída má mít
virtuální destruktor
Je to zadarmo
Může se to hodit
Dědičnost
Mechanismus
dědičnosti v C++ je velmi silný
Bývá používán i pro nevhodné účely
Ideální
použití dědičnosti je pouze toto
ISA hierarchie (typicky pro objekty s vlastní identitou)
•Živočich-Obratlovec-Savec-Pes-Jezevčík
•Objekt-Viditelný-Editovatelný-Polygon-Čtverec
Vztah interface-implementace
•Readable-InputFile
•Writable-OutputFile
•(Readable+Writable)-IOFile
Jiná použití dědičnosti obvykle signalizují chybu v návrhu
•Výjimky samozřejmě existují (traits...)
Dědičnost
ISA hierarchie
•C++: Jednoduchá nevirtuální veřejná dědičnost
class Derived : public Base
•Abstraktní třídy někdy obsahují datové položky
Vztah interface-implementace
•C++: Násobná virtuální veřejná dědičnost
class Derived : virtual public Base1,
virtual public Base2
•Abstraktní třídy obvykle neobsahují datové položky
•Interface nebývají využívány k destrukci objektu
Oba přístupy se často kombinují
class Derived : public Base,
virtual public Interface1,
virtual public Interface2
Nesprávné užití dědičnosti
Nesprávné
užití dědičnosti č. 1
class Real { public: double Re; };
class Complex : public Real { public: double Im; };
Porušuje pravidlo "každý potomek má všechny vlastnosti předka"
• např. pro vlastnost "má nulovou imaginární složku"
Důsledek - slicing:
double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; }
Complex x;
double a = abs( x);
// tento kód LZE přeložit, a to je špatně
Důvod: Referenci na potomka lze přiřadit do reference na předka
• Complex => Complex & => Real & => const Real &
Nesprávné užití dědičnosti
Nesprávné
užití dědičnosti č. 1
class Real { public: double Re; };
class Complex : public Real { public: double Im; };
Slicing nastává i u předávání hodnotou
double abs( Real p) { return p.Re > 0 ? p.Re : - p.Re; }
Complex x;
double a = abs( x);
// tento kód LZE přeložit, a to je špatně
Důvod: Předání hodnoty x do parametru p je provedeno implicitně
vytvořeným konstruktorem:
Real::Real( const Real & y) { Re = y.Re; }
Parametr x typu Complex do tohoto konstruktoru lze předat
• Complex => Complex & => Real & => const Real &
Nesprávné užití dědičnosti
Nesprávné
užití dědičnosti č. 2
class Complex { public: double Re, Im; };
class Real : public Complex { public: Real( double r); };
Vypadá jako korektní specializace:
"každé reálné číslo má všechny vlastnosti komplexního čísla"
Chyba: Objekty v C++ nejsou hodnoty v matematice
Třída Complex má vlastnost "lze do mne přiřadit Complex"
• Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude
Nesprávné užití dědičnosti
Nesprávné
užití dědičnosti č. 2
class Complex { public: double Re, Im; };
class Real : public Complex { public: Real( double r); };
Vypadá jako korektní specializace:
"každé reálné číslo má všechny vlastnosti komplexního čísla"
Chyba: Objekty v C++ nejsou hodnoty v matematice
Třída Complex má vlastnost "lze do mne přiřadit Complex"
• Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude
void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; }
Real x;
set_to_i( x);
// tento kód LZE přeložit, a to je špatně
Důvod: Referenci na potomka lze přiřadit do reference na předka
• Real => Real & => Complex &
Nesprávné užití dědičnosti
Nesprávné
užití dědičnosti č. 2
class Complex { public: double Re, Im; };
class Real : public Complex { public: Real( double r); };
Vypadá jako korektní specializace:
"každé reálné číslo má všechny vlastnosti komplexního čísla"
Chyba: Objekty v C++ nejsou hodnoty v matematice
Třída Complex má vlastnost "lze do mne přiřadit Complex"
• Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude
Poznámka: při přímem přiřazování tento problém nenastane
Complex y;
Real x;
x = y; // tento kód NELZE přeložit
• Důvod: operátor = se nedědí
Complex & Complex::operator=( const Complex &); // nezdědí se
Real & Real::operator=( const Real &); // nesouhlasí typ argumentu
Šablony
Templates
Šablony tříd - definice
Šablona
je generická třída parametrizovaná libovolným
počtem formálních parametrů těchto druhů:
celé číslo – uvnitř šablony se chová jako konstanta, použitelná jako
meze polí
ukazatel libovolného typu
libovolný typ – deklarováno zápisem class T nebo typename T,
identifikátor formálního parametru se chová jako identifikátor typu,
použitelný uvnitř šablony v libovolné deklaraci
šablona třídy s definovanými formálními parametry
seznam typů ("variadic template")
C++11
Prefix
definice šablony
template< formální-parametry>
lze použít před několika formami deklarací; oblastí platnosti
formálních parametrů je celá prefixovaná deklarace
Šablony tříd - instanciace
Instanciace
šablony: Šablonu lze použít jako typ pouze s
explicitním uvedením skutečných parametrů odpovídajících
druhů:
celé číslo: celočíselný konstantní výraz
ukazatel: adresa globální nebo statické proměnné či funkce
kompatibilního typu
libovolný typ – jméno typu či typová konstrukce (včetně jiné
instanciované šablony)
šablona s odpovídajícími formálními parametry
Užití
instanciované šablony:
Instanciované šablony jsou stejného typu, pokud jsou stejného
jména a jejich skutečné parametry obsahují stejné hodnoty
konstantních výrazů, adresy stejných proměnných či funkcí a stejné
typy
Šablony tříd – závislé typy
Šablony
tříd (včetně těl metod) se při deklaraci kontrolují
pouze syntakticky
•Některé překladače nedělají ani to
Překladač potřebuje odlišit jména typů od ostatních jmen
•U jmen ve tvaru A::B to překladač někdy nedokáže
•Programátor musí pomoci klíčovým slovem typename
template< typename T> class X
{
typedef typename T::B U;
// T::B je typ
typename U::D p;
// T::B::D je typ
typename Y<T>::C q;
// Y<T>::C je typ
void f() { T::D(); }
// T::D není typ
}
je nutné uvést před jmény typu ve tvaru A::B, kde A
je závislé jméno
•Závislé jméno je jméno obsahující přímo či nepřímo parametr
šablony
•typename
Šablony tříd - this
Pokud
je mezi předkem třídy závislé jméno
překladač pak neví, které identifikátory jsou zděděny
uživatel musí pomoci konstrukcí this->
template< typename T> class X
: public T
{
void f() { return this->a; }
}
Šablony funkcí
Šablona
funkce je generická funkce (globální nebo metoda)
prefixovaná konstrukcí template
se stejnými druhy formálních parametrů šablony jako u šablon tříd
template< typename T, int k>
int f( T * p, int q);
template< typename T, typename U>
int g( T * p, vector< U> q);
Šablony
//
//
//
//
parametry
parametry
parametry
parametry
šablony
funkce
šablony
funkce
funkcí lze volat dvěma způsoby
Explicitně
f< int, 729>( a, b)
Automaticky
g( a, b)
• Překladač dopočte parametry šablony z typů parametrů funkce
• Všechny formální argumenty šablony by měly být užity v typech
formálních parametrů funkce
Šablony funkcí
Pod
stejným identifikátorem může být deklarováno několik
různých šablon funkce a navíc několik obyčejných funkcí.
Obyčejné funkce mají přednost před generickými
template< class T> T max( T a, T b)
{ return a < b ? b : a; };
char * max( char * a, char * b)
{ return strcmp( a, b) < 0 ? b : a; };
template< int n, class T> T max( Array< n, T> a)
{ /* ... */ }
Příklad ze standardních knihoven:
template< class T> void swap( T & a, T & b)
{ T tmp(a); a = b; b = tmp; };
• K tomu řada chytřejších implementací swap pro některé třídy
Šablony – pokročilé konstrukce jazyka
Parciální specializace
Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definice
template< int n> class Array< n, bool>
{ /* specializace pro pole typu bool */ };
Krajním případem parciální specializace je explicitní specializace
Explicitní specializace
template<> class Array< 32, bool> { /* ... */ };
U šablon funkcí nahrazena obyčejnou funkcí
Explicitní instanciace
Překladač je možné donutit ke kompletní instanciaci šablony
template class Array< 128, char>;
Explicitní specializace
Příklad
template< int N>
struct Fib {
enum { value = Fib< N-1>::value + Fib< N-2>::value };
};
template<> struct Fib< 0> {
enum { value = 1 };
};
template<> struct Fib< 1> {
enum { value = 1 };
};
Kontrolní otázka:
Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value
Teoretický pohled na šablony
Jiný příklad
template< int N>
struct Fib {
enum { value = Fib< N-1>::value + Fib< N-2>::value };
};
template<> struct Fib< 0> {
enum { value = 1 };
};
template<> struct Fib< 1> {
enum { value = 1 };
};
Kontrolní otázka:
Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value
MS Visual C++ 7.1: Build Time 0:00
Kompilátory ukládají již vytvořené instanciace a nepočítají je znovu
Parciální specializace
Parciální specializace
Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definice
• Parciálně specializovat lze šablony funkcí, celé šablony tříd i jednotlivě
těla jejich metod
• Obsah specializace šablony třídy (teoreticky) nemusí nijak souviset se
základní definicí - může mít zcela jiné položky, předky apod.
• Základní definice dokonce nemusí vůbec existovat (ale musí být
deklarována)
template< class X, class Y> class C;
template< class P,
bool cmp( P *, Q
};
template< class Z>
bool set( Z &);
};
template< class X,
X add( Y);
};
// základní deklarace
class Q> class C< P *, Q *> { // specializace
*);
class C< Z, Z> : public Z { // jiná specializace
class Y> class C { // základní definice
Parciální specializace
Parciální specializace
Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definice
• Parciální specializace může mít stejný, menší i větší počet formálních
parametrů než základní definice, jejich hodnoty se odvozují ze
skutečných parametrů šablony (kterých je vždy tolik, kolik určuje
základní definice)
template< class T, class U, int n> class C< T[n], U[n]>
{ /* specializace pro dvě pole stejné velikosti */ };
Parciální specializace
Parciální specializace
Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definice
• Parciální specializace může mít stejný, menší i větší počet formálních
parametrů než základní definice, jejich hodnoty se odvozují ze
skutečných parametrů šablony (kterých je vždy tolik, kolik určuje
základní definice)
template< class T, class U, int n> class C< T[n], U[n]>
{ /* specializace pro dvě pole stejné velikosti */ };
Krajním případem parciální specializace je explicitní specializace
Explicitní specializace
template<> class C< char, int[ 8]> { /* ... */ };
Explicitní specializace šablony není šablona
Podléhá trochu jiným (jednodušším) pravidlům
• Překlad se neodkládá
• Těla metod se nepíší do hlavičkových souborů
Parciální specializace
Typická použití parciální a explicitní specializace
Výhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu
Programátor - uživatel šablony o specializaci nemusí vědět
Příklad: Implementace vector<char> může být jednodušší
Parciální specializace
Typická použití parciální a explicitní specializace
Výhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu
Programátor - uživatel šablony o specializaci nemusí vědět
Příklad: Implementace vector<char> může být jednodušší
Mírná
změna rozhraní ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu
Uživatel by měl být o specializaci informován
Příklad: vector< bool> nedovoluje vytvořit ukazatel na jeden prvek
Parciální specializace
Typická použití parciální a explicitní specializace
Výhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu
Programátor - uživatel šablony o specializaci nemusí vědět
Příklad: Implementace vector<char> může být jednodušší
Mírná
změna rozhraní ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu
Uživatel by měl být o specializaci informován
Příklad: vector< bool> nedovoluje vytvořit ukazatel na jeden prvek
Modifikace
chování jiné šablony
Specializovaná šablona je volána z jiného šablonovaného kódu
Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony
Autor specializace tak upravuje chování volající šablony
Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkce
Parciální specializace
Modifikace chování jiné šablony
Specializovaná šablona je volána z jiného šablonovaného kódu
Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony
Autor specializace tak upravuje chování volající šablony
Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkce
template< class T> struct char_traits;
template< class T> class basic_string { /* ... */
int compare( const basic_string & b) const
{ /*...*/ char_traits< T>::compare( /* ... */) /*...*/ }
};
template<> struct char_traits< char> { /* ... */
static int compare(const char* s1, const char* s2, size_t n)
{ return memcmp( s1, s2, n); }
};
Parciální specializace
Modifikace chování jiné šablony
Specializovaná šablona je volána z jiného šablonovaného kódu
Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony
Autor specializace tak upravuje chování volající šablony
Traits
Šablony, ze kterých nejsou vytvářeny objekty
Obsahují pouze:
Definice typů
Statické funkce
Určeny
k doplnění informací o nějakém typu
Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Traits & policies
Traits
Šablony, ze kterých nejsou vytvářeny objekty
Obsahují pouze:
Definice typů
Statické funkce
Určeny
k doplnění informací o nějakém typu
Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Policy classes
Třídy, ze kterých obvykle nejsou vytvářeny objekty
Předávány jako parametr šablonám
Defaultní hodnotou parametru často bývá šablona traits
Určeny
k definování určitého chování
Příklad: Alokační strategie
Triky s šablonami
Porovnání typů s booleovským výstupem
template< class A, class B>
struct Equal {
enum { value = false };
};
template< class A>
struct Equal< A, A> {
enum { value = true };
};
Equal< X, Y>::value je konstantní výraz
Použití
template< class T1>
class Test {
enum { T1_is_int = Equal< int, T1>::value};
enum { T1_is_long = Equal< long, T1>::value};
/* ... */
};
Triky s šablonami
Porovnání typů s typovým výstupem
template< class A, class B, class C, class D>
struct IfEqual {
typedef D Result;
};
template< class A, class C, class D>
struct Equal< A, A, C, D> {
typedef C Result;
};
IfEqual< X, Y, U, V>::Result je typ
• Význam: X == Y ? U : V
Použití
template< class T1>
class Test {
typedef Equal< T1, unsigned, unsigned long, long>::Result longT1;
/* ... */
};
Triky s šablonami
Kompilační ověření invariantu
template< int x>
struct AssertNot;
template<>
struct AssertNot< 0> {
enum { value = true };
};
template< int x>
struct Assert {
enum { value = AssertNot< ! x>::value };
};
Triky s šablonami
Kompilační ověření invariantu
template< int x>
struct AssertNot;
template<>
struct AssertNot< 0> {
enum { value = true };
};
template< int x>
struct Assert {
enum { value = AssertNot< ! x>::value };
};
Použití
template< int N> class Array {
enum { check = Assert< (N > 0)>::value };
/* ... */
};
Array< -3> x;
• error C2027: use of undefined type 'AssertNot<x>' with [x=1]
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Použití
enum { N = 10 };
int permutations[ Fact< N>::value];
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Kontrolní otázka:
Kolik je Fact< -1>::value
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Kontrolní otázka:
Kolik je Fact< -1>::value
MS Visual C++ 7.1:
• fatal error C1202: recursive type or function dependency context too
complex
Řetěz instanciací Fact< -1>, Fact< -2>, Fact< -3>, ... způsobí
přetečení tabulek kompilátoru
Šablony tříd – pravidla použití
Uvnitř
těla šablony (nebo jako její předky) je možno užívat
libovolné typy včetně:
Instancí jiných šablon
Téže šablony s jinými argumenty
Téže šablony se stejnými argumenty
• V tomto případě se argumenty mohou, ale nemusí opisovat
Ekvivalentní varianty šablony s copy-constructorem:
template< typename T> class X {
X( const X< T> &);
};
template< typename T> class X {
X( const X &);
};
• Některé překladače připouštějí i tuto variantu
template< typename T> class X {
X< T>( const X< T> &);
};
Šablony tříd – pravidla použití
Metody
šablon mohou mít těla uvnitř třídy nebo vně
Vně uvedená těla metod musejí být připojena k šabloně takto:
template< typename T> void X< T>::f( int a, int b)
{ /* ... */ }
V kvalifikovaném jméně metody je nutné uvést patřičný seznam
argumentů, tj. X< T>::f a nikoliv X::f
metod musejí být viditelná z každého místa, kde jsou
pro nějakou instanci šablony volána
Těla
Musejí tedy typicky být v témže hlavičkovém souboru jako sama
šablona.
Uvedení těla metody vně třídy tedy u šablon typicky nic nepřináší,
může být však vynuceno rekurzivními odkazy mezi šablonami apod.
Šablony tříd – pravidla použití
Šablona
třídy se překládá až v okamžiku instanciace, tj.
použití s konkrétními parametry
Překladač instanciuje (tj. překládá) pouze ty metody, které
jsou zapotřebí (tj. jsou volány nebo jsou virtuální)
Některá těla metod tedy nemusí být pro některé případy parametrů
přeložitelná
template< typename Container> class Proxy {
public:
void pop_front() { c->pop_front(); }
/* ... */
private:
Container * c;
};
// jen pro list/deque
Explicitní instanciace
Překladač je možné donutit ke kompletní instanciaci šablony
template class Array< 128, char>;
Šablony tříd – triky
Dopředná deklarace šablony
template< typename T> class X;
/* ... zde může být použito X<U> s jakýmikoliv argumenty U...
... pouze v kontextech,
kde kompilátor nepotřebuje znát tělo šablony ...
*/
template< typename T> class X
{ /* ... */
};
Cast
Různé druhy přetypování
Přetypování
Různé varianty syntaxe
C-style cast
(T)e
Převzato z C
Přetypování
Různé varianty syntaxe
C-style cast
(T)e
Převzato z C
Function-style
T(e)
cast
Ekvivalentní (T)e
• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Přetypování
Různé varianty syntaxe
C-style cast
(T)e
Převzato z C
Function-style
T(e)
cast
Ekvivalentní (T)e
• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Type
conversion operators
Pro odlišení účelu (síly a nebezpečnosti) přetypování:
const_cast<T>(e)
static_cast<T>(e)
reinterpret_cast<T>(e)
Přetypování
Různé varianty syntaxe
C-style cast
(T)e
Převzato z C
Function-style
T(e)
cast
Ekvivalentní (T)e
• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Type
conversion operators
Pro odlišení účelu (síly a nebezpečnosti) přetypování:
const_cast<T>(e)
static_cast<T>(e)
reinterpret_cast<T>(e)
Novinka - přetypování s běhovou kontrolou:
dynamic_cast<T>(e)
Přetypování
const_cast<T>(e)
Odstranění
konstantnosti
const U & => U &
const U * => U *
používáno pro měnění pomocných položek v
logicky konstantních objektech
Obvykle
Příklad: Čítač odkazů na logicky konstantní objekt
class Data {
public:
void register_pointer() const
{ const_cast< Data *>( this)->references++; }
private:
/* ... data ... */
int references;
};
Jiný příklad: datové struktury s amortizovaným vyhledáváním
Přetypování
const_cast<T>(e)
Odstranění
konstantnosti
const U & => U &
const U * => U *
moderních překladačů lze nahradit specifikátorem
mutable
U
Příklad: Čítač odkazů na logicky konstantní objekt
class Data {
public:
void register_pointer() const
{ references++; }
private:
/* ... data ... */
mutable int references;
};
Přetypování
static_cast<T>(e)
Umožňuje
Všechny implicitní konverze
Bezztrátové i ztrátové aritmetické konverze (int <=> double apod.)
Konverze přidávající modifikátory const a volatile
Konverze ukazatele na void *
Konverze odkazu na odvozenou třídu na odkaz na předka:
• Derived & => Base &
• Derived * => Base *
Aplikace copy-constructoru; v kombinaci s implicitní konverzí též:
• Derived => Base (slicing: okopírování části objektu)
Aplikace libovolného konstruktoru T::T s jedním parametrem
• Uživatelská konverze libovolného typu na třídu T
Aplikace konverzního operátoru: operator T()
• Uživatelská konverze nějaké třídy na libovolný typ T
Přetypování
static_cast<T>(e)
Umožňuje
Všechny implicitní konverze
Ekvivalentní použití pomocné proměnné tmp deklarované takto:
T tmp(e);
Používá se tehdy, pokud se vynucením jedné z možných
implicitních konverzí odstraní nejednoznačnost nebo vynutí volání
jiné varianty funkce
class A { /* ... */ }; class B { /* ... */ };
void f( A *); void f( B*);
class C : public A, public B { /* ... */
void g() { f( static_cast< A>( this)); }
};
Přetypování
static_cast<T>(e)
Umožňuje
Všechny implicitní konverze
Konverze na void - zahození hodnoty výrazu
Používá se v makrech a podmíněných výrazech
Konverze
odkazu na předka na odkaz na odvozenou třídu
• Base & => Derived &
• Base * => Derived *
Pokud objekt, na nějž konvertovaný odkaz ukazuje, není typu
Derived či z něj odvozený, je výsledek nedefinovaný
• K chybě obvykle dojde později!
Konverze
celého čísla na výčtový typ
Pokud hodnota čísla neodpovídá žádné výčtové konstantě, výsledek
je nedefinovaný
Konverze
void * na libovolný ukazatel
Přetypování
static_cast<T>(e)
Nejčastější použití
Konverze odkazu na předka na odkaz na odvozenou třídu
class Base { public: enum Type { T_X, T_Y };
virtual Type get_type() const = 0;
};
class X : public Base { /* ... */
virtual Type get_type() const { return T_X; }
};
class Y : public Base { /* ... */
virtual Type get_type() const { return T_Y; }
};
Base *
switch
case
case
}
p = /* ... */;
( p->get_type() ) {
T_X: { X * xp = static_cast< X *>( p); /* ... */ } break;
T_Y: { Y * yp = static_cast< Y *>( p); /* ... */ } break;
Přetypování
reinterpret_cast<T>(e)
Umožňuje
Konverze ukazatele na dostatečně velké celé číslo
Konverze celého čísla na ukazatel
Konverze mezi různými ukazateli na funkce
Konverze odkazu na odkaz na libovolný jiný typ
• U * => V *
• U & => U &
Neuvažuje příbuzenské vztahy tříd, neopravuje hodnoty ukazatelů
Většina použití je závislá na platformě
Příklad: Přístup k reálné proměnné po bajtech
Typické použití: Čtení a zápis binárních souborů
void put_double( std::ostream & o, const double & d)
{ o.write( reinterpret_cast< char *>( & d), sizeof( double)); }
• Obsah souboru je nepřenositelný
Přetypování
dynamic_cast<T>(e)
Umožňuje
Konverze odkazu na odvozenou třídu na odkaz na předka:
• Derived & => Base &
• Derived * => Base *
Implicitní konverze, chová se stejně jako static_cast
Konverze
odkazu na předka na odkaz na odvozenou třídu
• Base & => Derived &
• Base * => Derived *
Podmínka: Base musí obsahovat alespoň jednu virtuální funkci
Pokud konvertovaný odkaz neodkazuje na objekt typu Derived nebo
z něj odvozený, je chování definováno takto:
• Konverze ukazatelů vrací nulový ukazatel
• Konverze referencí vyvolává výjimku std::bad_cast
Umožňuje přetypování i v případě virtuální dědičnosti
Přetypování
dynamic_cast<T>(e)
Nejčastější použití
Konverze odkazu na předka na odkaz na odvozenou třídu
class Base { public:
virtual ~Base(); /* alespoň jedna virtuální funkce */
};
class X : public Base { /* ... */
};
class Y : public Base { /* ... */
};
Base * p = /* ... */;
X * xp = dynamic_cast< X *>( p);
if ( xp ) { /* ... */ }
Y * yp = dynamic_cast< Y *>( p);
if ( yp ) { /* ... */ }
Exception handling
Mechanismus výjimek
Exception handling
Srovnání: goto
Start: příkaz goto
Cíl: návěští
Určen při kompilaci
Skok
může opustit blok
Proměnné korektně zaniknou
voláním destruktorů
Cíl
musí být v téže proceduře
int f()
{
if ( something == wrong )
{
goto label;
}
else
{
MyClass my_variable;
if ( anything != good )
{
goto label;
}
/* ... */
}
return 0;
label:
std::cerr
<< "Error"
<< std::endl;
return -1;
}
Exception handling
Srovnání: goto
Start: příkaz goto
Cíl: návěští
Určen při kompilaci
Skok
může opustit blok
Proměnné korektně zaniknou
voláním destruktorů
Cíl
musí být v téže proceduře
Srovnání 2: <csetjmp>
Pro pokročilé
Start: volání longjmp
Cíl: volání setjmp
Skok může opustit proceduru
Neřeší lokální proměnné
Nelze použít v C++
Předává
hodnotu typu int
int f()
{
if ( something == wrong )
{
goto label;
}
else
{
MyClass my_variable;
if ( anything != good )
{
goto label;
}
/* ... */
}
return 0;
label:
std::cerr
<< "Error"
<< std::endl;
return -1;
}
Exception handling
Mechanismus výjimek
Start: příkaz throw
Cíl: try-catch blok
Určen za běhu
Skok
může opustit proceduru
Proměnné korektně zaniknou
voláním destruktorů
Předává
hodnotu libovolného
typu
Typ hodnoty se podílí na určení
cíle skoku
void f()
{
if ( something == wrong )
throw 729;
else
{
MyClass my_variable;
if ( anything != good )
throw 123;
/* ... */
}
}
void g()
{
try {
f();
}
catch ( int e ) {
std::cerr
<< "Exception in f(): "
<< e
<< std::endl;
}
}
Exception handling
Mechanismus výjimek
Start: příkaz throw
Cíl: try-catch blok
Určen za běhu
Skok
může opustit proceduru
Proměnné korektně zaniknou
voláním destruktorů
Předává
hodnotu libovolného
typu
Typ hodnoty se podílí na určení
cíle skoku
Obvykle se používají pro tento
účel zhotovené třídy
class WrongException { /*...*/ };
class BadException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException( something);
if ( anything != good )
throw BadException( anything);
}
void g()
{
try {
f();
}
catch ( const WrongException & e1 ) {
/*...*/
}
catch ( const BadException & e2 ) {
/*...*/
}
}
Exception handling
Mechanismus výjimek
Start: příkaz throw
Cíl: try-catch blok
Určen za běhu
Skok
může opustit proceduru
Proměnné korektně zaniknou
voláním destruktorů
Předává
hodnotu libovolného
typu
Typ hodnoty se podílí na určení
cíle skoku
Obvykle se používají pro tento
účel zhotovené třídy
Mechanismus výjimek
respektuje hierarchii dědičnosti
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException( something);
if ( anything != good )
throw BadException( anything);
}
void g()
{
try {
f();
}
catch ( const AnyException & e1 ) {
/*...*/
}
}
Exception handling
Mechanismus výjimek
Start: příkaz throw
Cíl: try-catch blok
Určen za běhu
Skok
může opustit proceduru
Proměnné korektně zaniknou
voláním destruktorů
Předává
hodnotu libovolného
typu
Typ hodnoty se podílí na určení
cíle skoku
Obvykle se používají pro tento
účel zhotovené třídy
Mechanismus výjimek
respektuje hierarchii dědičnosti
Hodnotu není třeba využívat
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
void g()
{
try {
f();
}
catch ( const AnyException &) {
/*...*/
}
}
Exception handling
Mechanismus výjimek
Start: příkaz throw
Cíl: try-catch blok
Určen za běhu
Skok
může opustit proceduru
Proměnné korektně zaniknou
voláním destruktorů
Předává
hodnotu libovolného
typu
Typ hodnoty se podílí na určení
cíle skoku
Obvykle se používají pro tento
účel zhotovené třídy
Mechanismus výjimek
respektuje hierarchii dědičnosti
Hodnotu není třeba využívat
Existuje univerzální catch blok
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
void g()
{
try {
f();
}
catch (...) {
/*...*/
}
}
Exception handling
Fáze zpracování výjimky
Vyhodnocení výrazu v příkaze throw
Hodnota je uložena "stranou"
Stack-unwinding
Postupně se opouštějí bloky a funkce, ve kterých bylo provádění
vnořeno
Na zanikající lokální a pomocné proměnné jsou volány destruktory
Stack-unwinding končí dosažením try-bloku, za kterým je catch-blok
odpovídající typu výrazu v příkaze throw
Provedení
kódu v catch-bloku
Původní hodnota throw je stále uložena pro případné pokračování:
• Příkaz throw bez výrazu pokračuje ve zpracování téže výjimky počínaje
dalším catch-blokem - začíná znovu stack-unwinding
Zpracování
definitivně končí opuštěním catch-bloku
Běžným způsobem nebo příkazy return, break, continue, goto
• Nebo vyvoláním jiné výjimky
Exception handling
Použití mechanismu výjimek
Vyvolání
a zpracování výjimky je relativně časově náročné
Používat pouze pro chybové nebo řídké stavy
• Např. nedostatek paměti, ztráta spojení, chybný vstup, konec souboru
Připravenost
na výjimky také něco (málo) stojí
Za normálního běhu je třeba zařídit, aby výjimka dokázala najít cíl a
zrušit proměnné
• Výjimky se týkají i procedur, ve kterých není ani throw, ani try-blok
Většina kompilátorů umí překládat ve dvou režimech "s" a "bez"
• Celý spojovaný program musí být přeložen stejně
Exception handling
Standardní výjimky
<stdexcept>
Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášením
bad_alloc:
vyvolává operátor new při nedostatku paměti
V režimu "bez výjimek" new vrací nulový ukazatel
bad_typeid: Chybné použití RTTI
Odvozené z třídy logic_error:
bad_cast,
domain_error, invalid_argument, length_error, out_of_range
vyvolávány např. funkcí vector::operator[]
Odvozené
z třídy runtime_error:
range_error, overflow_error, underflow_error
Exception handling
Standardní výjimky
<stdexcept>
Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášením
bad_alloc:
vyvolává operátor new při nedostatku paměti
V režimu "bez výjimek" new vrací nulový ukazatel
bad_cast,
bad_typeid: Chybné použití RTTI
Odvozené z třídy logic_error:
domain_error, invalid_argument, length_error, out_of_range
vyvolávány např. funkcí vector::operator[]
Odvozené
z třídy runtime_error:
range_error, overflow_error, underflow_error
Aritmetické
ani ukazatelové operátory na vestavěných
typech NEHLÁSÍ běhové chyby prostřednictvím výjimek
např. dělení nulou nebo dereference nulového ukazatele
Exception handling
Typické použití
Deklarace výjimek
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
Vyvolání
výjimek
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
Částečné
ošetření
void g()
{ try {
f();
}
catch (...) {
std::cout << "Exception in g()";
throw;
}
}
Podrobné
ošetření
int main( /*...*/)
{ try {
g();
h();
}
catch ( WrongException ) {
std::cout << "WrongException";
}
catch ( BadException ) {
std::cout << "BadException";
}
}
Exception handling
Použití se std::exception
Deklarace výjimek
class WrongException
: public std::exception {
virtual const char * what() const
{ return "WrongException"; }
};
class BadException
: public std::exception {
virtual const char * what() const
{ return "BadException"; }
};
Vyvolání
výjimek
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
Částečné
ošetření
void g()
{ try {
f();
}
catch (...) {
std::cout << "Exception in g()";
throw;
}
}
Podrobné
ošetření
int main( /*...*/)
{ try {
g();
h();
}
catch ( const std::exception & e ) {
std::cout << e.what();
}
}
Exception-safe programming
Používat
throw a catch je
jednoduché
Těžší
je programovat běžný
kód tak, aby se choval korektně
i za přítomnosti výjimek
Exception-safety
Exception-safe programming
void f()
{
int * a = new int[ 100];
int * b = new int[ 200];
g( a, b);
delete[] b;
delete[] a;
}
Pokud new int[ 200] způsobí
výjimku, procedura zanechá
naalokovaný nedostupný blok
Pokud výjimku vyvolá
procedura g, zůstanou dva
nedostupné bloky
Exception-safe programming
Používat
throw a catch je
jednoduché
Těžší
je programovat běžný
kód tak, aby se choval korektně
i za přítomnosti výjimek
Exception-safety
Exception-safe programming
T & operator=( const T & b)
{
if ( this != & b )
{
delete body_;
body_ = new TBody( b.length());
copy( body_, b.body_);
}
return * this;
}
Pokud new TBody způsobí
výjimku, operátor= zanechá v
položce body_ původní
ukazatel, který již míří na
dealokovaný blok
Pokud výjimku vyvolá
procedura copy, operátor
zanechá třídu v neúplném
stavu
Exception-safe programming
Pravidla vynucená jazykem
Destruktor
nesmí skončit vyvoláním výjimky
Výjimka může být vyvolána uvnitř, ale musí být zachycena
nejpozději uvnitř destruktoru
Zdůvodnění:
V rámci ošetření výjimek (ve fázi stack-unwinding) se volají
destruktory lokálních proměnných
Výjimku zde vyvolanou nelze z technických i logických důvodů
ošetřit (ztratila by se původní výjimka)
Nastane-li taková výjimka, volá se funkce terminate() a program
končí
Exception-safe programming
Pravidla vynucená jazykem
Destruktor
nesmí skončit vyvoláním výjimky
Výjimka může být vyvolána uvnitř, ale musí být zachycena
nejpozději uvnitř destruktoru
Toto
pravidlo jazyka sice platí pouze pro destruktory
lokálních proměnných
A z jiných důvodů též pro globální proměnné
Je
však vhodné je dodržovat vždy
Bezpečnostní zdůvodnění: Destruktory lokálních proměnných často
volají jiné destruktory
Logické zdůvodnění: Nesmrtelné objekty nechceme
Exception-safe programming
Pravidla vynucená jazykem
Destruktor
nesmí skončit vyvoláním výjimky
Konstruktor
globálního objektu nesmí skončit vyvoláním
výjimky
Zdůvodnění: Není místo, kde ji zachytit
Stane-li se to, volá se terminate() a program končí
Jiné konstruktory ale výjimky volat mohou (a bývá to vhodné)
Exception-safe programming
Pravidla vynucená jazykem
Destruktor
nesmí skončit vyvoláním výjimky
Konstruktor
globálního objektu nesmí skončit vyvoláním
výjimky
typu v hlavičce catch-bloku nesmí skončit
vyvoláním výjimky
Copy-constructor
Zdůvodnění: Catch blok by nebylo možné vyvolat
Stane-li se to, volá se terminate() a program končí
Jiné copy-constructory ale výjimky volat mohou (a bývá to vhodné)
Exception-safe programming
Pravidla vynucená jazykem
Destruktor
nesmí skončit vyvoláním výjimky
Konstruktor
globálního objektu nesmí skončit vyvoláním
výjimky
typu v hlavičce catch-bloku nesmí skončit
vyvoláním výjimky
Copy-constructor
Exception-safe programming
Poznámka: Výjimky při zpracování výjimky
Výjimka
při výpočtu výrazu v throw příkaze
Tento throw příkaz nebude vyvolán
Výjimka
v destruktoru při stack-unwinding
Povolena, pokud neopustí destruktor
Po zachycení a normálním ukončení destruktoru se pokračuje v
původní výjimce
Výjimka
uvnitř catch-bloku
Pokud je zachycena uvnitř, ošetření původní výjimky může dále
pokračovat (přikazem throw bez výrazu)
Pokud není zachycena, namísto původní výjimky se pokračuje
ošetřováním nové
Exception-safe programming
Kompilátory samy ošetřují některé výjimky
Dynamická alokace polí
Dojde-li k výjimce v konstruktoru některého prvku, úspěšně
zkonstruované prvky budou destruovány
• Ve zpracování výjimky se poté pokračuje
Exception-safe programming
Kompilátory samy ošetřují některé výjimky
Dynamická alokace polí
Dojde-li k výjimce v konstruktoru některého prvku, úspěšně
zkonstruované prvky budou destruovány
• Ve zpracování výjimky se poté pokračuje
Výjimka
v konstruktoru součásti (prvku nebo předka) třídy
Sousední, již zkonstruované součásti, budou destruovány
Ve zpracování výjimky se poté pokračuje
• Uvnitř konstruktoru je možno výjimku zachytit speciálním try-blokem:
X::X( /* formální parametry */)
try : Y( /* parametry pro konstruktor součásti Y */)
{ /* vlastní tělo konstruktoru */
} catch ( /* parametr catch-bloku */ ) {
/* ošetření výjimky v konstruktoru Y i ve vlastním těle */
}
Konstrukci objektu nelze dokončit
• Opuštění speciálního catch bloku znamená throw;
Exception-safe programming
Definice
(Weak)
exception safety
Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v
případě výjimky zanechá veškerá data v konzistentním stavu
Konzistentní stav znamená zejména:
• Nedostupná data byla korektně destruována a odalokována
• Ukazatele nemíří na odalokovaná data
• Platí další invarianty dané logikou aplikace
Exception-safe programming
Definice
(Weak)
exception safety
Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v
případě výjimky zanechá veškerá data v konzistentním stavu
Konzistentní stav znamená zejména:
• Nedostupná data byla korektně destruována a odalokována
• Ukazatele nemíří na odalokovaná data
• Platí další invarianty dané logikou aplikace
Strong
exception safety
Funkce je silně bezpečná, pokud v případě, že skončí vyvoláním
výjimky, zanechá data ve stejném stavu, ve kterém byla při jejím
vyvolání
Nazýváno též "Commit-or-rollback semantics"
Exception-safe programming
Poznámky
(Weak)
exception safety
Tohoto stupně bezpečnosti lze většinou dosáhnout
Stačí vhodně definovat nějaký konzistentní stav, kterého lze vždy
dosáhnout, a ošetřit pomocí něj všechny výjimky
• Konzistentním stavem může být třeba nulovost všech položek
• Je nutné upravit všechny funkce tak, aby je tento konzistentní stav
nepřekvapil (mohou na něj ale reagovat výjimkou)
Strong
exception safety
Silné bezpečnosti nemusí jít vůbec dosáhnout, pokud je rozhraní
funkce navrženo špatně
Obvykle jsou problémy s funkcemi s dvojím efektem
• Příklad: funkce pop vracející odebranou hodnotu
Exception-safe programming
Příklad: String č. 2
operator=
Nebezpečná implementace:
Pokud new char způsobí
výjimku, operátor= zanechá v
položce str_ původní ukazatel,
který již míří na dealokovaný
blok
class String {
public:
// ...
private:
char * str_;
};
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Nebezpečná implementace:
Pokud new char způsobí
výjimku, operátor= zanechá v
položce str_ původní ukazatel,
který již míří na dealokovaný
blok
K jiné výjimce zde dojít
nemůže:
• std::operator delete výjimky
nikdy nevyvolává
• char je vestavěný typ a nemá
tedy konstruktory které by
mohly výjimku vyvolávat
• strlen a strcpy jsou C-funkce
• Parametry a návratová
hodnota se předávají odkazem
class String {
public:
// ...
private:
char * str_;
};
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Naivní
pokus o opravu:
Pokud new char způsobí
výjimku, ošetří se
Objekt se uvede do
konzistentního stavu
Výjimka se propaguje dál - ven
z funkce
Problém:
V catch bloku teoreticky může
vzniknout nová výjimka
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
try {
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
catch ( ... )
{
str_ = new char[ 1];
* str_ = 0;
throw;
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Lepší pokus o opravu:
Pokud new char způsobí
výjimku, ošetří se
Je
nutné pozměnit invariant
třídy String:
Položka str_ nyní smí
obsahovat nulový ukazatel
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
try {
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
catch ( ... )
{
str_ = 0;
throw;
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Lepší pokus o opravu:
Pokud new char způsobí
výjimku, ošetří se
Je
nutné pozměnit invariant
třídy String:
Položka str_ nyní smí
obsahovat nulový ukazatel
Takový exemplář String je
považován za konzistentní
Konzistentnost nemusí
znamenat, že to je z
uživatelského pohledu platná
hodnota
Může být považována i za
chybovou a každá operace s
takovou hodnotou může
vyvolávat výjimku
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
try {
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
catch ( ... )
{
str_ = 0;
throw;
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Ekvivalentní řešení:
Nulovat str_ po delete
Pokud new způsobí výjimku, v
str_ zůstane nulový ukazatel
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Chyba: změnili jsme invariant
str_ nyní může být nulové
delete _str je v pořádku
• operator delete je vždy proti
nulovému ukazateli ošetřen
(nedělá nic)
strlen a strcpy ale fungovat
nebudou
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Opraveno
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
if ( b.str_ )
{
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Vylepšení:
operator= může vyvolávat
výjimku, pokud se přiřazuje
neplatná hodnota
Tato výjimka může být
definována např. takto:
#include <exception>
class InvalidString
: public std::exception
{
virtual const char * what() const
{ return "Invalid string";
}
}
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
if ( b.str_ )
{
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
else
{
throw InvalidString();
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Toto řešení je slabě bezpečné
Silně bezpečné ale není:
Pokud dojde k výjimce,
nezachovává se původní stav
dat
To bude pro uživatele
nepříjemné:
String x, y;
/* ... */
try { x = y + x;
}
catch (...) { /* ... */
}
Uživatel nedokáže rozlišit mezi
výjimkami v operátorech + a =
Náš operator= ale v případě
výjimky ztratí hodnotu x
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
if ( b.str_ )
{
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
else
{
throw InvalidString();
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
Pokud dojde k výjimce v new,
nestane se nic
Ani před throw nenastane
žádná změna
String & String::operator=(
const String & b)
{
if ( this != & b )
{
if ( b.str_ )
{
char * aux = new char[
strlen( b.str_) + 1];
strcpy( aux, b.str_);
delete[] str_;
str_ = aux;
}
else
{
throw InvalidString();
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
Pozorování:
Toto řešení je "shodou
okolností" imunní proti
this == & b
String & String::operator=(
const String & b)
{
if ( this != & b )
{
if ( b.str_ )
{
char * aux = new char[
strlen( b.str_) + 1];
strcpy( aux, b.str_);
delete[] str_;
str_ = aux;
}
else
{
throw InvalidString();
}
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
Pozorování:
Toto řešení je "shodou
okolností" imunní proti
this == & b
Test je možno zrušit
String & String::operator=(
const String & b)
{
if ( b.str_ )
{
char * aux = new char[
strlen( b.str_) + 1];
strcpy( aux, b.str_);
delete[] str_;
str_ = aux;
}
else
{
throw InvalidString();
}
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
Pokud je copy-constructor silně
bezpečný
Standardní
řešení:
Copy-constructor naplní lokální
proměnnou c kopií parametru b
• Zde může dojít k výjimce
Metoda swap vyměňuje obsah
this a proměnné c
• Metoda swap je rychlá a
nevyvolává výjimky
Před návratem z operatoru se
volá destruktor c
• Tím zaniká původní obsah this
void String::swap( String & x)
{
char * aux = str_;
str_ = x.str_;
x.str_ = aux;
}
String & String::operator=(
const String & b)
{
String c( b);
swap( c);
return * this;
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
Metodu
swap je vhodné
publikovat ve formě globální
funkce
Některé algoritmy nad
kontejnery obsahujícími String
se tak zrychlí a stanou se
bezpečnými vůči výjimkám
void String::swap( String & x)
{
char * aux = str_;
str_ = x.str_;
x.str_ = aux;
}
String & String::operator=(
const String & b)
{
String c( b);
swap( c);
return * this;
}
void swap( String & x, String & y)
{
x.swap( y);
}
Exception-safe programming
Příklad: String č. 2
operator=
Silně bezpečné řešení
swap je vhodné
publikovat ve formě globální
funkce
#include <algorithm>
void String::swap( String & x)
{
swap( str_, x.str_);
}
Metodu
Některé algoritmy nad
kontejnery obsahujícími String
se tak zrychlí a stanou se
bezpečnými vůči výjimkám
Sama
metoda swap může
využívat šablonu swap pro typ
char *
String & String::operator=(
const String & b)
{
String c( b);
swap( c);
return * this;
}
void swap( String & x, String & y)
{
x.swap( y);
}
Exception-safe programming
Příklad: String č. 2
copy-constructor
Silně bezpečné řešení
Pokud tělo dorazí na konec,
budou datové položky korektně
vyplněny
Tělo může vyvolávat výjimky
• V takovém případě není třeba
datové položky vyplňovat
• Objekt nebude považován za
platný a nebude používán ani
destruován
Obecně je však třeba ošetřit
try-blokem situace, kdy je v
objektu více dynamicky
alokovaných ukazatelů
String( const String & b)
{
if ( b.str_ )
{
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
else
{
throw InvalidString();
}
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Slabě
bezpečná implementace:
Při výjimce v konstruktoru
proměnné s se nestane nic
operator delete nezpůsobuje
výjimky
struct Box { String v; Box * next; };
class StringStack {
public:
// ...
private:
Box * top_;
};
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
Box * p = top_;
String s = p->v;
top_ = p->next;
delete p;
return s;
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Slabě
bezpečná implementace
Není silně bezpečná:
Funkce vrací hodnotou
Pokud při vracení dojde k
výjimce v copy-constructoru,
zásobník již bude zkrácen
struct Box { String v; Box * next; };
class StringStack {
public:
// ...
private:
Box * top_;
};
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
Box * p = top_;
String s = p->v;
top_ = p->next;
delete p;
return s;
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Slabě
bezpečná implementace
Není silně bezpečná:
Funkce vrací hodnotou
Pokud při vracení dojde k
výjimce v copy-constructoru,
zásobník již bude zkrácen
Tuto výjimku lze ošetřit
try-blokem okolo příkazu return
• Uvést zásobník do původního
stavu
• Ale: co když se uvedení do
původního stavu nezdaří?
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
Box * p = top_;
String s = p->v;
top_ = p->next;
delete p;
try {
return s;
}
catch ( ...)
{
p = new Box;
p->v = s;
p->next = top_;
top_ = p;
throw;
}
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Nefunkční
implementace
Není silně bezpečná:
Funkce vrací hodnotou
Pokud při vracení dojde k
výjimce v copy-constructoru,
zásobník již bude zkrácen
Tuto výjimku lze ošetřit
try-blokem okolo příkazu return
Dokážeme udělat obnovení
původního stavu bez
nebezpečí výjimky
• Ale: jak zrušíme proměnnou p,
když k výjimce nedojde?
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
Box * p = top_;
String s = p->v;
top_ = p->next;
// tady bylo delete p;
try {
return s;
// tady by delete p; nepomohlo
}
catch ( ...)
{
top_ = p;
throw;
}
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Silně
bezpečná implementace
Jak zrušíme proměnnou p,
když k výjimce nedojde?
std::auto_ptr<
T>
"chytrý" ukazatel na T, který se
chová jako "jediný vlastník
objektu":
• po zkopírování se vynuluje
• při zániku volá delete
Pozor: auto_ptr má
nestandardní copy-constructor
a operator=
• modifikují svůj parametr
• pro auto_ptr nefungují
kontejnery apod.
#include <memory>
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
std::auto_ptr< Box> p = top_;
top_ = p->next;
try {
return p->v;
}
catch ( ...)
{
top_ = p;
// toto přiřazení nuluje p
throw;
}
}
// při návratu se automaticky zruší * p
// pokud je p nenulové
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
bezpečná implementace
Uživatel ji nedokáže použít tak,
aby to bylo silně bezpečné
Silně
Vracenou hodnotu je nutné
okopírovat
Nedá se poznat, zda výjimku
vyvolala metoda pop nebo
operator=
• V prvním případě je zásobník
nedotčen, ale ve druhém je již
zkrácen
StringStack stk;
String a;
/* ... */
try {
a = stk.pop();
}
catch (...)
{
/* ??? */
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Řešení A
Jako v STL
Rozdělit pop na dvě funkce
top vrací vrchol zásobníku
• může jej vracet odkazem
• nemodifikuje data
pop pouze zkracuje
• je silně bezpečná
StringStack stk;
String a;
/* ... */
try {
a = stk.top();
}
catch (...)
{
/* chyba kopírování
nebo prázdný zásobník,
proměnná a nezměněna,
zásobník nedotčen */
}
try {
stk.pop();
}
catch (...)
{
/* chyba zkracování,
proměnná a změněna,
zásobník nedotčen */
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Řešení B
Namísto vracení hodnoty
funkce pop vyplňuje parametr
předávaný odkazem
tím se vyloučí nutnost
kombinovat volání pop s dalším
kopírováním
Pro
uživatele jednodušší,
implementace pop je však těžší
StringStack stk;
String a;
/* ... */
try {
stk.pop( a);
}
catch (...)
{
/* chyba zkracování nebo kopírování,
proměnná a nezměněna,
zásobník nedotčen */
}
Exception-safe programming
Příklad: StringStack::pop
Zásobník prvků typu String
Implementován seznamem
Řešení B
Lze implementovat nad
řešením A
#include <memory>
class StringStack {
public:
/* A */
String & top();
void pop();
/* B */
void pop( String & out)
{
String & t = top();
swap( out, t);
try {
pop();
}
catch (...) {
swap( out, t);
throw;
}
}
};
Exception specifications
Exception specifications
U každé funkce (operátoru,
metody) je možno určit seznam
výjimek, kterými smí být
ukončena
Na výjimky ošetřené uvnitř
funkce se specifikace
nevztahuje
Pokud není specifikace
uvedena, povoleny jsou
všechny výjimky
Specifikace respektuje
dědičnost, to jest automaticky
povoluje i všechny potomky
uvedené třídy
void a()
{
/* tahle smí všechno */
}
void b() throw ()
{
/* tahle nesmí nic */
}
void c() throw ( std::bad_alloc)
{
/* tahle smí std::bad_alloc */
}
void d() throw ( std::exception, MyExc)
{
/* tahle smí potomky
std::exception a MyExc */
}
Exception specifications
Exception specifications
Kompilátor zajistí, že nepovolená výjimka neopustí funkci:
Pokud by se tak mělo stát, volá se unexpected()
• unexpected() smí vyvolat "náhradní" výjimku
Pokud ani náhradní výjimka není povolena, zkusí se vyvolat
std::bad_exception
Pokud ani std::bad_exception není povoleno, volá se terminate() a
program končí
Exception specifications
Exception specifications
Kompilátor zajistí, že
nepovolená výjimka neopustí
funkci
Toto je běhová kontrola
Kompilátor smí vydávat
nejvýše varování
Funkce smí volat jinou, která
by mohla vyvolat nepovolenou
výjimku (ale nemusí)
void f() throw ( std::exception)
{
}
void g() throw ()
{
f(); /* tohle se smí */
}
Exception specifications
Exception specifications
Kompilátor (a runtime) zajistí, že nepovolená výjimka
neopustí funkci
Microsoft Visual C++ 7.0 to ovšem neimplementuje
Kompilátor
to může využít
Speciálně při volání funkce s prázdným throw () se nemusí
generovat ošetřující kód
Program se zmenší a možná i zrychlí
Užitek
pro programátory:
Komentář
Ladicí prostředek
Přetěžování funkcí
Implicitní hodnoty parametrů funkcí
C++ dovoluje definování implicitní hodnoty parametrů
Definovány musí být pro několik posledních parametrů
Definují se u hlavičky funkce
U těla funkce se neopakují
Implicitní
hodnoty řeší kompilátor pouze na straně volání
Volaná funkce nezjistí, zda jsou hodnoty parametrů určeny
explicitně nebo implicitně
Implicitní
hodnoty se vyhodnocují jakoby v místě definice
Nemohou se odvolávat na lokální proměnné ani předchozí
argumenty
void f( int a, int b, int c = 7, int d = 26);
f( 1, 2, 3, 4);
f( 1, 2, 3);
f( 1, 2);
// f( 1, 2, 3, 26);
// f( 1, 2, 7, 26);
Přetěžování funkcí
C++ dovoluje existenci více funkcí téhož jména
ve stejné oblasti platnosti
Podmínkou je odlišnost v počtu a/nebo typech parametrů
Odlišnost typu návratové hodnoty nestačí
Při
volání funkce se konkrétní varianta určuje takto:
Vyberou se aplikovatelné varianty funkce podle počtu a typu
skutečných parametrů
• Přitom hrají roli implicitní hodnoty parametrů
Určí se ceny typových konverzí parametrů, zjednodušeně:
• Uživatelská konverze / ztrátová aritmetická konverze jsou
nejdražší
• Konverze potomek -> předek / aritmetická konverze na větší typ
• Konverze non-const -> const / typ <-> reference jsou nejlevnější
Vybere se nejlacinější aplikovatelná varianta
• Pokud je jich více, kompilátor ohlásí chybu
Přetěžování funkcí
int min( int a, int b)
{ return a < b ? a : b; }
double min( double a, double b)
{ return a < b ? a : b; }
min(
min(
min(
min(
1, 2); // min( int, int) - přesná shoda
1, 2.0); // min( double, double) - levnější varianta
1.0, 2); // min( double, double) - levnější varianta
1.0, 2.0); // min( double, double) - přesná shoda
void f( int, double);
void f( double, int);
f( 1, 2); // chyba: obě varianty jsou stejně drahé
f( 1.0, 2.0); // chyba: obě varianty jsou stejně drahé
Koenig lookup
iostream
Problém: namespace
namespace prostor {
class Souradnice { public: int x, y; };
std::ostream & operator<<(
std::ostream & s, const Souradnice & a)
{
return s << '[' << a.x << ',' << a.y << ']';
}
};
prostor::Souradnice p;
std::cout << p;
// správný operator<< je v namespace prostor,
// který není přímo vidět
iostream
Problém: namespace
namespace prostor {
class Souradnice { public: int x, y; };
std::ostream & operator<<(
std::ostream & s, const Souradnice & a)
{
return s << '[' << a.x << ',' << a.y << ']';
}
};
prostor::Souradnice p;
std::cout << p;
// správný operator<< je v namespace prostor,
// který není přímo vidět
std::cout << std::endl;
// tentýž problém je ale už tady:
// tento operator<< je v namespace std
Koenig lookup
prostor::Souradnice p;
std::cout << p;
// správný operator<< je v namespace prostor,
// který není přímo vidět
std::cout << std::endl;
// tentýž problém je ale už tady:
// tento operator<< je v namespace std
případy jsou překládány správně
Je k tomu nutná složitá definice vyhledávání identifikátoru
Oba
tzv. Koenigovo vyhledávání
používá se, je-li význam identifikátoru závislý na parametrech
• volání funkce
• použití operátoru
Koenig lookup
Koenigovo vyhledávání (zjednodušeno)
Argument-dependent name lookup (ISO C++)
Pro
každý skutečný parametr se z jeho typu T určí množina
asociovaných namespace
Je-li T číselný, tyto množiny jsou prázdné
Je-li T union nebo enum, jeho asociovaným namespace je ten, ve
kterém je definován
Je-li T ukazatel na U nebo pole U, přejímá asociované namespace
od typu U
Je-li T funkce nebo ukazatel na funkci, přejímá (sjednocením)
asociované namespace všech parametrů a návratového typu
Je-li T třída, asociovanými namespace jsou ty, v nichž jsou
definovány tato třída a všichni její přímí i nepřímí předkové
Je-li T instancí šablony, přejímá kromě asociovaných tříd a
namespace definovaných pro třídu také asociované třídy a
namespace všech typových argumentů šablony
Koenig lookup
Koenigovo vyhledávání (zjednodušeno)
Argument-dependent name lookup (ISO C++)
Pro
každý skutečný parametr se z jeho typu T určí množina
asociovaných namespace
Identifikátor funkce se pak vyhledává v těchto prostorech
Globální prostor a aktuální namespace
Všechny namespace přidané direktivami using
Sjednocení asociovaných namespace všech parametrů funkce
Všechny
varianty funkce nalezené v těchto namespace
jsou rovnocenné
Mezi nimi se vybírá podle počtu a typu parametrů
• Pokud není jednoznačně určena nejlepší varianta, je to chyba
Volání
v kontextu třídy: Je-li identifikátor nalezen uvnitř této
třídy nebo některého předka (jako metoda), má přednost
před výše uvedenými variantami (globálními funkcemi)
Přetěžování operátorů
Operator overloading
Přetěžování operátorů
Většinu
operátorů jazyka C++ lze definovat pro uživatelské
datové typy.
Nelze předefinovat tyto operátory:
. .* ::
? : sizeof
jeden z operandů musí být třída nebo výčtový typ
nebo reference na ně
Alespoň
Nelze tudíž předefinovat operace na číselných typech a ukazatelích
Předefinováním
nelze měnit prioritu a asociativitu operátorů
Pro předefinované operátory nemusí platit identity
definované pro základní typy, např.:
++a nemusí být ekvivalentní a=a+1
a[b] nemusí být ekvivalentní *(a+b) ani b[a]
Pro
předefinované operátory && a || neplatí pravidla o
zkráceném vyhodnocování
Přetěžování operátorů
Typy
skutečných operandů předefinovaného operátoru
nemusejí přesně odpovídat typům formálních parametrů
operátoru.
Pro výběr správné varianty mezi předefinovanými operátory platí
stejná pravidla, jako pro přetížené funkce.
Předefinování
operátorů se provádí definováním metody se
speciálním jménem operatorxxx ve třídě (prvního
operandu), pro kterou má být operátor definován.
Některé operátory je možno definovat i jako globální funkce
s týmž speciálním jménem.
Speciální jméno je možno používat i pro explicitní vyvolání
této metody či funkce.
Operátory, které jsou metodami, jsou s výjimkou operátoru
přiřazení dědičné a smějí být virtuální.
Přetěžování operátorů - Binární operátory
Binární operátor xxx z množiny
+ - * / % << >> < > <= >= <<= >>= ^ & | && || == !=
+= -= *= /= %= ^= &= |= ->*
lze
pro operandy typu B a C předefinovat dvěma způsoby:
Globální funkcí
A operator xxx( B, C)
A operator xxx( B &, C &)
A operator xxx( const B &, const C &)
Metodou
A B::operator xxx( C)
A B::operator xxx( const C &)
A B::operator xxx( const C &) const
Binární operátor [ ]
lze předefinovat pouze metodou
A B::operator []( C)
A B::operator []( C &)
A B::operator []( const C &) const
Přetěžování operátorů - Unární operátory
Unární operátor xxx z množiny
+ - * & ~ !
a
prefixové operátory
++ - lze
pro operand typu B předefinovat dvěma způsoby:
Globální funkcí
A operator xxx( B)
A operator xxx( B &)
A operator xxx( const B &)
Metodou
A B::operator xxx()
A B::operator xxx() const
Přetěžování operátorů - Unární operátory
Postfixové operátory ++ a - lze pro operand typu B předefinovat dvěma způsoby:
Globální funkcí
A operator xxx( B, int)
A operator xxx( B &, int)
A operator xxx( const B &, int)
Metodou
A B::operator xxx( int)
A B::operator xxx( int) const
Přetěžování operátorů - Unární operátory
Operátor ->
je považován za unární operátor a jeho návratovou
hodnotou musí být buďto ukazatel na třídu s uvedenou
položkou, nebo objekt či referenci na objekt, pro který je
znovu definován operátor ->
Přetěžování operátorů - Unární operátory
Operátor volání funkce ()
smí být definován pouze jako metoda třídy a umožňuje
používat objekty této třídy jako funkce.
Smí mít libovolný počet parametrů a pro výběr konkrétní
varianty operátoru se použije podobný mechanismus, jako
pro přetížené funkce.
Complex
Komplexní číslo
Complex
class Complex {
public:
Complex() : re_( 0.0), im_( 0.0) {}
Complex( double re, double im = 0.0) : re_( re), im_( im) {}
double Re() const { return re_; }
double Im() const { return im_; }
Complex & operator+=( const Complex & b);
Complex operator-() const; // unární Complex & operator++();
// prefixové ++
Complex operator++( int);
// postfixové ++
// a mnoho dalších...
private:
double re_, im_;
};
Complex operator+( const Complex & a, const Complex & b);
Poučení - konstruktory
Ve
třídě Complex nejsou odkazy na data uložená jinde
Vyhovuje chování těchto kompilátorem vytvořených metod:
Complex( const Complex &);
Kopíruje datové položky
Complex & operator=( const Complex &);
Kopíruje datové položky
~Complex();
Nedělá nic
Tyto
metody není třeba psát vlastní
Poučení - konstruktory
Ve
třídě Complex jsou datové položky atomických typů
Ty nemají konstruktory a zůstávají neinicializované
Nevyhovalo
by tedy chování kompilátorem vytvořeného
konstruktoru bez parametrů:
Complex();
Nedělá nic
Navíc
je zde jiný konstruktor, takže kompilátor má
zakázáno konstruktor bez parametrů vytvořit
Nebylo by tedy možné deklarovat proměnnou typu Complex bez
explicitní inicializace
Konstruktor
bez parametrů musí být napsán ručně
Měl by nastavit datové položky na vhodnou hodnotu
Poučení - konstruktory
Speciální konstruktor
Complex( double re, double im = 0.0);
Lze zavolat s jedním parametrem
Slouží
jako konverzní konstruktor
Implementuje konverzi double => Complex
Důsledky:
Bude fungovat přiřazení Complex=double
Není nutné psát sčítání pro Complex+double:
Complex operator+( const Complex & a, double b) const;
• Dělává se to kvůli rychlosti
Kompilátor umí použít konverzi (jednu) a najít náhradní metodu:
Complex operator+(const Complex & a, const Complex & b) const;
Totéž funguje pro sčítání double+Complex
• Pouze pokud je sčítání Complex+Complex globální funkce
• U metod se levý operand nekonvertuje
Poučení - vracení hodnotou
Poučení: operator+ vždy vrací hodnotou
Vrací novou hodnotu, která jinde neexistuje
return Complex( ...);
Ale: operator+= může vracet odkazem
Vrací hodnotu levého operandu
return * this;
Poučení – binární operátory +, +=
Kanonické řešení
class Complex {
public:
Complex( double re, double im = 0.0); // konverzní konstruktor
// ...
Complex & operator+=( const Complex & b)
{
re_ += b.re_;
im_ += b.im_;
return * this;
}
// ...
};
Complex operator+( const Complex & a, const Complex & b)
{
Complex tmp( a);
tmp += b;
return tmp;
}
Poučení – unární operátory -, ++
Kanonické
řešení
Unární operátory jsou vždy metodami
• Není zapotřebí schopnost konverze operandů
class Complex {
public:
// ...
Complex operator-() const { return Complex( -re_, -im_); }
Complex & operator++() { _re += 1.0; return * this; }
Complex operator++( int)
{ Complex tmp( * this);
operator++();
return tmp;
}
};
Prefixové ++, -- vrací odkazem
• Může a nemusí být const
Postfixové ++, -- vrací hodnotou
Nepoužité slajdy
Globální proměnná
/* abc.hpp */
/* abc.cpp */
extern int x;
int x = 729;
extern const double a[ N];
const double a[ N] = { 1.2, 3.4 };
extern my_class y, z;
my_class y;
my_class z( 10, 20);
Statická položka třídy
/* abc.hpp */
/* abc.cpp */
class a_class
{
private:
static int x;
int a_class::x = 729;
static my_class y;
};
my_class a_class::y;
Statická lokální proměnná
Typické
použití: Singleton
Třída, vyskytující se v jediné
instanci
Zpřístupněna voláním f()
První volání inicializuje objekt
/* abc.cpp */
my_class & f()
{
static my_class z( 10, 20);
return z;
}
Lokální proměnná
void f()
{
int x = 729;
double u;
for ( int i = 0; i < x; ++i )
{
my_class y( i, 30);
y.f();
}
my_class z;
}
Pomocná proměnná
/* abc.cpp */
void f()
{
my_class x;
x = my_class( 20, 30);
typedef std::complex< double> my_complex;
my_complex p( 1.0, 0.0);
my_complex q = 2.0 * (p + 1.0);
}
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída
Definuje rozhraní objektu jako množinu předepsaných
virtuálních funkcí
class GraphicObject {
public:
virtual ~GraphicObject(); // každá abstraktní třída má mít v.d.
virtual void paint() = 0; // čistě virtuální funkce
virtual void move( int dx, int dy) = 0; // čistě virtuální funkce
};
Příklad: dědičnost a virtuální funkce
ISA hierarchie
Osoba
Student
Zaměstnanec
Učitel
• Matikář
• Fyzikář
• Chemikář
Ředitel
Ostatní
• Školník
• Kuchař
Které
abstraktní třídy potřebujeme?
Ty, jejichž specifické rozhraní někdo potřebuje
• Třída „Ostatní“ není potřeba
Příklad: dědičnost a virtuální funkce
Virtuální funkce a schopnosti tříd
Osoba (dá se evakuovat)
Student (platí školné)
Zaměstnanec (přijímá výplatu)
Učitel (umí uspořádat třídní schůzku)
• Matikář (umí učit Pythagorovu větu)
• Fyzikář (umí učit Archimedův zákon)
• Chemikář (umí předvést výbuch)
Ředitel (umí se podepsat)
Školník (umí odemykat)
Kuchař (umí počítat knedlíky)
Příklad: dědičnost a virtuální funkce
Násobná dědičnost
Absolvent učitelství na MFF umí
• uspořádat třídní schůzku
• učit Pythagorovu větu
• učit Archimedův zákon
Implementuje rozhraní Učitel, Matikář, Fyzikář
• Má být potomkem všech těchto tříd
class Matfyzak : virtual public Ucitel,
virtual public Matikar, virtual public Fyzikar
{
virtual void tridni_schuzka() { ... }
virtual void pythagorova_veta() { ... }
virtual void archimeduv_zakon() { ... }
...
}
Příklad: dědičnost a virtuální funkce
Násobná dědičnost
class Matfyzak : virtual public Ucitel,
virtual public Matikar, virtual public Fyzikar
{
virtual void tridni_schuzka() { ... }
virtual void pythagorova_veta() { ... }
virtual void archimeduv_zakon() { ... }
...
}
•
na třídu vedou odkazy z různých míst:
class Trida { ... Ucitel * tridni; ... }
class III : public Trida { ... Matikar * matikar; ... }
class IV : public Trida { ... Fyzikar * fyzikar; ... }
kdo z nich je vlastník ?
• Nikdo.
Jednoznačným vlastníkem je seznam zaměstnanců.
Příklad: dědičnost a virtuální funkce
class Matfyzak :
public Zamestnanec,
virtual public Ucitel,
virtual public Matikar,
virtual public Fyzikar
{
virtual void evakuace() { ... }
virtual void vyplata() { ... }
virtual void tridni_schuzka() { ... }
virtual void pythagorova_veta() { ... }
virtual void archimeduv_zakon() { ... }
}
class Trida { ... Ucitel * tridni; ... }
class III : public Trida { ... Matikar * matikar; ... }
class IV : public Trida { ... Fyzikar * fyzikar; ... }
class Skola {
vector< Zamestnanec *> zamestnanci;
vector< Trida *> tridy;
};
Příklad: dědičnost a virtuální funkce
ISA hierarchie
Osoba
Student
Zaměstnanec
Ředitel
Školník
Kuchař
Potřebujeme
třídu Osoba?
Máme nějaký seznam osob?
Další rozhraní
Učitel
Matikář
Fyzikář
Chemikář
Má
být Matikář odvozen z Učitele?
Mají Matikář a Fyzikář něco společného?
Příklad přesněji: Abstraktní třídy v ISA hierarchii
class Osoba {
public:
virtual ~Osoba() {}
virtual void evakuace() = 0;
protected:
Osoba() {}
private:
Osoba( const Osoba &);
Osoba & operator=( const Osoba &);
};
class Zamestnanec : public Osoba {
public:
virtual void vyplata() = 0;
};
Příklad přesněji: Abstraktní třídy pro rozhraní
class Ucitel {
public:
virtual void tridni_schuzka() = 0;
protected:
virtual ~Osoba() {}
Ucitel() {}
private:
Ucitel( const Ucitel &);
Ucitel & operator=( const Ucitel &);
};
class Matikar : public Ucitel {
public:
virtual void pythagorova_veta() = 0;
};
Příklad přesněji: Konkrétní třída
class Matfyzak :
public Zamestnanec,
virtual public Ucitel,
virtual public Matikar,
virtual public Fyzikar
{
public:
Matfyzak( const string & j ) : jmeno_( j) {}
private:
virtual void evakuace();
virtual void vyplata();
virtual void tridni_schuzka();
virtual void pythagorova_veta();
virtual void archimeduv_zakon();
string jmeno_;
};
void Matfyzak::evakuace() { ... }
...
Příklad přesněji: Kontejner odkazů
class Zamestnanci
{
public:
~Zamestnanci();
private:
typedef vector< Zamestnanec *> my_vector;
my_vector v;
};
Zamestnanci::~Zamestnanci()
{
for ( my_vector::iterator it = v.begin(); it != v.end(); ++it)
delete * it;
}
class Skola {
...
private:
Zamestnanci zamestnanci;
};
Příklad přesněji: Kontejner odkazů na rozhraní
class Ucitele
{
public:
Matikar * najdi_matikare() const;
private:
typedef vector< Ucitel *> my_vector;
my_vector v;
};
Matikar * Ucitele::najdi_matikare() const
{
for ( my_vector::iterator it = v.begin(); it != v.end(); ++it)
{
Matikar * p = dynamic_cast< Matikar *>( * it);
if ( p )
return p;
}
return 0;
}
Základní datové typy
Co umí hardware
Datové typy podporované procesorem
Celá čísla o 8/16/32/64 bitech
Několik formátů čísel s pohyblivou čárkou
Co umí hardware
Operace podporované procesorem
Celočíselná aritmetika a bitové operace
Operace určuje význam bitů
• Bez znaménka = mod 2N
• Se znaménkem = dvojkový doplněk
Aritmetika
s pohyblivou čárkou (vč. konverzí)
Čtení z paměti, zápis do paměti
Přesouvá pouze podporované datové typy
Adresou je celé číslo o 32 resp. 64 bitech
• Konstanta (součástí instrukce)
• Registr + konstanta
• Registr
Zarovnání: Adresa dělitelná velikostí přesouvaných dat (v bajtech)
• Vždy výhodné, někdy nutné
Překlad adres: Zadaná adresa je virtuální
Elementární typy v C++
Datové typy podporované procesorem
Celá čísla o 8/16/32/64 bitech
8: bool, char, signed char, unsigned char
16: short, unsigned short
16 nebo 32: wchar_t
32: int, unsigned int, long, unsigned long, výčtové typy
64: [C++11] long long, unsigned long long
32 nebo 64: ukazatel (T *), reference (T &), std::size_t, std::ptrdiff_t
Počty bitů nejsou stanoveny normou
Několik
formátů čísel s pohyblivou čárkou
float, double, long double
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
• + - * / % ~ << >> & ^ | < > <= >= == !=
• Konverze int na long long apod.
Typy argumentů určují počet bitů a přítomnost znaménka
Typy argumentů určují typ výsledku (delší vyhrává, nejméně int)
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Realizováno celočíselnou aritmetikou procesoru
+ - < > <= >= == !=
Ukazatel +/- číslo
• Posun ukazatele mezi prvky pole
• Překladač doplní vynásobení čísla velikostí typu
Ukazatel – ukazatel
• Vzdálenost mezi prvky pole
• Překladač doplní vydělení výsledku velikostí typu
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
• + - * / < > <= >= == !=
• Konverze int na double apod.
Typy argumentů určují typ výsledku (delší vyhrává, nejméně double)
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Realizováno podmíněnými skoky nebo bitovými operacemi
! && || ? :
Zaručeno zkrácené vyhodnocování
Přístup do paměti
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Ke globálním proměnným (konstantní adresa)
K lokálním proměnným (vrchol zásobníku + konstanta)
Dereference ukazatele (*)
• Odpovídá operaci čtení nebo zápisu (podle kontextu)
• V některých kontextech pouze formální operace
K prvkům třídy (konstantní posunutí)
K prvkům pole (viz ukazatelová aritmetika)
Formální operace
Elementární operace v C++
Celočíselná aritmetika a bitové operace
Ukazatelová aritmetika
Aritmetika v pohyblivé čárce
Booleovská algebra a podmíněné výrazy
Přístup do paměti
Formální operace
• Reference tj. získání adresy (&)
• Dereference (*) v kontextu předání odkazem
• Konverze T * na U *
• Konverze signed na unsigned apod
Většinou pouze změna v záznamech překladače
• Některé konverze T * na U * mohou vyžadovat operaci
přičtení/odečtení konstanty
main
Start programu
Start programu z pohledu OS
„Někdo“ (shell,...) požádá OS o spuštění programu
• Jméno spustitelného souboru
• Parametry (MS: vcelku, unix: po částech)
OS (loader)
• vytvoří proces, vyhradí základní kvantum paměti
• načte instrukce (a inicializační data) ze souboru do paměti
• doplní referencované DLL, pospojuje odkazy a relokuje
• oživí proces na definovaném místě
Proces běží a prostřednictvím systémových volání žádá o
• interakci s okolím (soubory, uživatelská rozhraní, komunikace,...)
• přidělení další paměti, vytvoření dalších vláken, ...
• ukončení (s návratovým kódem)
Proces je zlikvidován (na vlastní žádost nebo po pádu), zabit, ...
• nadřazenému procesu je předán návratový kód
• význam návratového kódu je závislý na konvencích
Start programu
Start programu z pohledu C++ knihoven
OS (loader)
• oživí proces na definovaném místě v kódu standardní knihovny
Knihovní kód
• požádá OS o první kvantum paměti pro dynamickou alokaci
• inicializuje struktury dynamické alokace
• naváže standardní vstupy a výstupy na OS
• inicializuje všechny globální proměnné
• zkonvertuje parametry programu
• zavolá main
• uklidí globální proměnné
• zavře všechny otevřené soubory
• požádá o ukončení s návratovým kódem vráceným z main
Proces je zlikvidován
Funkce main
#include <vector>
#include <string>
#include <iostream>
int main( int argc, char * * argv)
{
std::vector<std::string> arg( argv, argv + argc);
if ( arg.size() > 1 && arg[1] == "--help" )
{
std::cout << "Usage: myprog [OPTION]... [FILE]..." << std::endl;
return -1;
}
// ...
return 0;
}
Kontejnery počítají pozice od 0 do size()-1
arg[0] je jméno souboru spouštěného programu
arg[1] je první parametr
Podmíněný překlad
V C++ lze programovat tak, aby se program choval stejně na všech
platformách (hardware, překladačích, operačních systémech)
Pokud se má program chovat jinak, lze použít direktivy #if...
int main( int argv, char * * argc)
{
std::vector<std::string> arg( argv, argv + argc);
#ifdef unix
if ( arg.size() > 1 && arg[1] == "--help" )
#else
if ( arg.size() > 1 && arg[1] == "/HELP" )
#endif
{
std::cout << "Usage: myprog [OPTION]... [FILE]..." << std::endl;
return -1;
}
// ...
return 0;
}
Ladění
Makro
assert
Pokud podmínka neplatí,
vypíše pozici ve zdrojovém textu a ukončí program
Některé překladače (MSVC) v některých režimech (Release) mají
makro assert prázdné
• Nepoužívat assert jako if
#include <cassert>
int main( int argv, char * * argc)
{
std::vector<std::string> arg( argv, argv + argc);
assert( arg.size() > 0 );
// ...
return 0;
}
Preprocesor
Preprocesor
je dědictví jazyka C z roku 1970
• #include
• #define/#ifndef
• #if...
• assert
Umí ještě řadu dalších věcí – ty se ale dají v C++ udělat lépe
Paměť procesu
Organizace paměti procesu
IP
R0
R1
...
Kódový
segment
Datový segment
Heap
Zásobník (stack segment)
SP
Segmenty (vyjma zásobníku)
nemusejí být souvislé
• Dynamicky-linkované
knihovny sdílené mezi
procesy
• Postupná alokace heapu
Organizace paměti procesu
IP
R0
R1
...
SP
Kódový
segment
Připraven kompilátorem –
součást spustitelného souboru
• Kód uživatelských i knihovních
funkcí
• Obvykle chráněn proti zápisu
Datový
segment
Heap
Zásobník
(stack segment)
Organizace paměti procesu
IP
R0
R1
...
SP
Kódový
segment
Datový segment
Připraven kompilátorem –
součást spustitelného souboru
• Explicitně nebo implicitně
(nulami) inicializované globální
proměnné
• Řetězcové konstanty
• Data knihoven
• Pomocná data generovaná
kompilátorem
Heap
Zásobník
(stack segment)
Organizace paměti procesu
IP
R0
R1
...
SP
Kódový
segment
Datový segment
Heap
Vytvářen startovacím modulem
knihoven
• Neinicializovaná dynamicky
alokovaná data
• malloc/free
• C++: new/delete
• Obsazené bloky různé velikosti
+ seznam volných bloků
• Knihovny mohou též požádat
OS o zvětšení segmentu
Zásobník
(stack segment)
Organizace paměti procesu
IP
R0
R1
...
SP
Kódový
segment
Datový segment
Heap
Zásobník (stack segment)
Připraven op. systémem,
knihovny mohou požádat OS o
zvětšení
• Explicitně inicializované nebo
neinicializované lokální
proměnné
• Pomocné proměnné
generované kompilátorem
• Návratové adresy
• Další pomocná data
Vícevláknové aplikace mají více
zásobníků
Organizace paměti vícevláknového procesu
thread 1
IP
R0
R1
...
Vlákno
z pohledu OS
IP – Ukazatel instrukcí
SP – Ukazatel zásobníku
Další registry procesoru
(Identifikátor vlákna)
SP
thread 2
Paměťový
prostor je společný
IP
Vlákno
R0
R1
...
SP
v paměťovém prostoru
Zásobník
Thread-local storage
• Na dně zásobníku, nebo
• lokalizováno dle id vlákna