• No results found

TDIU20 - Objektorienterad programmering i c++ - föreläsning 5

N/A
N/A
Protected

Academic year: 2022

Share "TDIU20 - Objektorienterad programmering i c++ - föreläsning 5"

Copied!
35
0
0

Loading.... (view fulltext now)

Full text

(1)

TDIU20 -

Objektorienterad

programmering i c++ - föreläsning 5

Pontus Haglund

Department of Computer and information science

(2)

Destruktor Kopiering

Kopieringstilldelening Flytt

Flyttilldelning

2 När genereras de inte?

Rule of three Rule of six Rule of zero Djup kopiering Korrekt flytt

(3)

Destruktor Kopiering

Kopieringstilldelening Flytt

Flyttilldelning

2 När genereras de inte?

Rule of three Rule of six Rule of zero Djup kopiering Korrekt flytt

(4)

Speciella medlemsfunktioner

Du har redan stött på några!

Funktioner som automatiskt genereras av konstruktorn

(5)

Vilka är de?

Defaultkonstruktor

Destruktor

Kopieringskonstruktor

Kopieringstilldelningsoperator

Flyttkonstrukor

Flyttilldelningsoperator

(6)

Defaultkonstruktor

Finns om ingen annan

konstruktor explicit deklarerats

Så vad gör default konstruktor?

Kallar basklassernas defaultkonstruktorer

Kallas datamedlemmarnas defaultkosntruktorer

Samma beteende som en användardefinierad konstruktor med tom initieringslista och kropp

// Vad händer?

class Foo { public:

Foo() {

cout <<"Constructor called"<< endl;

} };

class Bar { private:

Foo f;

};

int main() {

Bar c{};

}

(7)

Destructor

Anropas vid slutet av ett objekts livstid (lifetime)

När är detta?

När programmet avslutas (static)

End of scope (slut av block) för sånt på stacken

delete-expression för saker vi skapat med new-expression

slutet av uttrycket för anonyma temporära data

Och några fler fall...

(8)

Destruktor automatic duration

classBar {

public:

~Bar() {

cout <<"Destructor called"<< endl;

} };

intmain() {

{

Bar c{};

}

cout <<"end of main"<< endl;

}

(9)

Destruktor dynamic storage duration

classBar { public:

~Bar() { cout << "Destructor called"<< endl; } };

intmain() {

Bar* pb = newBar{};

cout <<"end of main"<< endl;

}

(10)

Destructor member

classFoo { public:

~Foo() { cout << "Foo Destructor called"<< endl; } private:

};

classBar { public:

~Bar() { cout << "Bar Destructor called"<< endl; } private:

Foo f{};

};

intmain() {

Bar* pb = newBar{};

delete pb;

cout <<"end of main"<< endl;

}

(11)

Destructor dynamic member

classFoo { public:

~Foo() { cout << "Foo Destructor called"<< endl; } };

classBar { public:

~Bar() {

cout <<"Bar Destructor called"<< endl;

} private:

Foo* f =new Foo{};

};

intmain() {

Bar* pb = newBar{};

delete pb;

cout <<"end of main"<< endl;

}

(12)

Destructor nameless temporaries

classBar { public:

~Bar() {

cout <<"Bar Destructor called"<< endl;

}

int operator+(int rhs) {

returni + rhs;

} // ...

private:

int i{};

};

intmain() {

// Vad är skillnaden?

Bar b{};

b + 4;

Bar{} + 4;

cout <<"end of main"<< endl;

}

(13)

Copy or copy initialization

Intialize an object from another object

- Cppreference

(14)

Value semantics

Value semantics

Kallas ibland copy semantics

Tilldening kopierar värden, inte bara pekaren

Reference semantics

Tilldening kopierar pekaren

Båda variablerna blir referenser till ett och samma värde

C++ har den första varianten som beteende och det är väll bra!?

(15)

Kopieringskonstruktor

