Programování v C++
NPRG041 Programování v C++ - 2014/2015 David Bednárek
1
Zápočty
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.2015 Doladění a odevzdání do 20.5.2015 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
NPRG041 Programování v C++ - 2014/2015 David Bednárek
2
Zkouška
Zkouška bude provedena formou abc-testu
Vlastnosti a pravidla jazyka C++ Používání knihoven C++ (kontejnery, algoritmy) Typické konstrukce objektového programování Generické 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
Course credits
Conditions
Exams
Passing practical programming tests
in lab, approx. 3 hours, common sessions for all groups - registration in SIS 1st attempts - 2nd half of January 2nd attempts - 1st half of February 3rd attempts - April
Creating an individual project
abc-tests January to February - registration in SIS
Agreement on project assignment - until end of November Beta version until March 31, 2015 Final version including documentation until May 20, 2015
Reasonable participation in labs Homework assignments
Conditions may be individually adjusted: contact your lab teacher during October
Erasmus students may need dates and deadlines sooner
NPRG041 Programování v C++ - 2014/2015 David Bednárek
5
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
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 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
inspirace
BCPL
B
C
Unix
(Cambridge 1966)
(Bell Labs. 1969)
(Bell Labs. 1971)
1973
nadmnožina téměř nadmnožina významná změna
K&R C
C with classes
(Kernigan & Ritchie 1978)
Objective-C (Cox & Love 1981)
(Stroustrup 1979)
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 C++11
Scott Meyers: Overview of the New C++ (C++11)
Bjarne Stroustrup: The C++ Programming Language - Fourth Edition
Addison-Wesley. ISBN 978-0321563842. May 2013 Učebnice celého C++ Zatím jediná kompletní učebnice obsahující C++11
Stanley B. Lippman, Josée Lajoie, Barbara E. Moo: C++ Primer (5th Edition)
360 slajdů z přednášek Vysvětluje motivaci k novým vlastnostem
Addison-Wesley. ISBN 978-0321714114. August 2012
http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list
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)
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
Spolupráce s hardware
C++ nechystá na programátora nepříjemná překvapení (garbage collection etc.) Embedded assembler, spojování s jinými jazyky
Spolupráce s OS
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
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++
Proč je Java/C# pomalejší?
Java/C# nutí k dynamické alokaci vždy a všude
V C++ lze dynamickou alokaci používat pouze v nezbytných případech
Garbage collection je pomalejší než explicitní dealokace (delete)
GC dealokuje pozdě - problémy s využitím cache GC je ale rychlejší než chytré ukazatele (shared_ptr)
Programy psané v C++ stylem Java/C# jsou pomalejší než originál
Chybí pokročilé metody optimalizace v překladačích
Vektorizace, transformace cyklů, ... Překladače nemají čas (JIT), jejich autoři motivaci Existují i situace, kdy je Java/C# rychlejší
datové struktury proměnlivé velikosti polymorfní datové struktury objekty s nepravidelnou dobou života
Překladače Javy/C# mají jednodušší úlohu při analýze kódu
Je C++ pomalejší než C
Ne, pokud v něm neprogramujete jako v Javě/C#
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í
Aplikace skládané z mnoha cizích součástí
V C++ neexistuje široce uznávaný styl psaní komponent Standard C++ nedostatečně zprostředkovává služby OS, Internetu atd.
Situace v C++11 a C++14 je však daleko lepší než dříve
Cizí knihovny obvykle doplňují chybějící části vlastní tvorbou
Přímé použití je obtížné a nebezpečné
Různé implementace chybějících částí mohou být v konfliktu
Pokud je ale zároveň zapotřebí výkon, nic jiného než C++ nezbývá
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
Porozumíte-li tomu, jak funguje C++, budete lépe rozumět
Programování OS, databází, překladačů Vědecké výpočty vyžadující výkon Hry, robotika, vestavěné systémy... Údržba rozsáhlých a historických softwarových systémů 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
Softwarové inženýrství a C++
NPRG041 Programování v C++ - 2014/2015 David Bednárek
23
Co je to programování
Algorithms + Data Structures = Programs
Niklaus Wirth, 1976
NPRG041 Programování v C++ - 2014/2015 David Bednárek
24
Co je to programování
Algorithms + Data Structures = Programs
Niklaus Wirth, 1976
NPRG041 Programování v C++ - 2014/2015 David Bednárek
25
Co je to programování
Čím proslul Henry Ford?
Vynalezl automobil?
NPRG041 Programování v C++ - 2014/2015 David Bednárek
26
Co je to programování
Čím proslul Henry Ford?
Vynalezl automobil?
Ne. Karl Benz 1885.
Byl prvním výrobcem automobilů?
NPRG041 Programování v C++ - 2014/2015 David Bednárek
27
Co je to programování
Čím proslul Henry Ford?
Vynalezl automobil?
Byl prvním výrobcem automobilů?
Ne. Karl Benz 1885. Ne. Panhard et Levassor 1887.
Dokázal při výrobě automobilů využít pracovní sílu lidí, kteří by sami automobil postavit nedokázali.
Úkolem dobrého programátora je vytvořit kód, který dokážou používat i horší programátoři
V C++ je to dvojnásobně důležité
NPRG041 Programování v C++ - 2014/2015 David Bednárek
28
Co je to programování Automobil
První automobil
Karl Benz 1885
Pásová výroba automobilů
Software
Henry Ford 1913
NPRG041 Programování v C++ - 2014/2015 David Bednárek
První programovatelný počítač
EDSAC 1949
Systematická výroba software
???
29
Co je to programování Automobil
První automobil
Software
Karl Benz 1885
Pásová výroba automobilů
První programovatelný počítač
EDSAC 1949
Systematická výroba software
Henry Ford 1913
???
EU27(2010, včetně výrobců částí): 2 172 000 zaměstnanců 95 269 000 000 EUR mzdy
EU27(2010, včetně IT služeb): 481 000 zaměstnanců 15 783 000 000 EUR mzdy
NPRG041 Programování v C++ - 2014/2015 David Bednárek
30
Co je to programování Automobil
První automobil
Software
Karl Benz 1885
Pásová výroba automobilů
Henry Ford 1913
Kdyby byly běžné automobily stejně spolehlivé jako běžný software, byli bychom dnes všichni mrtví
NPRG041 Programování v C++ - 2014/2015 David Bednárek
První programovatelný počítač
EDSAC 1949
Systematická výroba software
???
31
Co je to programování Automobil
První automobil
Software
Karl Benz 1885
Pásová výroba automobilů
První programovatelný počítač
EDSAC 1949
Systematická výroba software
Henry Ford 1913
???
Kdyby byly běžné automobily stejně spolehlivé jako běžný software, byli bychom dnes všichni mrtví
2010: Každý automobil obsahuje nejméně 30 vestavěných počítačů
NPRG041 Programování v C++ - 2014/2015 David Bednárek
Většina programována v C/C++ Spolehlivý software existuje!
32
Co je to programování
Každý program se dříve či později stane kolektivním dílem...
...nebo zmizí jako neúspěšný
Každý programátor by měl počítat s tím, že jeho dílo bude používat někdo cizí
Žádná překvapení, žádné exhibice geniality Dodržování konvencí, analogie dobře známých rozhraní
NPRG041 Programování v C++ - 2014/2015 David Bednárek
33
Žádná překvapení
NPRG041 Programování v C++ - 2014/2015 David Bednárek
34
Co je to programování
Algorithms + Data Structures + Best Practices = Programs
NPRG041 Programování v C++ - 2014/2015 David Bednárek
35
Hello, World!
NPRG041 Programování v C++ - 2014/2015 David Bednárek
36
Hello, World! #include
Vstupní bod programu
int main( int argc, char * * argv)
{
std::cout << "Hello, world!" << std::endl;
Dědictví jazyka C
Globální funkce main
Parametry programu
Z příkazové řádky
return 0;
}
globální proměnná
<< - výstup do streamu
Ukazatel na ukazatel Logicky pole polí
std - namespace knihoven cout - standardní výstup
Děleno na kousky
Archaické datové typy
Žádné třídy ani metody
přetížený operátor
endl - oddělovač řádek
globální funkce (!)
Hello, World! #include
Program entry point
int main( int argc, char * * argv)
{
std::cout << "Hello, world!" << std::endl;
Heritage of the C language
Global function "main"
main function arguments
Command-line arguments
return 0;
}
global variable
<< - stream output
Pointer to pointer to char Logically: array of strings
std - standard library namespace cout - standard output
Split to pieces
Archaic data types
No classes or namespaces
overloaded operator
endl - line delimiter
global function (trick!)
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
// world.cpp
#include "world.hpp"
#include "world.hpp" #include
int main( int argc, char * * argv) {
void world() world();
{
return 0;
std::cout << "Hello, world!" << std::endl;
} }
Hello, World! More than one module Module
interface described in a
file .hpp - "header" file
// world.hpp #ifndef WORLD_HPP_ #define WORLD_HPP_ void world(); #endif
The
defining and all the using modules shall "include" the file Text-based inclusion
// main.cpp
// world.cpp
#include "world.hpp"
#include "world.hpp" #include
int main( int argc, char * * argv) {
void world() world();
{
return 0;
std::cout << "Hello, world!" << std::endl;
} }
Hello, World! // world.hpp #ifndef WORLD_HPP_ #define WORLD_HPP_ #include #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 void world( const t_arg & arg) { if ( arg.empty() ) { std::cout << "Hello, world!" << std::endl; } }
Compilation and linking
NPRG041 Programování v C++ - 2014/2015 David Bednárek
42
Single-module programs - static linking // iostream // iostream
iostream.obj
#include #include namespace std { namespace std { extern ofstream extern ofstream cout, cerr; cout, cerr; }; };
// myprog.cpp #include int main() { std::cout << "Hello, world!\n"; }
msvcrt.lib
Compiler
myprog.obj
Linker
myprog.exe
Multiple-module programs Library include files
Library modules Library
.obj .lib
User include files .hpp
User modules .cpp
Compiler
Compiled
.obj
Linker
Runnable .exe
Module interfaces and linking
myprog.cpp #include "bee.hpp" int main(int,char**) { return B( 7); }
myprog.obj
Compiler
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
Compiler
0000: 10010110 00100010 10110001 export B(int)
make Library include files
Library modules Library
.obj .lib
User include files .hpp
User modules .cpp
Compiler
Compiled
Make
makefile
.obj
Linker
Runnable .exe
Integrated environment Library include files
Library modules Library
.obj .lib
User include files .hpp
User modules .cpp
Compiler
Compiled
Editor
.obj
Linker
Runnable .exe
Debugger
project file
Static libraries Std. library include files
Std. library modules .obj Std. library
.lib
User include files .hpp
User modules .cpp
Compiler
Compiled
.obj
Linker
Runnable .exe
Library as distributed (binary) Library
Library
.hpp
Library
.cpp
Library as distributed (source)
Compiler
Compiled
.obj
.lib
Librarian
Dynamic libraries (Microsoft) Std. library include files
Std. library modules .obj Std. library
.lib
User include files .hpp
User modules .cpp
Compiler
Compiled
.obj
Linker
Runnable .exe
Library as distributed (binary) Library .hpp
Library
.cpp
Library as distributed (source)
Library
Stub library .lib
Compiler
Compiled
.obj
Librarian
.dll
Dynamic libraries (Linux) Std. library include files
Std. library modules
.o
Std. library
.a
User include files .hpp
User modules .cpp
Compiler
Compiled
.o
Linker
Runnable
Library as distributed (binary) Library
Library .hpp
Library
.cpp
Library as distributed (source)
.so
Compiler
Compiled
.o
Librarian
Základní pravidla pro .cpp/.hpp
.hpp – "hlavičkové soubory"
Ochrana proti opakovanému include
#ifndef myfile_hpp_ #define myfile_hpp_ /* … */ #endif
Vkládají se direktivou s uvozovkami
#include "myfile.hpp"
Direktiva s úhlovými závorkami je určena pro (standardní) knihovny
#include
Direktivy #include používat vždy na začátku souboru (po ifndef+define) Soubor musí být samostatný: vše, co potřebuje, inkluduje sám
.cpp - "moduly"
Zařazení do programu pomocí projektu/makefile
Nikdy nevkládat pomocí #include
NPRG041 Programování v C++ - 2014/2015 David Bednárek
51
.cpp/.hpp - best practices
.hpp – "header files"
Protect against repeated inclusion
#ifndef myfile_hpp_ #define myfile_hpp_ /* … */ #endif
Use include directive with double-quotes
#include "myfile.hpp"
Angle-bracket version is dedicated to standard libraries
#include
Use #include only in the beginning of files (after ifndef+define) Make header files independent: it must include everything what it needs
.cpp - "modules"
Incorporated to the program using a project/makefile
Never include using #include
NPRG041 Programování v C++ - 2014/2015 David Bednárek
52
Základní pravidla pro .cpp/.hpp
.hpp – "hlavičkové soubory"
Deklarace/definice typů a tříd Implementace funkcí a metod malého rozsahu
Funkce a metody mimo třídy označeny "inline"
inline int max( int a, int b) { return a > b ? a : b; }
Hlavičky globálních funkcí velkého rozsahu
int big_function( int a, int b);
Externí deklarace globálních proměnných
extern int x;
Veškerý generický kód (šablony tříd a funkcí)
Lepší je použít singleton Jinak jej překladače neumí použít
.cpp - "moduly"
Implementace funkcí a metod velkého rozsahu
Včetně "main"
Definice globálních a statických proměnných
Včetně jejich inicializace int x = 729;
NPRG041 Programování v C++ - 2014/2015 David Bednárek
53
.cpp/.hpp - best practices
.hpp – "header files"
Declaration/definitions of types and classes Implementation of small functions
Outside classes, functions must be marked "inline"
inline int max( int a, int b) { return a > b ? a : b; }
Headers of large functions
int big_function( int a, int b);
Extern declarations of global variables
extern int x;
Any generic code (class/function templates)
Consider using singletons instead of global variables The compiler cannot use the generic code when hidden in a .cpp
.cpp - "modules"
Implementation of large functions
Including "main"
Definitions of global variables and static class data members
May int x = 729;
contain initialization
NPRG041 Programování v C++ - 2014/2015 David Bednárek
54
Vzájemné závislosti v kódu
Všechny identifikátory musejí být deklarovány před prvním použitím
Překladač čte zdrojové soubory jedním průchodem Výjimka: Těla metod jsou analyzována až na konci třídy
Zevnitř metod lze používat položky deklarované později
Pro generický kód platí složitější, ale obdobná pravidla
Cyklické závislosti je nutné rozbít rozdělením na deklaraci a definici
class one; class two { std::shared_ptr< one> p_; }; class one : public two {};
Nedefinovaná deklarovaná třída má omezené možnosti použití
Nelze použít jako předek, typ položky/proměnné, new, sizeof apod.
NPRG041 Programování v C++ - 2014/2015 David Bednárek
55
Dependences in code
All identifiers must be declared prior to first use
Compilers read the code in one pass Exception: Member-function bodies are analyzed at the end of the class
A member function body may use other members declared later
Generic code involves similar but more elaborate rules
Cyclic dependences must be broken using declaration + definition
class one;
// declaration
class two { std::shared_ptr< one> p_; }; class one : public two
// definition
{};
Declared class is of limited use before definition
Cannot be used as base class, data-member type, in new, sizeof etc.
NPRG041 Programování v C++ - 2014/2015 David Bednárek
56
Deklarace a definice
Declarations and definitions
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
Definice
Zápis, který určuje všechny vlastnosti věci
V některých případech je k tomu zapotřebí i definice
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
Declarations and definitions
Declaration
A construct to declare the existence (of a class/variable/function/...)
Identifier Some basic properties Ensures that (some) references to the identifier may be compiled
Definition
A construct to completely define (a class/variable/function/...)
Some references may require definition
Class contents, variable initialization, function implementation Ensures that the compiler may generate runtime representation
Every definition is a declaration
Declarations allow (limited) use of identifiers without definition
Independent compilation of modules Solving cyclic dependences Minimizing the amount of code that requires (re-)compilation
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
Declarations and definitions
One-definition rule #1:
One translation unit...
(module, i.e. one .cpp file and the .hpp files included from it)
... may contain at most one definition of any item
One-definition rule #2:
Program...
(i.e. the .exe file including the linked .dll files)
... may contain at most one definition of a variable or a non-inline function
Definitions of classes, types or inline functions may be contained more than once (due to inclusion of the same .hpp file in different modules)
If these definitions are not identical, undefined behavior will occur Beware of version mismatch between headers and libraries
Diagnostics is usually poor (by linker)
Class and type definitions Declaration
Definition
Class
class A;
class A { ... };
Structure (almost equivalent to class)
struct A;
struct A { ... };
Union (unusable in C++)
union A;
union A { ... };
Named type
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;
C++11 style of named types
using A2 = A; using AFP2 = A (*)();
Function declarations and definitions non-inline
Deklarace (.hpp nebo .cpp)
Definice (.cpp)
Global function
int f( int, int);
int f( int p, int q) { return p + q;}
Static member function
class A { static int f( int p); };
int A::f( int p) { return p + 1; }
Nonstatic member function
class A { int f( int p); };
int A::f( int p) { return p + 1; }
Virtual member function
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; }
Global inline function Nonstatic inline member fnc (a) Nonstatic inline member fnc (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;} };
Variable declarations and definitions Declaration
Definition
Global variable
extern int x, y, z;
int x; int y = 729; int z(729); int u{729}; C++11
Static member variable
class A { static int x, y, z; };
int A::x; int A::y = 729; int A::z( 729); int A::z{ 729}; C++11
Constant member
class A { static const int x = 729; };
Static local variable
void f() static static static }
Nonstatic member variable
class A { int x, y; };
Nonstatic local variable
void f() { int x; int y = 7, z( 7); int u{ 7}; C++11 };
{ int x; int y = 7, z( 7); int u{ 7}; C++11
Storage classes
Where data reside...
Static storage
Global, static member, static local variables, string constants
Allocated by compiler/linker/loader (listed in .obj/.dll/.exe)
Thread-local storage
One instance per thread
Automatic storage (stack or register)
Local variables, parameters, anonymous objects, temporaries
C++11
Variables marked "thread_local"
One instance per process
One instance per function invocation (execution of defining statement)
Placement by compiler, space allocated by compiler-generated instructions
Dynamic allocation
new/delete operators
The programmer is responsible for deallocation, no garbage collection
Allocation by library routines
Significantly slower than other storage classes
NPRG041 Programování v C++ - 2014/2015 David Bednárek
66
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
Kódový segment
R0 R1 ...
SP
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 ...
Kódový segment Datový segment
SP
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 ...
Kódový segment Datový segment Heap
SP
Vytvářen startovacím modulem knihoven
Neinicializovaná dynamicky alokovaná data C: 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
Vlákno z pohledu OS
R0 R1 ...
SP
thread 2
IP – Ukazatel instrukcí SP – Ukazatel zásobníku Další registry procesoru (Identifikátor vlákna)
Paměťový prostor je společný
Vlákno v paměťovém prostoru
IP
R0 R1 ...
Zásobník Thread-local storage
SP
Na dně zásobníku, nebo lokalizováno dle id vlákna
Storage classes
Where data reside...
Static storage
T x;
// global variable
Thread-local storage
thread_local T x; // global variable
Automatic storage (stack or register)
void f() { T x;
// local variable
}
Dynamic allocation
void f() { T * p = new T; // ... delete p; }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
73
Dynamic allocation
Use smart pointers instead of raw (T *) pointers
#include <memory>
one owner (pointer cannot be copied)
no runtime cost (compared to T *)
void f() { std::unique_ptr< T> p = new T; std::unique_ptr< T> q = std::move( p);
// pointer moved to q, p becomes nullptr
}
shared ownership
runtime cost of reference counting
void f() { std::shared_ptr< T> p = std::make_shared< T>(); std::shared_ptr< T> q = p;
// invokes new
// pointer copied to q
}
Memory is deallocated when the last owner disappears
Destructor of (or assignment to) the smart pointer invokes delete when required Reference counting cannot deallocate cyclic structures
NPRG041 Programování v C++ - 2014/2015 David Bednárek
74
Dynamic allocation
Dynamic allocation is slow
Use dynamic allocation only when necessary
compared to static/automatic storage the reason is cache behavior, not the allocation itself variable-sized or large arrays polymorphic containers (objects with inheritance) object lifetimes not corresponding to function invocations
Avoid data structures with individually allocated items
linked lists, binary trees, ...
std::list, std::map, ...
prefer B-trees (yes, also in memory) or hash tables avoiding is difficult - do it only if speed is important
This is how C++ programs may be made faster than C#/java
C#/java requires dynamic allocation of every class instance
NPRG041 Programování v C++ - 2014/2015 David Bednárek
75
Arrays Fixed size
Homogeneous
Polymorphic
static const std::size_t n = 3; std::array< T, n> a;
std::tuple< T1, T2, T3> a;
a[ 0] = /*...*/; a[ 1].f();
Variable size
std::size_t n = /*...*/; std::vector< T> a(n); a[ 0] = /*...*/; a[ 1].f();
std::get< 0>( a) = /*...*/; std::get< 1>( a).f(); std::vector< a.push_back( a.push_back( a.push_back(
std::unique_ptr< Tbase>> a; new T1); new T2); new T3);
a[ 1]->f();
NPRG041 Programování v C++ - 2014/2015 David Bednárek
76
Array layouts std::array< T, 3> T
T
T
std::vector< T>
T
T
std::tuple< T1, T2, T3> T1
T2
T3
std::vector< std::unique_ptr>
T
T1
NPRG041 Programování v C++ - 2014/2015 David Bednárek
T2
T3
77
Frequently used data types
Selected number types bool
false, true
char
character (ASCII, 8 bit)
std::wchar_t
character (Unicode, 16/32 bit)
int
signed integer (~32 bit)
unsigned
unsigned integer (~32 bit)
long long
extra large signed integer (~64 bit)
unsigned long long
extra large unsigned integer (~64 bit)
std::size_t
unsigned integer large enough for array sizes (32/64 bit)
double
"double precision" floating-point number (Intel: 64 bit)
long double
extended precision floating-point number (Intel: 80 bit)
std::complex<double>
complex number of double precision
Important non-number types std::string
string (containing char)
std::wstring
string (containing std::wchar_t)
std::istream
input stream (containing char)
std::wistream
input stream (containing std::wchar_t)
std::ostream
output stream (containing char)
std::wostream
output stream (containing std::wchar_t)
struct T { … }
structure (almost equivalent to class)
std::pair
pair of T1 and T2
std::tuple
k-tuple of various types
std::array
fixed-size array of T
std::vector
variable-size array of T
std::list
doubly linked list of T
std::map
ordered associative container of T indexed by K
std::multimap
ordered associative container with multiplicity of keys
std::unordered_map
hash table of T indexed by K
std::unordered_multimap
hash table with multiplicity of keys
Class
NPRG041 Programování v C++ - 2014/2015 David Bednárek
81
Class class X {
/*...*/ };
Class in C++ is an extremely powerful construct
Requires caution and conventions
Three degrees of usage
Other languages often have several less powerful constructs (class+interface)
Non-instantiated class - a pack of declarations (used in generic programming) Class with data members Class with inheritance and virtual functions (object-oriented programming)
class = struct
struct members are by default public
by convention used for simple or non-instantiated classes
class members are by default private
by convention used for large classes and OOP
Three degrees of classes Non-instantiated class
Class with data members
Classes with inheritance
class X {
class Y {
class U {
public:
public:
public: void f()
Y()
typedef int t;
{ f_(); }
: m_( 0)
static const int c = 1; static int f( int p)
{}
{ return p + 1; }
int get_m() const { return m_; }
};
void set_m( int m) { m_ = m; }
private:
virtual void f_() = 0; };
class V : public U { public:
private: int m_;
V() : m_( 0) {} private:
};
int m_; virtual void f_() { ++ m_; } };
NPRG041 Programování v C++ - 2014/2015 David Bednárek
83
Type and static members of classes class X {
public:
Type and static members...
class N { /*...*/ };
typedef unsigned long t;
static const t c = 1; static t f( t p)
{ return p + v_; }
private: static t v_;
// declaration of X::v_
};
X::t X::v_ = X::c;
// definition of X::v_
void f2() {
... are not bound to any class instance (object) Equivalent to global types/variables/functions
X::t a = 1; a = X::f( a); }
But referenced using qualified names (prefix X::) Encapsulation in a class avoids name clashes
NPRG041 Programování v C++ - 2014/2015 David Bednárek
Nested class definitions typedef definitions static member constants static member functions static member variables
But namespaces do it better
Some members may be private Class may be passed to a template 84
Uninstantiated classes vs. namespaces Uninstantiated class
Class definitions are intended for objects
Namespace members are always static
Static members must be explicitly marked
No objects can be made from namespaces
Class members may be public/protected/private
Functions/variables are not automatically inline/extern
Namespace
namespace X {
class X {
class N { /*...*/ };
public:
typedef unsigned long t;
class N { /*...*/ };
const t c = 1;
typedef unsigned long t;
inline t f( t p)
static const t c = 1; { return p + v; }
static t f( t p) extern t v;
// declaration of X::v
{ return p + v; } };
static t v;
// declaration of X::v
Namespace may be reopened
};
Class must be defined in one piece
Definitions of class members may be placed outside
X::t X::v = X::c;
Namespace may be split into several header files
Definitions of namespace members must reopen it
namespace X { t v = c;
// definition of X::v
// definition of X::v
};
void f2()
Namespace members can be made directly visible
{
"using namespace"
void f2()
X::t a = 1;
{
a = X::f( a);
X::t a = 1;
}
using namespace X;
A class may become a template argument
typedef some_generic_class< X> specific_class;
NPRG041 Programování v C++ - 2014/2015 David Bednárek
a = f( a); }
85
Class with data members class Y { public: Y() : m_( 0)
Class (i.e. type) may be instantiated (into objects)
Y v1;
{} int get_m() const
Using a variable of class type
{ return m_; }
This is NOT a reference!
Dynamically allocated
Held by a (smart) pointer
void set_m( int m)
std::unique_ptr< Y> p = new Y;
{ m_ = m; }
std::shared_ptr< Y> q =
private: int m_; };
std::make_shared< Y>();
Element of a larger type
typedef std::array< Y, 5> A;
class C1 { public: Y v; }; class C2 : public Y {}; NPRG041 Programování v C++ - 2014/2015 David Bednárek
Embedded into the larger type NO explicit instantiation by new! 86
Class with data members class Y { public: Y() : m_( 0)
{} int get_m() const
Class (i.e. type) may be instantiated (into objects)
Y v1; std::unique_ptr< Y> p = new Y;
{ return m_; } void set_m( int m)
{ m_ = m; } private:
Non-static data members constitute the object Non-static member functions are invoked on the object Object must be specified when referring to non-static members
v1.get_m() p->set_m(0)
int m_;
}; v1.m_
References from outside may be prohibited by "private"/"protected" // error
Only "const" methods may be called on const objects
const Y * pp = p.get(); // secondary pointer pp->set_m(0)
NPRG041 Programování v C++ - 2014/2015 David Bednárek
// error
87
Pointer vs. value
Forms of pointers in C++
References
T & const T &
Built in C++ Syntactically identical to values when used (r.a)
Raw pointers
T * const T *
Built in C/C++ Requires special operators to access the referenced value (*p, p->a) Pointer arithmetics allows to access adjacent values residing in arrays Manual allocation/deallocation
Smart pointers
std::shared_ptr< T> std::unique_ptr< T>
Class templates in standard C++ library Operators to access the referenced value same as with raw pointers (*p, p->a) Represents ownership - automatic deallocation on destruction of the last reference
Iterators
K::iterator K::const_iterator
Classes associated to every kind of container (K) in standard C++ library Operators to access the referenced value same as with raw pointers (*p, p->a) Pointer arithmetics allows to access adjacent values in the container
C#/Java vs. C++ Reference types (C#,Java) class T { public int a; } class test { static void f( T z) { z.a = 3; } static void g() { T x = new T(); // allocation
} }
Raw pointers (C++) class T { public: int a; }; void f( T * z) { z->a = 3; } void g() { T * x = new T; // allocation
x.a = 1;
x->a = 1;
T y = x; // second reference
T * y = x; // second pointer
y.a = 2; // x.a == 2
y->a = 2; // x->a == 2
f( x); // x.a == 3
f( x); // x->a == 3
// garbage collector will later // reclaim the memory when needed
delete x; // manual deallocation }
C#/Java vs. C++ Reference types (C#,Java) class T { public int a; } class test { static void f( T z) { z.a = 3; } static void g() { T x = new T(); // allocation
Smart pointers (C++) class T { public: int a; }; void f( T * z) { z->a = 3; } void g() { std::shared_ptr< T> x = std::make_shared< T>(); // allocation
x.a = 1; x->a = 1; T y = x; // second reference
std::shared_ptr< T> y = x; // second pointer
y.a = 2; // x.a == 2
y->a = 2; // x->a == 2
f( x); // x.a == 3
f( x); // x->a == 3
// garbage collector will later // reclaim the memory when needed
// automatic deallocation // when pointers are destructed
} }
}
C#/Java vs. C++ Reference types (C#,Java) class T { public int a; } class test { static void f( T z) { z.a = 3; } static void g() { T x = new T(); // allocation
References (C++) class T { public: int a; }; void f( T & z) { z.a = 3; } void g() { T x;
x.a = 1;
x.a = 1;
T y = x; // second reference
T & y = x; // a reference to the stack object
y.a = 2; // x.a == 2
y.a = 2; // x.a == 2
f( x); // x.a == 3
f( x); // x.a == 3
// garbage collector will later // reclaim the memory when needed } }
// automatic storage (stack)
// x is destructed on exit }
C#/Java vs. C++ Value types (C#) struct T { int a; } class test { static void f( T z) { z.a = 3; } static void g() { T x; // creation
Values (C++) class T { public: int a; }; void f( T z) { z.a = 3; } void g() { T x; // creation
x.a = 1;
x.a = 1;
T y = x; // a copy
T y = x; // a copy
y.a = 2; // x.a == 1
y.a = 2; // x.a == 1
f( x); // x.a == 1
f( x); // x.a == 1
// destruction on exit } }
// destruction on exit }
C#/Java vs. C++ Passing value types by reference (C#) struct T { int a; } class test { static void f( ref T z) { z.a = 3; } static void g() { T x; // creation
Passing by lvalue reference (C++) class T { public: int a; }; 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 }
C#/Java vs. C++ Passing reference types by reference (C#) class T { public int a; } class test { static void f( ref T z) { z = new T(); // allocation of another object } static void g() { T x = new T(); // allocation
Passing smart pointers by reference (C++) class T { public: int a; }; void f( std::unique_ptr & z) { z = new T; // allocation of another object // deallocation of the old object } void g() { std::unique_ptr< T> x = new T; // allocation
f( ref x); // x is now a different object
f( x); // *x is now a different object
// deallocation later by GC } }
// deallocation by destruction of x }
Pointer/reference conventions
Pointer/references
C++ allows several ways of passing links to objects
Technically, all the forms allow almost everything
smart pointers C-like pointers references
At least using dirty tricks to bypass language rules
By convention, the use of a specific form signalizes some intent
Conventions (and language rules) limits the way how the object is used Conventions help to avoid "what-if" questions
What if someone destroys the object I am dealing with? What if someone modifies the contents of the object unexpectedly? ...
Passing a pointer/reference in C++ - conventions What the recipient may do?
For how long?
What the others will do meanwhile?
std::unique_ptr
Modify the contents As required and destroy the object
Nothing
std::shared_ptr
Modify the contents As required
Read/modify the contents
T *
Modify the contents Until noticed to stop/by agreement
Read/modify the contents
const T *
Read the contents
Modify the contents
T &
Modify the contents During a call/statement
Nothing (usually)
const T &
Read the contents
Nothing (usually)
Until noticed to stop/by agreement
During a call/statement
Transferring unique ownership channel ch;
class packet { /*...*/ };
void send_hello()
class channel
{ std::unique_ptr< packet> p = new packet;
{
p->set_contents( "Hello, world!");
public:
ch.send( std::move( p));
void send( std::unique_ptr< packet> q);
// p is nullptr now }
bool empty() const;
std::unique_ptr< packet> receive();
void dump_channel() { while ( ! ch.empty() )
private:
{
/*...*/
std::unique_ptr< packet> m = ch.receive(); std::cout << m->get_contents();
};
// the packet is deallocated here } }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
99
Transferring unique ownership channel ch;
class packet { /*...*/ };
void send_hello()
class channel
{
{
std::unique_ptr< packet> p = new packet; p->set_contents( "Hello, world!"); ch.send( std::move( p));
public:
void send( std::unique_ptr< packet> q) { q_.push_back( std::move( q));
// p is nullptr now }
}
void dump_channel()
std::unique_ptr< packet> receive() {
{
std::unique_ptr< packet> r = std::move( q_.front());
while ( ! ch.empty() )
// remove the nullptr from the queue q_.pop_front();
{ std::unique_ptr< packet> m = ch.receive(); std::cout << m->get_contents(); // the packet is deallocated here } }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
return r; } private: std::deque< std::unique_ptr< packet>> q_; };
100
Shared ownership class sender {
class channel { /*...*/ };
public: sender( std::shared_ptr< channel> ch) : ch_( ch) {}
std::unique_ptr< sender> s; std::unique_ptr< recipient> r;
void send_hello() { /*...*/ ch_->send( /*...*/); } private:
void init() {
std::shared_ptr< channel> ch_;
std::shared_ptr< channel> ch = std::make_shared< channel>();
};
s.reset( new sender( ch));
r.reset( new recipient( ch));
class recipient { public:
}
recipient( std::shared_ptr< channel> ch) : ch_( ch) {}
void kill_sender()
void dump_channel()
{ s.reset(); }
{ /*...*/ = ch_->receive(); /*...*/ }
void kill_recipient()
private: std::shared_ptr< channel> ch_; }
{ r.reset(); }
The server and the recipient may be destroyed in any order
NPRG041 Programování v C++ - 2014/2015 David Bednárek
The last one will destroy the channel 101
Accessing without ownership transfer class sender {
class channel { /*...*/ };
public: sender( channel * ch) : ch_( ch) {} void send_hello()
std::unique_ptr< channel> ch; std::unique_ptr< sender> s; std::unique_ptr< recipient> r;
{ /*...*/ ch_->send( /*...*/); } private: channel * ch_;
void init() { ch.reset( new channel);
};
s.reset( new sender( ch.get())); r.reset( new recipient( ch.get()));
class recipient { public: recipient( channel * ch) : ch_( ch) {}
}
void shutdown() { s.reset();
void dump_channel()
r.reset();
{ /*...*/ = ch_->receive(); /*...*/ } private: channel * ch_; }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
ch.reset(); }
The server and the recipient must be destroyed before the destruction of the channel 102
Holding pointers to locally allocated objects class sender { public:
void do_it( sender &, receiver &); void do_it_all()
sender( channel * ch) : ch_( ch) {}
{
void send_hello()
channel ch;
{ /*...*/ ch_->send( /*...*/); }
sender s( & ch);
private:
recipient r( & ch);
channel * ch_; };
do_it( s, r); class recipient { public:
}
recipient( channel * ch) : ch_( ch) {}
The need to use "&" in constructor parameters warns of long life of the reference
void dump_channel()
{ /*...*/ = ch_->receive(); /*...*/ }
private: channel * ch_; }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
"&" - converts reference to pointer "*" - converts pointer to reference
Local variables are automatically destructed in the reverse order of construction 103
Class holding a reference class sender { public:
void do_it( sender &, receiver &); void do_it_all()
sender( channel & ch) : ch_( ch) {}
{
void send_hello()
channel ch;
{ /*...*/ ch_.send( /*...*/); }
sender s( ch);
private:
recipient r( ch);
channel & ch_; };
do_it( s, r); class recipient { public:
}
recipient( channel & ch) : ch_( ch) {} void dump_channel() { /*...*/ = ch_.receive(); /*...*/ } private:
s and r will hold the reference to ch for their lifetime
There is no warning of that!
If references are held by locally allocated objects, everything is OK
Destruction occurs in reverse order
channel & ch_; }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
104
ERROR: Passing a reference to local object out of its scope class sender { public:
std::unique_ptr< sender> s; std::unique_ptr< recipient> r;
sender( channel & ch) : ch_( ch) {} void send_hello()
void init()
{ /*...*/ ch_.send( /*...*/); }
{
private:
channel ch;
channel & ch_;
s.reset( new sender( ch));
};
r.reset( new recipient( ch)); class recipient {
}
public: recipient( channel & ch) : ch_( ch) {}
void dump_channel() { /*...*/ = ch_.receive(); /*...*/ } private: channel & ch_;
ch will die sooner than s and r
s and r will access invalid object Fatal crash sooner or later
Nothing warns of this behavior
Prefer pointers in this case
}
NPRG041 Programování v C++ - 2014/2015 David Bednárek
105
ERROR: Killing an object in use class sender {
std::unique_ptr< channel> ch;
public: sender( channel & ch) : ch_( ch) {} void send_hello()
void do_it() {
ch.reset( new channel);
{ /*...*/ ch_.send( /*...*/); } private:
sender s( ch.get());
channel & ch_;
recipient r( ch.get());
};
do_it( s, r);
ch.reset( new channel);
class recipient {
do_it( s, r);
public: recipient( channel & ch)
}
: ch_( ch) {} void dump_channel()
{ /*...*/ = ch_.receive(); /*...*/ } private:
ch is destructed before s and r
Fatal crash sooner or later
Rare programming practice
channel & ch_; } NPRG041 Programování v C++ - 2014/2015 David Bednárek
106
Allowing access temporarily channel ch;
class packet { void set_contents( const std::string & s);
void send_hello()
const std::string & get_contents() const;
{
/*...*/ std::unique_ptr< packet> p = new packet; p->set_contents( "Hello, world!");
};
ch.send( std::move( p));
get_contents returns a reference to data stored inside the packet
// p is nullptr now
}
How long the reference is valid?
void dump_channel() {
const prohibits modification
while ( ! ch.empty() )
Probably until modification/destruction of the packet It will last at least during the statement containing the call
{ std::unique_ptr< packet> m = ch.receive();
Provided there is no other action on the packet in the same statement
std::cout << m->get_contents();
set_contents receives a reference to data stored elsewhere
// the packet is deallocated here
} }
NPRG041 Programování v C++ - 2014/2015 David Bednárek
const prohibits modification the reference is valid throughout the call
107
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
Returning by reference Functions which compute their return values must NOT return by reference
the computed value usually differs from values of arguments the value of arguments must not be changed there is nothing that the reference might point to
Invalid idea #1: Local variable
Complex & add( const Complex & a, const Complex & b) { Complex r( a.Re + b.Re, a.Im + b.Im); return r; }
RUNTIME ERROR: r disappears during exit from the function
before the calling statement can read it
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 ?
Returning by reference Functions which compute their return values must NOT return by reference
the computed value usually differs from values of arguments the value of arguments must not be changed there is nothing that the reference might point to
Invalid idea #2: Dynamic allocation
Complex & add( const Complex & a, const Complex & b) { Complex * r = new Complex( a.Re + b.Re, a.Im + b.Im); return * r; }
PROBLEM: who will deallocate the object?
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));
Returning by reference Functions which compute their return values must NOT return by reference
the computed value usually differs from values of arguments the value of arguments must not be changed there is nothing that the reference might point to
Invalid idea #3: Global variable
Complex g; Complex & add( const Complex & a, const Complex & b) { g = Complex( a.Re + b.Re, a.Im + b.Im); return g; }
PROBLEM: the variable is shared
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);
Returning by reference Functions which compute their return values must
return by value
the computed value usually differs from values of arguments the value of arguments must not be changed there is nothing that a reference might point to
(The only) correct function interface:
Complex add( const Complex & a, const Complex & b) { Complex r( a.Re + b.Re, a.Im + b.Im); return r; }
This body may be shortened to (equivalent by definition):
return Complex( a.Re + b.Re, a.Im + b.Im);
Returning by reference Functions which
enable access to existing objects may return by reference
the object must survive the return from the function
Example:
template< typename T, std::size_t N> class array { public: T & at( std::size_t i) { return a_[ i]; } private: T a_[ N]; };
Returning by reference may allow modification of the returned object
array< int, 5> x; x.at( 1) = 2;
Returning by reference
Functions which enable access to existing objects may return by reference
Often there are two versions of such function
template< typename T, std::size_t N> class array { public:
Allowing modification of elements of a modifiable container
T & at( std::size_t i) { return a_[ i]; }
Read-only access to elements of a read-only container
const T & at( std::size_t i) const { return a_[ i]; }
private: T a_[ N]; }; void f( array< int, 5> & p, const array< int, 5> & q) {
}
p.at( 1) = p.at( 2);
// non-const version in BOTH cases
int x = q.at( 3);
// const version
Returning by reference
Functions which enable access to existing objects may return by reference
The object must survive the return from the function
template< typename T> class vector { public:
back returns the last element which will remain on the stack it may allow modification of the element
T & back(); const T & back() const;
this pop_back removes the last element from the stack and returns its value it must return by value - slow (and exception-unsafe)
T pop_back();
therefore, in standard library, the pop_back function returns nothing
void pop_back(); // ... };
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
Containers
Generic data structures
Based on arrays, linked lists, trees, or hash-tables
Store objects of given type (template parameter)
The container takes care of allocation/deallocation of the stored objects
All objects must be of the same type (defined by the template parameter)
New objects are inserted by copying/moving/constructing in place
Containers can not directly store polymorphic objects with inheritance Containers can not hold objects created outside them
Inserting/removing objects: Member functions of the container Reading/modifying objects: Iterators
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
Sequential containers
New objects are inserted in specified location
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
stack< T> - zásobník priority_queue< T> - prioritní fronta
queue< T> - fronta (maskovaná deque)
forward_list< T> - jednosměrně vázaný seznam list< T> - obousměrně vázaný seznam
STL
Sequential containers
New objects are inserted in specified location
array< T, N> - fixed-size array (no insertion/removal) vector< T> - array, fast insertion/removal at the back end
basic_string< T> - vektor s terminátorem
string = basic_string< char> wstring = basic_string< wchar_t>
deque< T> - fast insertion/removal at both ends
stack< T> - insertion/removal only at the top (back end) priority_queue< T> - priority queue (heap implemented in vector)
queue< T> - FIFO (insert to back, remove from front)
forward_list< T> - linked list list< T> - doubly-linked list
STL - Kontejnery
Asociativní kontejnery
Uspořádané (samovyvažující se stromy)
set - množina multiset - množina s opakováním map - asociativní pole, tj. parciální zobrazení K -> T multimap - relace s rychlým vyhledáváním podle klíče K
Hashované
unordered_set - množina unordered_multiset - množina s opakováním unordered_map - asociativní pole, tj. parciální zobrazení K -> T unordered_multimap - relace s rychlým vyhledáváním podle klíče K
pair - pomocná šablona uspořádané dvojice
s položkami first, second
STL
Associative containers
New objects are inserted at a position defined by their properties
sets: type T must define ordering relation or hash function maps: stored objects are of type pair< const K, T>
multi-: multiple objects with the same (equivalent) key value may be inserted
Ordered (implemented usually by red-black trees)
type K must define ordering or hash
set multiset map multimap
Hashed
unordered_set unordered_multiset unordered_map unordered_multimap
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 - Ordered Containers
Ordered containers require ordering relation on the key type
Only < is used (no need to define >, <=, >=, ==, !=) In simplest cases, the type has a built-in ordering
std::map< std::string, my_value> my_map;
If not built-in, ordering may be defined using a global function
bool operator<( const my_key & a, const my_key & b) { return /*...*/; } std::map< my_key, my_value> mapa;
If global definition is not appropriate, ordering may be defined using a functor
struct my_functor { bool operator()( const my_key & a, const my_key & b) const { return /*...*/; } }; std::map< my_key, my_value, my_functor> my_map;
If the ordering has run-time parameters, the functor will carry them
struct my_functor { my_functor( bool a); /*...*/ bool ascending; }; std::map< my_key, my_value, my_functor> my_map( my_functor( true));
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 - Unordered containers
Hashed containers require two functors: hash function and equality comparison
struct my_hash { std::size_t operator()( const my_key & a) const { /*...*/ } }; struct my_equal { public: bool operator()( const my_key & a, const my_key & b) const { /*return a == b;*/ } }; std::unordered_map< my_key, my_value, my_hash, my_equal> my_map;
If not explicitly defined by container template parameters, hashed containers try to use generic functors defined in the library
std::hash< K> std::equal_to< K>
Defined for numeric types, strings, and some other library types
std::unordered_map< std::string, my_value> my_map;
STL – Iterátory Metody kontejneru, vracející iterátory
Odkaz na začátek kontejneru - první platný prvek
iterator begin() const_iterator begin() const
Odkaz za konec kontejneru - za poslední platný prvek
iterator end() const_iterator end() const
iterator a const_iterator jsou typy definované uvnitř kontejneru, zvané iterátory
přístupné konstrukcemi jako vector< int>::iterator vlastnosti iterátorů jsou mírně závislé na druhu kontejneru
Iterátor 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 *"
Vytváří se tak iluze, že kontejner je pole
STL – Iterators
Each container defines two member types: iterator and const_iterator
using my_container = std::map< my_key, my_value>; using my_iterator = my_container::iterator; using my_const_iterator = my_container::const_iterator;
Iterators act like pointers to objects inside the container
objects are accessed using operators *, -> const_iterator does not allow modification of the objects
An iterator may point
to an object inside the container to an imaginary position behind the last object: end()
STL – Iterators void example( my_container & c1, const my_container & c2) {
Every container defines functions to access both ends of the container
begin(), cbegin() - the first object (same as end() if the container is empty) end(), cend() - the imaginary position behind the last object
my_iterator i1 = begin( c1);
// also c1.begin()
my_const_iterator i2 = cbegin( c1);
// also c1.cbegin(), begin( c1), c1.begin()
my_const_iterator i3 = cbegin( c2);
// also c2.cbegin(), begin( c2), c2.begin()
Associative containers allow searching
find( k) - first object equal (i.e. not less and not greater) to k, end() if not found lower_bound( k) - first object not less than k , end() if not found upper_bound( k) - first object greater than k , end() if not found
my_key k = /*...*/; my_iterator i4 = c1.find( k); my_const_iterator i5 = c2.find( k);
Iterators may be shifted to neighbors in the container
all iterators allow shifting to the right and equality comparison
for ( my_iterator i6 = c1.begin(); i6 != c1.end(); ++ i6 ) { /*...*/ }
bidirectional iterators (all containers except forward_list) allow shifting to the left
random access iterators (vector, string, deque) allow addition/subtraction of integers, difference and comparison
-- i1;
my_container::difference_type delta = i4 - c1.begin(); my_iterator i7 = c1.end() - delta;
// number of objects left to i4
// the same distance from the opposite end
if ( i4 < i7 ) my_value v = i4[ delta].second; }
// same as (*(i4 + delta)).second, (i4 + delta)->second
STL – Iterators
Caution:
Shifting an iterator before begin() or after end() is illegal
for (my_iterator it = c1.end(); it >= c1.begin(); -- it) // ERROR: underruns begin()
Comparing iterators associated to different (instances of) containers is illegal
if ( c1.begin() < c2.begin() )
// ILLEGAL
Insertion/removal of objects in vector/basic_string/deque invalidate all associated iterators
The only valid iterator is the one returned from insert/erase
std::vector< std::string> c( 10, "dummy"); auto it = c.begin() + 5;
// the sixth dummy
std::cout << * it;
auto it2 = c.insert( std::begin(), "first"); std::cout << * it;
// CRASH
it2 += 6;
// the sixth dummy
c.push_back( "last");
std::cout << * it2;
// CRASH
STL – Insertion/deletion
Containers may be filled immediately upon construction
using n copies of the same object
std::vector< std::string> c1( 10, "dummy");
or by copying from another container
std::vector< std::string> c2( c1.begin() + 2, c1.end() - 2);
Expanding containers - insertion
insert - copy or move an object into container emplace - construct a new object (with given parameters) inside container
Sequential containers
position specified explicitly by an iterator
new object(s) will be inserted before this position
c1.insert( c1.begin(), "front"); c1.insert( c1.begin() + 5, "middle"); c1.insert( c1.end(), "back");
// same as c1.push_back( "back");
STL – insertion/deletion
insert by copy
slow if copy is expensive
std::vector< std::vector< int>> c3;
not applicable if copy is prohibited
std::vector< std::unique_ptr< T>> c4;
insert by move
explicitly using std::move
std::unique_ptr< T> p( new T); c4.push_back( std::move( p));
implicitly when argument is rvalue (temporal object)
c3.insert( begin( c3), std::vector< int>( 100, 0));
emplace
constructs a new element from given arguments
c4.emplace_back( new T);
c3.insert( begin( c3), 100, 0);
STL – insertion/deletion
Shrinking containers - erase/pop
single object
my_iterator it = /*...*/; c1.erase( it); c2.erase( c2.end() - 1);
// same as c2.pop_back();
range of objects
my_iterator it1 = /*...*/, it2 = /*...*/; c1.erase( it1, it2); c2.erase( c2.begin(), c2.end());
// same as c2.clear();
by key (associative containers only)
my_key k = /*...*/; c3.erase( k);
Algoritmy
Algorithms
Algoritmy
Sada generických funkcí pro práci s kontejnery
cca 90 funkcí od triviálních po sort, make_heap, set_intersection, ...
#include
Kontejnery se zpřístupňují nepřímo - pomocí iterátorů
Některé algoritmy kontejner pouze čtou
Typicky vracejí iterátor Např. hledání v setříděných sekvenčních kontejnerech
Většina algoritmů modifikuje objekty v kontejneru
Obvykle pomocí dvojice iterátorů - polouzavřený interval Lze pracovat s výřezem kontejneru Je možné použít cokoliv, co se chová jako iterátor požadované kategorie
Kopírování, přesun (pomocí std::move), výměna (pomocí std::swap) Aplikace uživatelem definované akce (funktor)
Iterátory neumožňují přidávání/odebírání objektů v kontejneru
Pro "nové" prvky musí být předem připraveno místo Odebrání nepotřebných prvků musí provést uživatel dodatečně
Algorithms
Set of generic functions working on containers
cca 90 functions, trivial or sophisticated (sort, make_heap, set_intersection, ...)
#include
Containers are accessed indirectly - using iterators
Some algorithms are read-only
The result is often an iterator E.g., searching in non-associative containers
Most algorithms modify the contents of a container
Typically a pair of iterator specifies a range inside a container Algorithms may be run on complete containers or parts Anything that looks like an iterator may be used
Copying, moving (using std::move), or swapping (pomocí std::swap) elements Applying user-defined action on elements (defined by functors)
Iterators does not allow insertion/deletion of container elements
The space for "new" elements must be created before calling an algorithm Removal of unnecessary elements must be done after returning from an algorithm
Algoritmy
Iterátory neumožňují přidávání/odebírání objektů v kontejneru
Pro "nové" prvky musí být předem připraveno místo
my_container c2( c1.size(), 0); std::copy( c1.begin(), c1.end(), c2.begin());
Tento příklad lze zapsat bez algoritmů jako
my_container c2( c1.begin(), c1.end());
Odebrání nepotřebných prvků musí provést uživatel dodatečně
auto my_predicate = /*...*/;
// some condition
my_container c2( c1.size(), 0);
// max size
my_iterator it2 = std::copy_if( c1.begin(), c1.end(), c2.begin(), my_predicate); c2.erase( it2, c2.end());
// shrink to really required size
my_iterator it1 = std::remove_if( c1.begin(), c1.end(), my_predicate);
c1.erase( it1, c1.end());
// really remove unnecessary elements
Algorithms
Iterators does not allow insertion/deletion of container elements
The space for "new" elements must be created before calling an algorithm
my_container c2( c1.size(), 0); std::copy( c1.begin(), c1.end(), c2.begin());
Note: This example does not require algorithms:
my_container c2( c1.begin(), c1.end());
Removal of unnecessary elements must be done after returning from an algorithm
auto my_predicate = /*...*/;
// some condition
my_container c2( c1.size(), 0);
// max size
my_iterator it2 = std::copy_if( c1.begin(), c1.end(), c2.begin(), my_predicate); c2.erase( it2, c2.end());
// shrink to really required size
my_iterator it1 = std::remove_if( c1.begin(), c1.end(), my_predicate);
c1.erase( it1, c1.end());
// really remove unnecessary elements
STL – Algoritmy
Falešné iterátory
Algoritmy dovedou pracovat s čímkoliv, co napodobuje iterátor Požadavky algoritmu na iterátory definovány pomocí kategorií
Každý iterátor musí prozradit svou kategorii a další vlastnosti
Input, Output, Forward, Bidirectional, RandomAccess std::iterator_traits Některé algoritmy se přizpůsobují kategorii svých parametrů (std::distance)
Insertery
my_container c2;
// empty
auto my_inserter = std::back_inserter( c2); std::copy_if( c1.begin(), c1.end(), my_inserter, my_predicate);
Textový vstup/výstup
auto my_inserter2 = std::ostream_iterator< int>( std::cout, " "); std::copy( c1.begin(), c1.end(), my_inserter2);
STL – Algorithms
Fake iterators
Algorithms may accept anything that works like an iterator The required functionality is specified by iterator category
Every iterator must specify its category and some other properties
Input, Output, Forward, Bidirectional, RandomAccess std::iterator_traits Some algorithms change their implementation based on the category (std::distance)
Inserters
my_container c2;
// empty
auto my_inserter = std::back_inserter( c2); std::copy_if( c1.begin(), c1.end(), my_inserter, my_predicate);
Text input/output
auto my_inserter2 = std::ostream_iterator< int>( std::cout, " "); std::copy( c1.begin(), c1.end(), my_inserter2);
Funktory
NPRG041 Programování v C++ - 2014/2015 David Bednárek
146
Functors
NPRG041 Programování v C++ - 2014/2015 David Bednárek
147
STL – Funktory
Příklad - funkce for_each
template
Function for_each( InputIterator first, InputIterator last, Function f) { for (; first != last; ++first) f( * first);
return f; }
f je cokoliv, co lze zavolat syntaxí f(x)
globální funkce (ukazatel na funkci), nebo funktor, tj. třída obsahující operator()
Funkce f (případně metoda operator()) je zavolána na každý prvek v zadaném intervalu
prvek se předává jako * iterator, což může být odkazem funkce f tedy může modifikovat prvky seznamu
STL – Functors
Example - for_each
template Function for_each( InputIterator first, InputIterator last, Function f) { for (; first != last; ++first) f( * first); return f; }
f may be anything that has the function call operator f(x)
a global function (pointer to function), or a functor, i.e. a class containing operator()
The function f (its operator()) is called for each element in the given range
The element is accessed using the * operator which typically return a reference The function f can modify the elements of the container
STL – Algoritmy
Jednoduché použití funkce for_each
void my_function( double & x) { x += 1; } void increment( std::list< double> & c) { std::for_each( c.begin(), c.end(), my_function); }
[C++11] Lambda
Nová syntaktická konstrukce generující funktor
void increment( std::list< double> & c) { for_each( c.begin(), c.end(), []( double & x){ x += 1;}); }
STL – Algorithms
A simple application of for_each
void my_function( double & x) { x += 1; } void increment( std::list< double> & c) { std::for_each( c.begin(), c.end(), my_function); }
[C++11] Lambda
New syntax construct - generates a functor
void increment( std::list< double> & c) { for_each( c.begin(), c.end(), []( double & x){ x += 1;}); }
STL – Algoritmy
Předávání parametrů vyžaduje funktor
class my_functor { public:
double v; void operator()( double & x) const { x += v; } my_functor( double p) : v( p) {} }; void add( std::list< double> & c, double value) { std::for_each( c.begin(), c.end(), my_functor( value)); }
Lambda s přístupem k lokální proměnné
void add( std::list< double> & c, double value) { std::for_each( c.begin(), c.end(), [value]( double & x){ x += value;}); }
STL – Algorithms
Passing parameters requires a functor
class my_functor { public:
double v; void operator()( double & x) const { x += v; } my_functor( double p) : v( p) {} }; void add( std::list< double> & c, double value) { std::for_each( c.begin(), c.end(), my_functor( value)); }
Equivalent implementation using lambda
void add( std::list< double> & c, double value) { std::for_each( c.begin(), c.end(), [value]( double & x){ x += value;}); }
STL – Algoritmy
Funktory modifikující svůj obsah
class my_functor { public: double s; void operator()( const double & x) { s += x; } my_functor() : s( 0.0) {} }; double sum( const std::list< double> & c) { my_functor f = std::for_each( c.begin(), c.end(), my_functor()); return f.s; }
Lambda s referencí na lokální proměnnou
double sum( const std::list< double> & c) {
double s = 0.0; for_each( c.begin(), c.end(), [& s]( const double & x){ s += x;}); return s;
}
STL – Algoritmy
A functor may modify its contents
class my_functor { public: double s; void operator()( const double & x) { s += x; } my_functor() : s( 0.0) {} }; double sum( const std::list< double> & c) { my_functor f = std::for_each( c.begin(), c.end(), my_functor()); return f.s; }
Using lambda (the generated functor contains a reference to s)
double sum( const std::list< double> & c) {
double s = 0.0; for_each( c.begin(), c.end(), [& s]( const double & x){ s += x;}); return s;
}
Lambda
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 je nahrazen vytvořením objektu
ftor( vlist ...)
C++11
Lambda expressions
Lambda expression
[ capture ]( params ) mutable -> rettype { body }
Declares a class
class ftor {
public: ftor( TList ... plist) : vlist( plist) ... { } rettype operator()( params ) const { body } private: TList ... vlist; };
vlist determined by local variables used in the body TList determined by their types and adjusted by capture operator() is const if mutable not present
The lambda expression corresponds to creation of an anonymous object
ftor( vlist ...)
C++11
Lambda výrazy – návratový typ a typ funkce
C++11
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
Lambda expressions – return types
C++11
Return type of the operator()
Explicitly defined
[]() -> int { /*…*/ }
Automatically derived if body contains just one return statement
[]() { return V; }
void otherwise
Lambda výrazy – capture
Capture
Způsob zpřístupnění vnějších entit
lokální proměnné this
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,this]
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
předání odkazem, vyjmenované výjimky hodnotou
[&] [&,a,c]
C++11
Lambda expressions – capture
Capture
Defines which external variables are accessible and how
local variables in the enclosing function this, if used in a member function
Determines the data members of the functor Explicit capture
The external variables explicitly listed in capture
[a,&b,c,&d,this]
variables marked & passed by reference, the others by value
Implicit capture
The required external variables determined automatically by the compiler, capture defines the mode of passing
[=] [=,&b,&d]
passed by value, the listed exceptions by reference
passed by reference, the listed exceptions by value
[&] [&,a,c]
C++11
Konstruktory a destruktory Constructors and Destructors
Constructors and destructors Constructors and Destructors
Konstruktory a destruktory
Konstruktor třídy T je metoda se jménem T
Konstruktor je volán vždy, když vzniká objekt typu T
Typ návratové hodnoty se neurčuje Konstruktorů může být více, liší se parametry Nesmí být virtuální 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 ~T
Destruktor je volán vždy, když zaniká objekt typu T
Nesmí mít parametry ani návratovou hodnotu Může být virtuální Destruktor může generovat sám kompilátor
Destruktor lze vyvolat přímo pouze speciální syntaxí
Constructors and destructors
Constructor of class T is a method named T
A constructor is called whenever an object of the type T is created
Return type not specified More than one constructor may exist with different arguments Never virtual Constructor parameters specified in the moment of creation Some constructors have special meaning Some constructors may be generated by the compiler
Constructors cannot be called directly
Destructor of class T is a method named ~T
The destructor is called whenever an object of the type T is destroyed
No arguments, no return value May be virtual The destructors may be generated by the compiler
Explicit call must use special syntax
Speciální metody tříd
Konstruktor bez parametrů (default constructor)
T();
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
~T();
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 ~T();
Special member functions
Default constructor
T();
For object without explicit initialization Generated by compiler if required and if the class has no constructor at all:
Data members of non-class types are not initialized Data members of class types and base classes are initialized by calling their default constructors Generation may fail due to non-existence or inaccessibility of element constructors
Destructor
~T();
Generated by compiler if required and not defined
Calls destructors of data members and base classes
If a class derived from T has to be destroyed using T *, the destructor of T must be virtual
All abstract classes shall have a virtual destructor
virtual ~T();
copy/move Speciální metody tříd – C++11
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 operator
T & operator=( T && x);
copy/move
Special member functions
Copy constructor
T( const T & x);
Move constructor
T( T && x);
Copy assignment operator
T & operator=( const T & x);
Move assignment operator
T & operator=( T && x);
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í
copy/move
Compiler-generated implementation
Copy constructor
T( const T & x) = default;
applies copy constructor to every element
Move constructor
T( T && x) = default;
applies move constructor to every element
Copy assignment operator
T & operator=( const T & x) = default;
applies copy assignment to every element
Move assignment operator
T & operator=( T && x) = default;
applies move assignment to every element
elements are data members and base classes for elements of non-class types, move is equivalent to copy
the default keyword allows to enforce generation by the compiler
copy/move
Podmínky automatického defaultu
Copy 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
If needed, compiler will generate the methods automatically under these conditions:
Copy constructor/assignment operator
Move constructor/assignment operator
if there is no definition for the method and no move method is defined this is backward-compatibility rule; future development of the language will probably make the condition more stringent (no copy/move/destructor at all)
if no copy/move method is defined and no destructor is defined
the default keyword overrides the conditions
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)
Třída obsahující složky vyžadující zvláštní péči
Překladačem generované chování (default) nevyhovuje Bez podpory move (typický stav před C++11, dnes funkční, ale neoptimální)
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();
copy/move
Most-frequent cases
A harmless class
No copy/move method, no destructor No dangerous data members (raw pointers)
A class containing dangerous members
Compiler-generated behavior (default) would not work properly No move support (before C++11, still functional but not optimal)
T( const T & x); T & operator=( const T & x); ~T();
Full copy/move support
T( const T & x); T( T && x); T & operator=( const T & x);
T & operator=( T && x); ~T();
copy/move
Další kombinace
Nekopírovatelná a nepřesouvatelná třída
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 (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ý
copy/move
Less frequent cases
A non-copiable and non-movable class
E.g., dynamically allocated "live" objects in simulations
T( const T & x) = delete; T & operator=( const T & x) = delete;
The delete keyword prohibits automatic default for copy methods Language rules prohibit automatic default for move methods A destructor may be required
A movable non-copiable class
E.g., an owner of another object (like std::unique_ptr< U>)
T( T && x); T & operator=( T && x); ~T();
Language rules prohibit automatic default for copy methods A destructor is typically required
copy/move
Důsledky přítomnosti datových položek
Číselné typy
Vyžadují explicitní inicializaci, destrukce není nutná Kopírování/přesouvání je bez problémů
Struktury/třídy
Nemají-li vlastní copy/move operace, chovají se stejně, jako kdyby jejich součásti byly přítomny přímo Mají-li dobře udělané vlastní copy/move, obvykle nezpůsobují problémy
Vyžadují ošetření, pokud má být semantika vnější třídy jiná (např. chytré ukazatele ve třídách s hodnotovou semantikou)
Kontejnery a řetězce
Kontejnery mají vlastní copy/move operace Chovají se stejně, jako kdyby elementy kontejneru byly přítomny přímo
Kontejner je však automaticky inicializován jako prázdný - není třeba inicializovat jeho prvky
NPRG041 Programování v C++ - 2014/2015 David Bednárek
181
copy/move
Handling data members in constructors and destructors
Numeric types
Explicit initialization recommended, no destruction required Compiler-generated copy/move works properly
Structs/classes
If they have no copy/move methods, they behave as if their members were present directly If they have copy/move methods, they usually do not require special handling
Special handling required if the outer class semantics differ from the inner class (e.g., using smart pointers to implement containers)
Containers and strings
Behave as if their members were present directly
Containers are initialized as empty - no need to initialize even containers of numeric types
NPRG041 Programování v C++ - 2014/2015 David Bednárek
182
copy/move
Důsledky přítomnosti datových položek - odkazy bez odpovědnosti vlastníka
Reference (U&)
Vyžadují explicitní inicializaci, destrukce není nutná Copy/move konstruktory jsou bez problémů Copy/move operator= je znemožněn
Ukazatele (U*) bez (spolu-)vlastnické semantiky
Dealokaci řeší někdo jiný
Vyžadují explicitní inicializaci, destrukce není nutná Kopírování/přesouvání je bez problémů
NPRG041 Programování v C++ - 2014/2015 David Bednárek
183
copy/move
Data members - links without ownership
References (U&)
Explicit initialization required, destruction not required Copy/move constructors work smoothly Copy/move operator= is impossible
Raw pointers (U*) without ownership semantics
Proper deallocation is ensured by someone else
Explicit initialization required, destruction not required Copy/move work smoothly
NPRG041 Programování v C++ - 2014/2015 David Bednárek
184
copy/move
Důsledky přítomnosti datových položek - vlastnické odkazy
Ukazatele (U*) se semantikou vlastníka
Naše třída musí řešit dealokaci připojeného objektu
Vyžadují explicitní inicializaci (alokace nebo vynulování) Destrukce je nutná (dealokace) Kopírování musí alokovat nový připojený objekt a kopírovat jeho obsah Přesouvání musí vynulovat odkazy ve zdrojové třídě Copy/move operator= musí navíc uklidit původní obsah
Ukazatele (U*) se semantikou spoluvlastníka
Naše třída musí řešit počítání odkazů a dealokaci připojeného objektu
Vyžadují explicitní inicializaci (alokace nebo vynulování) Destrukce je nutná (odečtení odkazu, dealokace) Kopírování musí aktualizovat počet odkazů Přesouvání musí vynulovat odkazy ve zdrojové třídě Copy/move operator= musí navíc uklidit původní obsah
NPRG041 Programování v C++ - 2014/2015 David Bednárek
185
copy/move
Data members - links with ownership
Raw pointers (U*) with unique ownership
Our class must deallocate the remote object properly
Explicit initialization required (allocate or set to zero) Destruction is required (deallocate if not zero) Copy methods must allocate new space a copy data Move methods must clear links in the source object In addition, copy/move operator= must clean the previous contents
Raw pointer (U*) with shared ownership
Our class must count references and deallocate if needed
Explicit initialization required (allocate or set to zero) Destruction is required (decrement counter, deallocate if needed) Copy methods must increment counter Move methods must clear links in the source object In addition, copy/move operator= must clean the previous contents
NPRG041 Programování v C++ - 2014/2015 David Bednárek
186
copy/move
Důsledky přítomnosti datových položek - chytré odkazy
std::unique_ptr
Explicitní inicializace není nutná (automaticky nullptr) Explicitní destrukce není nutná (automaticky dealokuje připojený objekt) Copy operace nejsou možné
Jsou-li vyžadovány, musejí být implementovány kopírováním obsahu
Move operace jsou bezproblémové
std::shared_ptr
Explicitní inicializace není nutná (automaticky nullptr) Explicitní destrukce není nutná Copy operace fungují, ale mají semantiku sdílení
Mají-li mít semantiku kopírování, je nutné upravit ostatní metody třídy - všechny modifikující metody si musejí vytvořit privátní kopii připojeného objektu
Move operace jsou bezproblémové
NPRG041 Programování v C++ - 2014/2015 David Bednárek
187
copy/move
Data members - smart pointers
std::unique_ptr
Explicit initialization not required (nullptr by default) Explicit destruction not required (smart pointers deallocate automatically) Copying is impossible
If copying is required, it must be implemented by duplicating the linked object
Move methods work smoothly
std::shared_ptr
Explicit initialization not required (nullptr by default) Explicit destruction not required (smart pointers deallocate automatically) Copying works as sharing
If sharing semantics is not desired, other methods must be adjusted - all modifying operations must ensure a private copy of the linked object
Move methods work smoothly
NPRG041 Programování v C++ - 2014/2015 David Bednárek
188
Konverze
Conversions
Speciální metody tříd
Konverzní konstruktory
class T { T( U x); };
Zobecnění kopírovacího konstruktoru Definuje uživatelskou konverzi typu U na T Je-li tento speciální efekt nežádoucí, lze jej zrušit:
explicit T( U v);
Konverzní operátory
class T { operator U() const; };
Definuje uživatelskou konverzi typu T na U Vrací typ U hodnotou (tedy s použitím kopírovacího konstruktoru U, pokud je U třída)
Kompilátor vždy použije nejvýše jednu uživatelskou konverzi
Special member functions
Conversion constructors
class T { T( U x); };
Generalized copy constructor Defines conversion from U to T If conversion effect is not desired, all one-argument constructors must be "explicit":
explicit T( U v);
Conversion operators
class T { operator U() const; };
Defines conversion from T to U Returns U by value (using copy-constructor of U, if U is a class)
Compiler will never use more than one user-defined conversion in a chain
Přetypování
Různé varianty syntaxe
C-style cast
(T)e
Převzato z C
Function-style cast
T(e)
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(e) static_cast(e) reinterpret_cast(e)
Novinka - přetypování s běhovou kontrolou:
dynamic_cast(e)
Type cast
Various syntax styles
C-style cast
(T)e
Inherited from C
Function-style cast
T(e)
Equivalent to (T)e T must be single type identifier or single keyword
Type conversion operators
Differentiated by intent (strength and associated danger) of cast:
const_cast(e) static_cast(e) reinterpret_cast(e)
New - run-time assisted cast:
dynamic_cast(e)
Přetypování const_cast(e)
Odstranění konstantnosti
const U & => U & const U * => U *
Lze nahradit specifikátorem mutable
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(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:
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
Derived & => Base & Derived * => Base *
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(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í reinterpret_cast(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(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(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 ) { /* ... */ }
Dynamic cast dynamic_cast(e)
Most frequent use
Converting a pointer to a base class to a pointer to a derived class
class Base { public: virtual ~Base(); /* base class must have at least one virtual function */ }; 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 ) { /* ... */ }
Konstruktory a spol. Typické situace
Class patterns 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_; };
Class patterns
POD: Plain-Old-Data
Public data members The user is responsible for initialization
class T { public: std::string x_; };
struct often used instead of class
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_; };
Class patterns
All data-members harmless
Every data member have its own constructor The class does not require any constructor
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_; };
Class patterns
All data-members harmless
Every data member have its own constructor Constructor enables friendly initialization
Due to language rules, the parameterless constructor is often needed too
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_; };
Class patterns
Some slightly dangerous elements
Some elements lack suitable default constructors
Numeric types, including bool, char
A constructor is required to properly initialize these elements
Consequently, default (parameterless) constructor is (typically) also required One-parameter constructors marked 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é
Je nutný copy/move 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( 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_; };
Class patterns
Some very dangerous elements
Pointers with (exclusive/shared) ownership semantics
copy/move constructor/operator= and destructor required
Some additional constructor (e.g. default) is also required
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.
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_; };
Class patterns
Classes containing unique_ptr
Uncopiable class
But movable
class T { public: T() : p_( new Data) {} private: std::unique_ptr< Data> p_; };
Konstruktory a spol.
Použití unique_ptr
Třída s povoleným kopírováním
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) { return operator=( T( x));} T & operator=( T && x) = default; private: std::unique_ptr< Data> p_; };
Class patterns
Classes containing unique_ptr
Copying enabled
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) { return operator=( T( x));} T & operator=( T && x) = default; 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() {} };
Class patterns
Abstract class
Copying/moving prohibited
class T { protected: T() {} T( const T & x) = delete; T & operator=( const T & x) = delete; public: virtual ~T() {} // required for proper deletion of objects };
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; };
Class patterns
Abstract class
Cloning support
class T { protected: T() {} T( const T & x) = default;
// descendants will need it to implement clone
T & operator=( const T & x) = delete; public: virtual ~T() {} virtual std::unique_ptr< T> clone() const = 0; };
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í
Může
vhodné novými zakrývat staré, vyjma virtuálních
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() { /* ... */ } };
Inheritance class Base { /* ... */ }; class Derived : public Base { /* ... */ }
Derived
class is a descendant of Base class
Contains all
types, data elements and functions of Base New types/data/functions may be added Hiding
old names by new names is not wise, except for virtual functions
Functions
declared as virtual in Base may change their behavior by reimplementation in Derived class Base { virtual void f() { /* ... */ } };
class Derived : public Base { virtual void f() { /* ... */ } };
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; b.f();
// slicing = kopie části objektu
// volá Base::f ikdyž je virtuální
Slicing je specifikum jazyka C++
Virtual functions class Base { virtual void f() { /* ... */ } };
class Derived : public Base { virtual void f() { /* ... */ } };
Virtual function call works only in the presence of pointers or references
Base * p = new Derived; p->f();
// calls Derived::f although p is pointer to Base
Without pointers/references, having functions virtual has no sense
Derived d;
d.f();
// calls Derived::f even for non-virtual f
Base b = d; b.f();
// slicing = copying a part of an object
// calls Base::f even for virtual f
Slicing is specific to 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
Classes in inheritance
Abstract class
Definition in C++: A class that contains some pure virtual functions
virtual void f() = 0;
Such class are incomplete and cannot be instantiated alone
General definition: A class that will not be instantiated alone (even if it could) Defines the interface which will be implemented by the derived classes
Concrete class
A class that will be instantiated as an object Implements the interface required by its base class
Dědičnost a destruktor class Base {
Base * p = new Derived;
public: virtual ~Base() {}
delete p;
};
class Derived : public Base { public:
virtual ~Derived() { /* ... */ }
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í
};
Odvozené pravidlo:
Každá abstraktní třída má mít virtuální destruktor
Je to zadarmo Může se to hodit
Inheritance and destructors class Base {
Base * p = new Derived;
public: virtual ~Base() {}
delete p;
};
class Derived : public Base { public:
virtual ~Derived() { /* ... */ } };
If an object is destroyed using delete applied to a pointer to its base class, the destructor of the base class must be virtual
Rule of thumb:
Every abstract class must have a virtual destructor
There is no additional cost (there are other virtual functions) It will be probably needed
Dědičnost Mechanismus dědičnosti v C++ je velmi Bývá používán
silný
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...)
Inheritance
Inheritance mechanisms in C++ are very strong
Often misused
Inheritance shall be used only in these cases
ISA hiearachy
Eagle IS A Bird Square-Rectangle-Polygon-Drawable-Object
Interface-implementation
Readable-InputFile Writable-OutputFile (Readable+Writable)-IOFile
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
Inheritance
ISA hierarchy
C++: Single non-virtual public inheritance class Derived : public Base
Abstract classes may contain data (although usually do not)
Interface-implementation
C++: Multiple virtual public inheritance class Derived : virtual public Base1, virtual public Base2
Abstract classes usually contain no data Interfaces are not used to own (destroy) the object
Often combined 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 &
Misuse of inheritance
Misuse of inheritance - #1
class Real { public: double Re; }; class Complex : public Real { public: double Im; };
Leads to slicing:
double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; }
Complex x; double a = abs( x);
// it CAN be compiled - but it should not
Reference to the derived class may be assigned to a reference to the base class
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
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 &
Misuse of inheritance
Misuse of inheritance - #2
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };
Mistake: Objects in C++ are not mathematical objects
void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; }
Real x; set_to_i( x);
// it CAN be compiled - but it should not
Real => Real & => Complex &
Šablony Templates
Templates 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")
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
Templates
Template
a generic piece of code parameterized by types and integer constants
Class templates
Global classes Classes nested in other classes, including class templates
template< typename T, std::size_t N> class array { /*...*/ };
Function templates
Global functions Member functions, including constructors
template< typename T> inline T max( T x, T y) { /*...*/ }
Type templates [C++11]
template< typename T> using array3 = std::array< T, 3>;
Templates
Template
a generic piece of code parameterized by types and integer constants
Class templates
Global classes Classes nested in other classes, including class templates
template< typename T, std::size_t N> class array { /*...*/ };
Function templates
Global functions Member functions, including constructors
template< typename T> inline T max( T x, T y) { /*...*/ }
Type templates [C++11]
template< typename T> using array3 = std::array< T, 3>;
Templates
Template instantiation
Using the template with particular type and constant parameters Class and type templates: parameters specified explicitly
std::array< int, 10> x;
Function templates: parameters specified explicitly or implicitly
Implicitly - derived by compiler from the types of value arguments
int a, b, c; a = max( b, c);
// calls max< int>
Explicitly
a = max< double>( b, 3,14);
Mixed: Some (initial) arguments explicitly, the rest implicitly
Templates
Multiple templates with the same name
Class and type templates:
one "master" template
template< typename T> class vector {/*...*/};
any number of specializations which override the master template
partial specialization
template< typename T, std::size_t n> class unique_ptr< T [n]> {/*...*/};
explicit specialization
template<> class vector< bool> {/*...*/};
Function templates:
any number of templates with the same name shared with non-templated functions
Writing templates
Compiler needs hints from the programmer
Dependent names have unknown meaning/contents
type names must be explicitly designated
template< typename T> class X { typedef typename T::B U; typename U::D p;
typename Y::C q; void f() { T::D(); }
// T::D is not a type
}
explicit template instantiations must be explicitly designated members inherited from dependent classes must be explicitly designated
template< typename T> class X : public T { void f() { return this->a; } }
Š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::C q; // Y::C je typ void f() { T::D(); } // T::D není typ } typename
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
Š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);
// parametry šablony
// parametry funkce
template< typename T, typename U>
// parametry šablony
int g( T * p, vector< U> q);
// parametry funkce
Šablony funkcí lze volat dvěma způsoby
Explicitně
f< int, 729>( a, b)
Automaticky
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
g( a, b)
Š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>;
Variadic templates
Šablony s proměnlivým počtem parametrů Hlavička s
šablony
proměnlivým počtem typových argumentů
template< typename ... TList> class C { /* ... */ }; pojmenovaný
parametr zastupující seznam typů lze i kombinovat s pevnými parametry
template< typename T1, int c2, typename ... TList> class D { /* ... */ }; platí i
pro hlavičky parciálních specializací
template< typename T1, typename ... TList> class C< T1, TList ...> { /* ... */ };
C++11
Šablony s proměnlivým počtem parametrů template< typename ... TList>
pojmenovaný parametr
- seznam typů lze uvnitř šablony použít v těchto konstrukcích: vždy se suffixem ... typové
C++11
argumenty v použití (jiné) šablony
X< TList ...> Y< int, TList ..., double> seznam
předků třídy
class E : public TList ... deklarace
parametrů funkce/metody/konstruktoru
void f( TList ... plist); double g( int a, double c, TList ... b); tím
vzniká pojmenovaný parametr zastupující seznam hodnot ke každému seznamu hodnot musí být seznam typů několik
dalších okrajových případů počet prvků seznamu sizeof...(TList)
Šablony s proměnlivým počtem parametrů
template< typename ... TList> void f( TList ... plist); pojmenovaný parametr -
seznam hodnot lze uvnitř funkce použít v těchto konstrukcích: vždy
se suffixem ... hodnotové argumenty ve volání (jiné) funkce/konstruktoru
g( plist ...) new T( a, plist ..., 7) T v( b, plist ..., 8); inicializační
sekce konstruktoru
E( TList ... plist) : TList( plist) ... { } několik
dalších případů
C++11
Šablony s proměnlivým počtem parametrů
template< typename ... TList> void f( TList ... plist); při použití je možno prvky seznamu obalit suffix
... slouží jako kompilační for_each (každý) výskyt názvu seznamu je nahrazen jeho i-tým prvkem výsledkem je seznam
typů v parametrech šablony nebo deklaraci funkce
X< std::pair< int, TList *> ...> class E : public U< TList> ... void f( const TList & ... plist); seznam
výrazů ve volání funkce/metody/konstruktoru
g( make_pair( 1, & plist) ...); h( static_cast< TList *>( plist) ...); i( sizeof( TList) ...); // pozor, sizeof...( TList) je něco jiného seznam
inicializátorů v konstruktoru další okrajové případy
C++11
Generická N-tice template class tuple { public: tuple( const Types & ...); /* black magic */ }; template < size_t I, class T>
class tuple_element { public: typedef /* black magic */ type; }; template < size_t I, class ... Types> typename tuple_element< I, tuple< Types ...> >::type & get( tuple< Types ...> & t); použití typedef tuple< int, double, int> my_tuple; typedef typename tuple_element< 1, my_tuple>::type alias_to_double;
my_tuple t1( 1, 2.3, 4); double v = get< 1>( t1);
C++11:
Exception handling Mechanismus výjimek
Exception handling Mechanismus výjimek
Start: příkaz throw Cíl: try-catch blok
class WrongException : public AnyException { /*...*/ }; class BadException
Určen za běhu
Skok může opustit proceduru
class AnyException { /*...*/ };
Proměnné korektně zaniknou voláním destruktorů
: public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException( something);
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
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
class WrongException : public AnyException { /*...*/ }; class BadException
Určen za běhu
Skok může opustit proceduru
class AnyException { /*...*/ };
Proměnné korektně zaniknou voláním destruktorů
: public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException();
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
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
class WrongException : public AnyException { /*...*/ }; class BadException
Určen za běhu
Skok může opustit proceduru
class AnyException { /*...*/ };
Proměnné korektně zaniknou voláním destruktorů
: public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException();
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
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
Stack-unwinding
Hodnota je uložena "stranou" 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 Zhmotněné výjimky
std::exception_ptr je chytrý ukazatel na objekt výjimky
void g() {
try {
std::current_exception()
Objekt zanikne při zániku posledního ukazatele
std::exception_ptr p;
f();
Vrací aktuálně řešenou výjimku
}
std::rethrow_exception( p)
catch (...) {
Vyvolává uloženou výjimku
p = std::current_exception();
}
Tento mechanismus umožňuje odložit ošetřování výjimky, zejména:
Propagace výjimky do jiného vlákna Řešení výjimek v promise/future
} void h() { std::rethrow_exception( p); }
C++11
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
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:
metoda what() vrací řetězec s chybovým hlášením
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
bad_alloc: vyvolává operátor new při nedostatku paměti
domain_error, invalid_argument, length_error, out_of_range vyvolávány např. funkcí vector::operator[]
Odvozené z třídy runtime_error:
V režimu "bez výjimek" new vrací nulový ukazatel
bad_cast, bad_typeid: Chybné použití RTTI Odvozené z třídy logic_error:
metoda what() vrací řetězec s chybovým hlášením
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 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
throw( T) specifikace se příliš nepoužívaly
C++11 void f() noexcept
{
C++11 definuje novou syntaxi
noexcept noexcept( c) kde c je Booleovský konstantní výraz
} template< typename T> void g( T & y)
noexcept( std::is_nothrow_copy_constructible < T>::value) { T x = y; }
Exception-safe programming Bezpečné programování s výjimkami
Exception-safe programming void f() {
int * a = new int[ 100];
Používat throw a catch je jednoduché
int * b = new int[ 200]; g( a, b);
delete[] b;
Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek
Exception-safety Exception-safe programming
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 T & operator=( const T & b) {
if ( this != & b )
Používat throw a catch je jednoduché
{ delete body_;
body_ = new TBody( b.length());
Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek
Exception-safety Exception-safe programming
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
Destruktor nesmí skončit vyvoláním výjimky
vynucená jazykem
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
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
vynucená jazykem
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
Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky
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
Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky
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
Výjimka v destruktoru při stack-unwinding
Tento throw příkaz nebude vyvolán
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 (pozorovatelném) stavu, ve kterém byla při jejím vyvolání Observable state - chování veřejných metod 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