Bar b{}; //Initiera objektet b

Bar bc{b};//Initiera objektet bc med b's datamedlemmar

Med hjälp av ett annat objekt initializera detta objekt

Vad är default beteende?

Varje datamedlem i det nya objektet initieras med motsvarande datamedlem från det andra objektet

Båda objekten är i valid-state efter kopiering

Default finns om ingen flyttkonstruktor eller flyttildelningsoperator är deklarerad

(16)

Skriv din egen konstruktor

classBar { public:

Bar() : i{0} {}

Bar(Barconst& rhs) : Bar{rhs.get_i()}

{

cout <<"Bar copy constructor called"<< endl;

}

int get_i() { returni; } //...

private:

int i{};

};

intmain() {

Bar b{};

Bar bc{b};

cout <<"end of main" << endl;

}

(17)

Operator för kopieringstilldelning

Bar b{};

Bar bc = b;

Samma default regler och beteende som kopieringskonstruktorn

(18)

Skriv din egen operator

classBar { public:

Bar() : i{0} {}

Bar&operator = (Barconst& rhs) {

i = rhs.get_i();

cout <<"Bar copy assignment operator called" << endl;

return *this;

}

int get_i() { returni; } //...

private:

int i{};

};

intmain() {

Bar b{};

Bar bc{b};

cout <<"end of main" << endl;

}

(19)

Move

Initiera ett objekt med ett annat objekt

Men lite annorlunda från copy

xvalue?

(20)

Flyttkonstruktor

Bar bm{Bar{}};

Bar b{};

Bar bem{std::move(b)};

Initierar ett objekt med hjälp av ett annat

Flyttar datamedlemmarna från det en objektet till det andra

Alltså bevaras inte original objektet

Båda objekten är i valid state efter operationen

Båda objekten är inte i ett specificerat state

Default genereras om ingen kopieringskonstruktor, operator och destruktor finns

(21)

Skriv din egen konstruktor

classBar { public:

Bar() : i{0} {}

Bar(Barconst&& rhs) : Bar(std::exchange(rhs.i, 0)) {

cout <<"Bar move constructor called"<< endl;

}

int get_i()const{ returni; }

private:

int i{};

};

intmain() {

Bar b{};

Bar bc{move(b)};

cout <<"end of main"<< endl;

}

(22)

Flyttilldelningsoperator

Bar bm{Bar{}};

Bar b{};

Bar bem = std::move(b);

Initierar ett objekt med hjälp av ett annat

Flyttar datamedlemmarna från det en objektet till det andra

Alltså bevaras inte original objektet

Båda objekten är inte i valid state efter operationen

Default genereras om ingen kopieringskonstruktor, operator och destruktor finns

(23)

Skriv din egen operator

classBar { public:

Bar() : i{0} {}

Bar(intn) : i{n} {}

Bar&operator= (Bar && rhs) {

cout <<"Before move"<< endl;

i = std::exchange(rhs.i, 0);

cout <<"After move"<< endl;

return*this;

}

int get_i()const{ returni; }

private:

int i{};

};

intmain() {

Bar b{};

Bar bc{move(b)};

cout <<"end of main"<< endl;

}

(24)

Destruktor Kopiering

Kopieringstilldelening Flytt

Flyttilldelning

2 När genereras de inte?

Rule of three Rule of six Rule of zero Djup kopiering Korrekt flytt

(25)

Rule of three

Om en klass har användardefinerat (non implicit) beteende för kopieringskonstruktor,

tilldelningsoperatorn eller destruktorn så behöver den nästan säkerligen användardefinerat beteende för alla tre.

- cppreference.com (översatt)

(26)

Varför?

Används i många olika situationer

Exempelvis vid parameteröverföring (by value)

Implicita varianter klarar inte råpekare (annat)

Leder ofta till inkorrekt beteende

Om du inte följer denna regel blir det förmodligen fel

(27)

Rule of five (six?)

Om en klass har användardefinerat beteende för kopiering, tilldelning och destruering så förhindrar det implicit definition av flyttkonstruktor och

fylttilldelningsoperator. Därför måste man definera sina egna om man vill ha flyttsematik.

(28)

Varför

C++ använder flyttsemantik för att optimera programkod

Det är mycket effektivare beteende att flytta ett objekt än att kopiera det

Om defaultbeteende inte duger för de tre tidigare duger det inte för flytt heller

Blir förmodligen inte fel, men dåligt optimerat

(29)

Rule of zero

Om du kan, undvik att definera några speciella medlemsfunktioner.

(30)

Extra hänsynstagande

På grund av hur arv fungerar (se nästa

labb/föreläsning) förenklas ofta dessa regler till följande:

Om du definerar eller tar bort en av dessa funktioner, definera eller ta bort alla 5.

Alla dessa regler kommer från cpp core guidlines som man kan titta i om man vill:

https://github.com/isocpp/CppCoreGuidelines

(31)

= default, = delete

Nyckelord för att explicit berätta att speciella medlemsfunktioner inte finns eller har defaultbeteende

//Bar.h //...

classBar { public:

Bar() =delete; //No default constructor

~Bar() =default; //Default destruktor

Bar(Bar&) = delete; //No copy constructor

Bar(Bar&&); //User-defined move constructor

Bar&operator=(Bar&) = delete; //No copy assignment operator Bar&operator=(Bar&&); //User-defined move assignment //..

};

(32)

Djup kopiering

Blir kopian rätt?

Pass by value

#include <iostream>

using namespacestd;

classBar { public:

Bar(): ptri{nullptr} {}

Bar(int* ptr): ptri{ptr} {}

int* get_ptr() {return ptri; } private:

int* ptri;

};

int main() {

Bar b{new int{5}};

Bar bc{b};

++(*b.get_ptr());

cout << *b.get_ptr() << *bc.get_ptr() << endl;

cout <<"end of main"<< endl;

}

(33)

Djup kopiering

Blir kopian rätt?

#include <iostream>

using namespacestd;

class Bar { public:

Bar(): ptri{nullptr} {}

Bar(int* ptr): ptri{ptr} {}

Bar(Barconst& rhs): Bar{new int{*rhs.get_ptr()}} {}

int* get_ptr() const{ returnptri; } private:

int* ptri;

};

int main() {

Bar b{new int{5}};

Bar bc{b};

++(*b.get_ptr());

cout << *b.get_ptr() << *bc.get_ptr() << endl;

cout <<"end of main"<< endl;

}

(34)

Korrekt flytt

Hur implementerar vi korrekt flytt?

(35)

References

Related documents

En modellklass LifeModel i ett program som kan simulera denna utveckling lagrar den aktuella generationen i en tillståndsvariabel med deklarationen.. private

• För att programmet ska kunna reagera på händelser kopplar man på olika typer av lyssnare till komponenter i fönstret. • Lyssnarna kan fånga upp händelser som

I Detta innebär i förlängningen att funktioner deklarerade med static inte kan komma åt variabler deklarerade utan static eftersom det inte är specificerat vilken

• Kompilatorn automatiskt det som behövs för att varje variabel får sin egen plats i minnet och att bitmönstret där tolkas enligt datatypen vi valt.. • En

förändring sker, antingen när någon läser från den, eller när någon skriver

MEN i fall där objekt av våra egna klasstyper har pekare som datamedlemmar, framförallt pekare till manuellt allokerat minne så kommer vi behöva definera hur detta ska fungera i

Idéen med en klass är att kunna modellera alla objekt som har samma uppsättning attribut och samma beteenden på en och samma gång, istället för att modellera varje enskilt objekt

plats för en referens till ett objekt av typen SegelPlan plats för en referens till ett objekt av typen String int length=0; // man kan ange ett initialt värde.