Elementi kontrole kopiranja

Svaki puta kada definiramo novi tip tada eksplicitno ili implicitno definiramo njegovo ponašanje prilikom kopiranja, pridruživanja i pri destrukciji. To činimo definiranjem sljedećih članova klase:

Konstruktor kopiranjem (CCtor) je konstruktor koji uzima jedan parametar tipa (konstantna) referenca na tip klase (u kojoj se CCtor nalazi). Koristi se eksplicitno kada se objekt konstruira i inicijalizira objektom istog tipa, te implicitno kod prijenosa parametara funkciji i u return naredbi.

Konstruktor premještanjem (MCtor) djeluje u istim situacijama kao i CCtor, ali samo na desnim vrijednostima.

Operator pridruživanja kopiranjem (OP-C) objektu s lijeve strane znaka jednakosti pridružuje vrijednost objekta s desne strane kopiranjem. Objekt na desnoj strani ostaje netaknut.

Operator pridruživanja premještanjem (OP-P) objektu s lijeve strane znaka jednakosti pridružuje vrijednost objekta s desne strane. Objekt na desnoj strani predaje svoje resurse objektu na lijevoj strani i ostaje dobar samo za destrukciju.

Destruktor se poziva automatski kada objekt izlazi iz dosega ili kad se dinamički alociran objekt briše iz memorije pomoću delete.

Budući da objekt bilo kojeg tipa mora imati mogućnost kopiranja prevodilac će sintetizirati neke elemente kontrole kopiranja (CCtor, MCtor, OP-C, OP-M i destruktor) ako ih sami ne definiramo.

Konstruktor kopiranjem

Konstruktor kopiranjem ili kraće konstrutor kopije (copy-constructor, CCtor) je konstruktor koji kreira objekt kopiranjem objekta istog tipa. Kao argument uzima referencu na objekt istog tipa.

Napomena Ako u klasi ne definiramo copy constructor, a u kodu se javlja potreba za njim, prevodilac će ga sintetizirati.

Kako djeluje sintetizirani CCtor?

Sintetizirani CCtor će izvršiti kopiranje svih nestatičkih varijabli članica tako što će

  • kod ugrađenih tipova kopirati varijablu;
  • kod članica tipa klase pozvati njihov CCtor da obavi kopiranje (definicija je dakle rekurzivna);
  • Ako je polje varijabla članica onda će CCtora obaviti kopiranje polja član-po-član.

Pozivanje CCtora

Poziva u ovim situacijama:

Pogledajmo primjer jednostavne klase MagicNo koja se može zadovoljiti sa kontrolom kopiranja koju sintetizira prevodilac.

Primjer:

Jednostavna klasa koja definira samo konstruktor. Konstruktor kopiranjem je sintetizirao prevodilac.

class MagicNo{
    public:
       MagicNo(const std::string& name, const int& value):
           data_name(name), data(value) {}
     ....
    private:
       std::string data_name;
       int         data;
};
//    ....
MagicNo no1("Magic number", 117); // Poziv konstruktoru
MagicNo no2(no1);                 // Poziv konstruktoru kopije
MagicNo no3 = no2;                // Poziv konstruktoru kopije

Nakon definicije klase definirali smo tri objekta tipa MagicNo. Prvi je konstruiran konstruktorom koji uzima dva parametra. Drugi i treći su konstruirani CCtorom; no2 je kopija no1, a no3 je kopija no2.

Definicija CCtora

Definirajmo u klasi MagicNo jedan CCtor koji se ponaša isto kao i sintetizirani, te jedan konstruktor koji uzima jedan argument tipa int i koji se može koristiti u implicitnim konverzijama:

#include <string>
#include <iostream>
#include <vector>


class MagicNo{
    public:
       MagicNo(const int& value):data_name("NoName"), data(value) // Ctor1
         {std::cout << "Ctor1"<<std::endl;}

       MagicNo(const std::string& name, const int& value):         // Ctor2
           data_name(name), data(value) { std::cout << "Ctor2"<<std::endl;}

       MagicNo(const MagicNo& obj);  // CCtor
    private:
       std::string data_name;
       int         data;
};
// u .cpp datoteci
 MagicNo::MagicNo(const MagicNo& obj):
     data_name(obj.data_name), data(obj.data)
{
     std::cout << "CCtor"<< std::endl;
}

Prijenos parametara po vrijednosti

Primjer: Imamo funkciju:

MagicNo f(MagicNo A)
{
   std::cout << "Unutar funkcije f " << std::endl;
   return A;
}

i poziv:

no1 = f(no2);

Dešava se sljedeće:

Optimizacija prevodioca (copy elision)

Što se dešava u ovoj liniji koda?

MagicNo no4 = 3

Prvo se poziva Ctor s jedim argumentom (3) i kreira se privremeni objekt. Zatim se poziva CCtor koji kreira no4. U ovakvoj situaciji prevodioc može izbjeći konstrukciju privremenog objekta i konstruirati no4 direktno pomoću poziva Ctor-u s jednim argumentom. Prevodioci to redovito rade. Efekt je kao da smo napisali:

 MagicNo no4(3);

Primjer. U čemu je razlika ako pozovemo funkciju

MagicNo f(MagicNo A);

na ovaj način:

no1 = f(no2);

ili na ovaj način:

MagicNo no5 = f(no2);
Napomena g++ ima opciju -fno-elide-constructors pomoću koje se optimizacija konstruktora može spriječiti.
Napomena U osnovi kad se vraća lokalni objekt u return naredbi kao u prethodnom primjeru prevodilac može izbjeći korištenje konstruktora kopije i direktno konstruirati objekt u memoriji rezerviranoj za dolazni objekt (no ta optimizacija ovisi o prevodiocu).
Napomena U standardu C++-17 elizija kopiranja je postala obavezna u ovakvim situacijama i više nije optimizacija prevodioca.

Prijenos parametra po referenci

Želimo li izbjeći nepotrebne pozive CCtoru i destruktoru treba parametar funkciji predati "po referenci". Budući da se stvarni argument pri tome ne kopira nema niti poziva CCtoru i Dtoru. Ako definiramo

MagicNo g(const MagicNo& A)
{ std::cout << "Unutar funkcije g " << std::endl; return A;}

Sada kod

std::cout << "Pozivam f" << std::endl;
no2 = f(no1);
std::cout << "f gotov" << std::endl;
std::cout << "Pozivam g" << std::endl;
no2 = g(no1);
std::cout << "g gotov" << std::endl;

daje

Pozivam f
CCtor
Unutar funkcije f
CCtor
operator =
Dtor
Dtor
f gotov
Pozivam g
Unutar funkcije g
CCtor
operator =
Dtor
g gotov

Kopiranje povratne vrijednosti

Kad se radi o povratnoj vrijednosti ovakva optimizacija nije uvijek moguća. Treba zapamtiti sljedeće pravilo:

Napomena Funkcija nikad ne smije vratiti pokazivač ili referencu na lokalnu varijablu.

Inicijalizacija spremnika

  • Ako pri inicijalizaciji spremnika zadamo samo broj elemenata, onda se za inicijalizaciju svakog pojedinog člana spremnika koristi defaultni konstruktor.
  • Ako sve elemente inicijaliziramo jednim zadanim elementom, onda se poziva CCtor.
std::cout << "vector s tri elementa:" << std::endl;
std::vector<MagicNo> niz1(3,no1);
std::cout << "vector s tri elementa + konverzija:" << std::endl;
std::vector<MagicNo> niz2(3,4);
std::cout << "polje s tri elementa:" << std::endl;
MagicNo niz3[] ={ no1, no2, no3};

Onemogućavanje kopiranja

Postoje situacije kada ne želimo dozvoliti kopiranje. Objekt tada ne možemo po vrijednosti predati funkciji niti ga iz nje vratiti. Takvi objekti su na primjer streamovi iz iostream biblioteke.

Onemogućavanje kopiranja je vrlo jednostavno. Imamo dva načina:

class B{
  public:
    B() : x(0) {}
    B(B const &) = delete;  // CCtor
  private:
    int x;
};

Prevodilac će sada javiti grešku (use of deleted function) ukoliko eksplicitno ili implicitno pukušamo pozvati CCtor.

class A{
  public:
    A() : x(0) {}
  private:
    int x;
    A(A const &);  // CCtor
};

Prevodilac ili linker će sada javiti grešku ako dođe do poziva CCtora.

Kopiranje klase s pokazivačkom varijablom članicom

Kopiranje postaje problematično u slučaju kada klasa sadrži pokazivač na dnamički alociranu memoriju. Pogledajmo klasu,

class Dangle{
    public:
        Dangle(){ p= new int(0); }
        ~Dangle() { delete p; } // Destruktor. Dealokacija memorije.
    private:
        int * p;
};

Resurse koje instance klase trebaju osiguravamo i inicijaliziramo u konstruktoru, a oslobađamo u destruktoru. To je opći princip konstrukcije klasa i naziva se RAII (Resource Acquisition Is Initialization).

Zamislimo sada situaciju u kojoj kreiramo novi objekt tipa Dangle iz već postojećeg, pomoću sintetiziranog CCtora:

void f()
{
    Dangle a;
    Dangle b = a; // Pozovi sintetizirani CCtor
}

Nakon što oba objekta izađu iz svog dosega pozivaju se njihovi destruktori, koji pozivaju operator delete na pokazivaču objekta. Tu dolazi do dvostrukog pozivanja operatora delete na istom pokazivaču što predstavlja sigurnu grešku!

Kako ispravno konstruirati takve klase pokazat ćemo malo kasnije.

Operator pridruživanja - kopiranjem

Operator pridruživanja mora biti funkcija članica klase. On se brine za pridruživanje objekata dane klase. Definira se tako da uzima jedan parametar koji je objekt na desnoj strani naredbe pridruživanja (desni operand); taj je parametar prirodno konstantna referenca na tip klase. Povratna vrijednost je referenca na tip klase i ona odgovara lijevom operandu — onom kojem se pridružuje. Vrijedi pravilo:

Napomena Ako u klasi ne definiramo operator pridruživanja, a u kodu se javlja potreba za njim, prevodilac će ga sintetizirati.

Sintetizirani OP vrši pridruživanje član po član na način kompatibilan sa sintetiziranim CCtorom. Svi ugrađeni tipovi bit će kopirani; polja će biti kopirana član-po-član kao i u CCtoru. Članice korisničkog tipa pridružuju se pozivanjem operatora pridruživanja iz njihove klase. Taj može biti eksplicitno definiran ili sintetiziran.

Primjer: Ovdje klasi MagicNo dodajemo operator pridruživanja koji djeluje kao sintetizirani OP.

Nastavak

class MagicNo{
    public:
       MagicNo(const int& value):data_name("NoName"),
                                 data(value) // Ctor1
         {std::cout << "Ctor1"<<std::endl;}

       MagicNo(const std::string& name, const int& value): // Ctor2
           data_name(name), data(value)
         { std::cout << "Ctor2"<<std::endl;}

       MagicNo(const MagicNo& obj);  // CCtor

       MagicNo& operator=(const MagicNo& obj);   // OP
    private:
       std::string data_name;
       int           data;
};

// u .cpp datoteci
MagicNo& MagicNo::operator=(const MagicNo& obj)
{
     data_name = obj.data_name;
     data = obj.data;
     std::cout << "operator = "<< std::endl;
     return *this;
}

Sintaksa

Sintaksa poziva operatora pridruživanja je prirodna. U liniji

no1 = no2;

vrijednost objekta no2 pridružuje se objektu no1. Ta je sintaksa samo pokrata za ekvivalentnu sintaksu:

no1.operator=(no2);

OP vraća nekonstantnu referencu

MagicNo no1("Number 1", 117);
MagicNo no2("Number 2", 63);
MagicNo no3("Number 3", 249);
//     ....
(no2 = no1) = no3;

Zadatak: Što bi se desilo u gornjem kodu da smo OP definirali tako da vraća objekt umjesto reference?

MagicNo operator=(const MagicNo& obj);   // OP, GREŠKA

Prevodilac može odbiti sintetizirati OP

Pravilo je da će prevodilac sintetizirati OP ako ga nismo definirali. To pravilo ima iznimaka, tj. postoje situacije u kojima će prevodilac odbiti sintetizirati OP za nas. To će se desiti u situacijama kada ponašanje sintetiziranog OP nije jednoznačno.

Tri situacije u kojima će prevodilac odbiti sintetizirati OP su sljedeće:

C++11: U gornjim situacijama prevodilac će sintetizirati obrisani OP. Ukupni efekt je isti, jedino što će prilikom pokušaja korištenja OP prevodilac javiti da je OP obrisan (deleted).

Slično pravilo vrijedi i za defaultni konstruktor: Sintetizirani defaultni konstruktor će biti obrisan u sljedećim situacijama:

Konstruktor kopije će biti sintetiziran kao obrisan samo ako klasa ima članicu koja ima nedostupan/obrisan CCtor pa se ne može kopirati.

Destruktor će biti sintetiziran kao obrisan samo ako klasa ima članicu koja ima nedostupan/obrisan destruktor.

OP i CCtor dolaze u paru

Ako je zadovoljavajući sintetizirani CCtor, onda je zadovoljavajući i sintetizirani OP, i obratno. Kada moramo definirati svoj OP onda redovito moramo definirati i CCtor, i obratno.

Pogledajmo na kraju kako bi trebalo korigirati klasu Dangle da postane ispravna:

class NoDangle{
    public:
       NoDangle() : p(new int(0)) {}
       NoDangle(const NoDangle& rhs) : p(new int(*rhs.p)) {}
       NoDangle& operator=(NoDangle &rhs){
           if(rhs.p != p) {
               delete p;
               p = new int(*rhs.p);
           }
           return *this;
       }
       ~NoDangle() { delete p; }
    private:
        int * p;
};

Destruktor

Destruktor je posebna funkcija članica klase koja se poziva automatski kod destrukcije objekta. Zadatak destruktora je osloboditi resurse koje je objekt rezervirao (file handles, socets, itd.), osloboditi dinamički alociranu memoriju itd.

Kada se poziva destruktor?

  • Kada je doseg objekta lokalan destruktor se poziva u trenutku kada objekt izlazi iz svog dosega.
  • Ako je objekt kreiran pomoću operatora new, destruktor će biti pozvan kad se pozove operator delete.

Važno je uočiti da se destruktor ne poziva kada referenca ili pokazivač izlazi iz dosega. To se dešava samo kada objekt (a ne referenca) izlazi iz dosega i kada se pozove delete na pokazivaču.

Spremnici kao što su polja i STL spremnici uništavaju se kada izlaze iz dosega. Pri tome, ako su im članovi tipa klase, onda se poziva destruktor na svakom članu spremnika, u redosljedu suprotnom od onog kojim su elementi konstruirani. Isti postupak se dešava s dinamički alociranim poljima na kojima je pozvan delete [].

Sintetizirani destruktor

Napomena Ako ne definiramo vlastiti destruktor prevodilac će uvijek sintetizirati destruktor za nas.

Sintetizirani destruktor uništava nestatičke objekte u poretku suprotnom od onog u kojem su konstruirani, a to znači u poretku suprotnom od onog kojim su u klasi deklarirani. Na svakom članu tipa klase poziva njegov destruktor.

Pisanje vlastitog destruktora

  • Klase koje ne alociraju resurse (memoriju i druge resurse) ne moraju definirati svoj destruktor. Sintetizirani će biti dovoljan da obavi posao.
  • Pravilo trojice: ako imamo potrebu napisati eksplicitni destruktor, onda moramo napisati i CCtor i OP.

Primjer (ovdje nemamo potrebu za destruktorom, samo ilustriramo sintaksu):

Nastavak

class MagicNo{
    public:
       MagicNo(const int& value):data_name("NoName"), data(value) // Ctor1
         {std::cout << "Ctor1"<<std::endl;}

       MagicNo(const std::string& name, const int& value):         // Ctor2
           data_name(name), data(value) { std::cout << "Ctor2"<<std::endl;}

       MagicNo(const MagicNo& obj);  // CCtor

       MagicNo& operator=(const MagicNo& obj);

       ~MagicNo(){ std::cout << "Dtor"<<std::endl;}

    private:
       std::string data_name;
       int        data;
};

Prazna klasa nije prazna

Ako definiramo praznu klasu

class Empty {};

dobivamo klasu oblika:

class Empty{
    Empty(){...}
    Empty(const Empty& rhs){...}
    ~Empty(){...}
    Empty& operator=(const Empty& rhs){...}
};

Primjer: Vektor varijabilne duljine: predložak klase Vec

Predložak Vec<T> sadržava dinamički alociran vektor proizvoljne duljine. Polje alociramo dinamički u konstruktoru, a dealociramo ga u destruktoru. Imamo još jednu dodatnu varijablu mime koja može predstavljati ime fizičke veličine koju vektor drži.

template <typename T>
class Vect
{
    public:
        using  Index =  unsigned int;

        explicit Vect(Index n = 0, T v=0.0, std::string ime="");
        ~Vect(){ delete [] mdata; std::cout << "Dtor" << std::endl; }

        Vect(const Vect& v);                            // CCtor
        Vect& operator=(const Vect& v);                 // OP

        T operator[](Index i) const { return mdata[i]; }
        T& operator[](Index i) { return mdata[i]; }

        Index size() const { return msize; }
        T two_norm() const;
        std::string get_name() const { return mime; }

    private:
        Index   msize;
        T *mdata;
        std::string  mime;
};

Dealokacija

Dealokacija memorije se dešava kod poziva destruktora, dakle kada objekt izlazi iz dosega ili kada se pozove delete na dinamički alociranom objektu.

int main()
{
    Vec<double> x(5,2.0);
    Vec<double> * px = new Vect(1024,1.9);

    delete px; // Poziv destruktor iz klase Vect.
    // x izlazi iz dosega -> poziv destruktora iz klase Vect.
    return EXIT_SUCCESS;
}

Destruktor je dan u samoj klasi (i stoga je implicitno inline):

~Vect(){ delete [] mdata; std::cout << "Dtor" << std::endl; }

Konstruktor kopije

Dizajn konstruktora kopije posve je izravan. Alociramo novu memoriju za vektor i kopiramo članove iz danog vektora.

template <typename T>
Vect<T>::Vect(const Vect& v): msize(v.size()), mdata(new T[v.size()]), mime(v.get_name())
{
    std::cerr << "C-Ctor"<<std::endl;
    for(Index i=0; i < msize; ++i)
        mdata[i]=v.mdata[i];
}

Operator pridruživanja

Kod operatora pridruživanja moramo paziti na dvije stvari:

  • Da dealociramo memoriju vektora na lijevoj strani.
  • Da dobro tretiramo samopridruživanje.
template <typename T>
Vect<T>& Vect<T>::operator=(const Vect& v)
{
    std::cerr << "OP"<<std::endl;
    if(mdata != v.mdata) // Vektori su jednaki ako pokazuju na istu memorijsku lokaciju
    {
        delete [] mdata;
        mdata = new T[v.msize]; // bad_alloc ako ne uspije
        msize = v.msize;
        mime = v.mime;
        for(Index i=0; i < msize; ++i)
            mdata[i]=v.mdata[i];
    }
    return *this;
}

Zadatak. Implementirajte sve ostale metode iz klase Vec<T>. Optimizirajte operator pridruživanja tako da ne dealocira memoriju ako to nije nužno.

Valgrind

Gubitak memorije predstavlja ozbiljan problem u C++ programima. Stoga su razvijeni različiti alati koji ga mogu detektirati. U Linux okruženju može se koristiti valgrind program http://www.valgrind.org/. Izvršavanje programa (recimo a.out) vrši se "kroz" valgrind koji prati izvršavanje programa i izvještava o greškama u radu s memorijom:

valgrind --tool=memcheck --leak-check=yes ./a.out

Pretpostavimo da smo iz klase Vect uklonili destruktor. Dio poruke koju valgrind daje mogla bi izgledati ovako:

==4128== 8,272 bytes in 3 blocks are definitely lost in loss record 1 of 1
==4128==    at 0x4024B9C: operator new[](unsigned) (vg_replace_malloc.c:195)
==4128==    by 0x804921C: Vect::Vect(int, double, std::string) (in /home/jurak/mytext/{cxx}/klase-pr/a.out)
==4128==    by 0x8048B5A: main (in /home/jurak/mytext/{cxx}/klase-pr/a.out)
==4128==
==4128== LEAK SUMMARY:
==4128==    definitely lost: 8,272 bytes in 3 blocks.
==4128==      possibly lost: 0 bytes in 0 blocks.
==4128==    still reachable: 0 bytes in 0 blocks.
==4128==         suppressed: 0 bytes in 0 blocks.

Vidimo da valgrind detektira klasu koja je alocirala memoriju koja nije dealocorana.

Konstrukcija i pridruživanje premještanjem - problem

Primjer 1. Napravimo prvo mali test program koji mjeri (posredno) vrijeme potrebno za kreiranje i kopiranje lokalnog objekta.

#include <chrono>  // za mjerenje vremena
#include "vector.h"

// Funkcija koja vraća lokalnu kopiju
template <typename T>
Vec<T> make_square(Vec<T> const & in) {
    Vec<T> y(in.size());
    for(typename Vec<T>::index i = 0; i < in.size(); ++i)
       { y[i] = in[i]*in[i]; }
    return y;
}

using namespace std::chrono;

int main()
{
  Vec<double> x(500000,2.0), y;

  auto start = system_clock::now(); // Pokreni sat.
  y = make_square(x);
  auto stop = system_clock::now();  // Ponovo startanje sata

  auto duration = duration_cast<microseconds>( stop - start); // vremenska razlika
  std::cout << "Time for the call = " << duration.count()
            << " micro sec" << std::endl; // u mikrosekundama

  return 0;
}

Rezultat izvršavanja je:

Ctor
Ctor
Ctor
OP
Dtor
Time for the call = 6898 micro sec
Dtor
Dtor

Ovdje je došlo do elizije CCtora u return naredbi.

Napomena. Neefikasnost ovog koda je u pozivu operatoru pridruživanja koji kopira vektor y, lokalno definiran unutar metode make_square, u vektor y definiran unutar metode main. Vektor y iz metode make_square je iskopiran član po član u vektor y unutar metode main, a zatim je uništen prolazom destruktora. Prirodnije je da vektor y unutar metode main jednostavno preuzme dinamički alociranu memoriju vektora y iz metode make_square jer se na taj način izbjegava nepotrebna alokacija i dealokacija memorije.

Nastavak

Primjer 2: Proučite broj poziva konstruktoru kopije i destruktoru u sljedećem kodu:

Vect<double> a1(3,4.0), a2(3,2.0), a3(4,1.0), a4(4,-1.0);
std::vector<Vect<double>> collection;

std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a1);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a2);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a3);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a4);
std::cout << "capacity = " << collection.capacity() << std::endl;

Rezultat:

Ctor
Ctor
Ctor
Ctor
capacity = 0
Ubaci element:
C-Ctor
capacity = 1
Ubaci element:
C-Ctor
C-Ctor
Dtor
capacity = 2
Ubaci element:
C-Ctor
C-Ctor
C-Ctor
Dtor
Dtor
capacity = 4
Ubaci element:
C-Ctor
capacity = 4
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor

Objašnjenje. Vektor počinje svoj život kao prazan. Kad ubacimo prvi element u njega on alocira memoriju za jedan element i kopira element u svoju memoriju (poziv C-Ctoru). Kada ubacujemo drugi element za njega nema mjesta u vektoru. Tada std::vector alocira memoriju za dva objekta, kopira prvi element na novu memorijsku lokaciju i zatim kopira novoubačeni element iza prvog elementa. Prvi element na staroj lokaciji se uništava pozivom destruktora. Kod ubacivanja trećeg elementa ponovo nema dovoljno mjesta u vektoru i postupak se ponavlja. Sada se alocira memorija za 4 elemenata na novoj lokaciji, prva dva elementa se kopiraju sa stare lokacije i ubacuje se treći element. Prva dva elementa na staroj lokaciji se uništavaju prolazom destruktora. Sada za četvrti element imamo slobodno mjesto pa pri njegovom ubacivanju nema nove realokacije. Jasno je da su u slučaju objekata (Vect) koji dinamički alociraju memoriju sva ta silna kopiranja nepotrebna i neefikasna.

Konstruktor kopije premještanjem i operator pridruživanje premještanjem

Neefikasnost u prehodna dva primjera (i drugim) možemo izbjeći dodavanjem dva nova operatora svakom tipu kod kojeg premještanje može biti efikasnije od kopiranja.

Obje ove funkcije uzimaju argument tipa desne reference što znači da djeluju na privremenim bezimenim objektima koji su na kraju svog životnog vijeka i stoga se njihovi resursi mogu preuzeti.

Puna kontrola kopiranja u klasi Vect ima ovaj oblik:

template <typename T>
class Vec
{
    public:
        using  Index = std::size_t;

        explicit Vect(Index n = 0, T v=0.0, std::string ime="");

        ~Vec(){  std::cerr << "Dtor"<<std::endl;  delete [] mdata;}

        Vec(const Vec& v);                            // CCtor
        Vec(Vec && v) noexcept;                       // MCtor

        Vec& operator=(const Vec& v);                 // OP
        Vec& operator=(Vec && v) noexcept;            // MOP

       // ...
};

Konstruktor kopije premještanjem: Implementacija

template <typename T>
Vect<T>::Vect(Vect && v) noexcept : msize(v.msize), mdata(v.mdata), mime(std::move(v.mime))
{
    std::cerr << "M-Ctor"<<std::endl;
    v.msize = 0;
    v.mdata = nullptr;
}

Operator pridruživanje premještanjem: Implementacija

  • Resursi se preuzimaju na isti način kao i u konstruktoru.
  • Moramo paziti na samopridruživanje zbog koda oblika: x = std::move(x);
  • Objekt s desne strane mora ostati u stanju pogodnom za prolaz destruktora (pokazivač obavezno na nullptr).
template <typename T>
Vect<T>& Vect<T>::operator=(Vect && v) noexcept
{
    std::cerr << "M-OP"<<std::endl;
    if(this != &v) // samopridruživanje je moguće ako se koristi std::move
    {
        delete [] mdata;
        mdata = v.mdata;
        msize = v.msize;
        mime = std::move(v.mime);
        v.mdata = nullptr;
        v.msize = 0;
    }
    return *this;
}

Primjer 1. Isti kao i ranije:

#include <chrono>  // za mjerenje vremena

template <typename T>
Vec<T> make_square(Vec<T> const & in)
{
    Vec<T> y(in.size());
    for(typename Vec<T>::index i = 0; i < in.size(); ++i)
       { y[i] = in[i]*in[i]; }
    return y;
}

using namespace std::chrono;

int main()
{
  Vec<double> x(500000,2.0), y;

  auto start = system_clock::now(); // Pokreni sat.
  y = make_square(x);
  auto duration = duration_cast<microseconds>(
                                      system_clock::now() - start
                                              ); // vremenska razlika
  std::cout << "Time for the call = " << duration.count()<< " micro sec" << std::endl; // u milisekundama

  return EXIT_SUCCESS;
}

Rezultat izvršavanja je:

Ctor
Ctor
Ctor
M-OP
Dtor
Time for the call = 5111 micro sec
Dtor
Dtor

Budući da tip koji vraćamo iz funkcije make_square ima operator pridruživanja premještanjem on je iskorišten umjesto operatora pridruživanja kopiranjem. Time smo uštedjeli dealokaciju i alokaciju 500000 elemenata tipa double.

Primjer 2. Spremanje u std::vector: U programu

Vect<double> a1(3,4.0), a2(3,2.0), a3(4,1.0), a4(4,-1.0);
std::vector<Vect<double>> collection;

std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a1);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a2);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a3);
std::cout << "capacity = " << collection.capacity() << std::endl;
std::cout << "Ubaci element:\n";
collection.push_back(a4);
std::cout << "capacity = " << collection.capacity() << std::endl;

sada dobivamo sljedeći rezultat iz kojeg je vidljivo da vektor vrši realokaciju pomoću konstruktora kopije premještanjem:

Ctor
Ctor
Ctor
Ctor
capacity = 0
Ubaci element:
C-Ctor
capacity = 1
Ubaci element:
C-Ctor
M-Ctor
Dtor
capacity = 2
Ubaci element:
C-Ctor
M-Ctor
M-Ctor
Dtor
Dtor
capacity = 4
Ubaci element:
C-Ctor
capacity = 4
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor
Dtor

Iz ispisa vidimo da pri premještanju starih elemenata na nove lokacije std::vector koristi operator pridruživanja premještanjem i na taj način štedi na alokaciji i dealokaciji memorije.

Napomena: std::vector garantira da polazni vektor neće biti promijenjen ako realokacija elemenata ne uspije. To je lako osigurati s kopiranjem jer kopiranje ne mijenja polaznu vrijednost. Da bi se moglo osigurati s premještanjem, konstruktor kopije premještanjem i operator pridruživanja premještanjem moraju garantirati da neće izbaciti izuzetak. Ukoliko nema takve garancije std::vector će pri realokaciji koristiti konstruktor kopije kopiranjem i operator pridruživanja kopiranjem. Zbog toga operatori premještanjem moraju biti deklarirani noexcept ukoliko želimo da budu efikasno iskorišteni.

Zaključci, sintetizirano premještanje:

Još o povratnoj vrijednosti funkcije

Vect &&  f(Vect & x){
    Vect y = x.scale(2);
    return std::move(y);
}

Vect z = f(x);       // Greška
Vect && z1 = f(x);   // Greška

Jedina mogućnost takvog koda je ovog tipa:

Vect &&  g(Vect && x){
        x.scale(2);
        return std::move(x);
}
x = g(std::move(x));  // OK
Vect f(Vect & x){
     Vect y = x.scale(2);
     return std::move(y);
}

Vect z = f(x);

Klase s pokazivačima

Problem

Programski jezik C++ nema sakupljača smeća (eng. garbage collector) kao neki drugi jezici (npr. Java) i stoga programer mora brinuti o dealokaciji dinamički alocirane memorije. Sljedeći primjer pokazuje jednu factory metodu. To je metoda koja dinamički alocira objekt i vraća pokazivač na njega.

class BFC{
    public:
        explicit BFC(int a = 0) : aa(a) {}
        int get() const { return aa; }
        void set(int a) { aa=a; }
        // .......
        ~BFC() { cout << "BFC(" << get() << ") is dead."<< endl; }
    private:
        int aa;
        // ....
};
BFC * factoryBFC(int i)
{
    // ...
    // Dinamički alociramo objekt
    BFC * pbfc = new BFC(i);
    // ...
    // Vraćamo pokazivač na dinamički alociran objekt
    return pbfc;
}

Problem - moramo brinuti o dealokaciji

Odgovornost je pozivnog programa da dealocira memoriju kada je gotov s objektom koji je dobio od factory metode.

void f()
{
    BFC *a = factoryBFC(1);
    BFC *b = factoryBFC(2);
    // ....
    cout << "a = " << a->get() << endl;
    cout << "b = " << b->get() << endl;

    // Dealociram memoriju alociranu u factoryBFC
    delete a;
    // Zaboravio uništiti b ! -- memory leak
}

Napomena. Ovo rješenje je primjer programskog principa koji se naziva Resource Acquisition Is Initialization (RAII). Prema tom principu objekt prikuplja potrebne resurse u konstruktoru, a oslobađa ih u destruktoru, što automatizira oslobađanje resursa.

unique_ptr<T>

Standardna biblioteka nam nudi klasu unique_ptr<T> koju možemo koristiti za sprečavanje gubljenja memorije. Dekarirana je u zaglavlju <memory>.

Klasu koristimo na sljedeći način: Ako imamo pokazivač na tip T za koji želimo da se automatski dealocira pri izlasku iz dosega predajemo ga konstruktoru klase unique_ptr<T>.

Objekt klase unique_ptr<T> koristimo na isti način kao i pokazivač. To je moguće stoga što je klasa unique_ptr<T> tzv. pokazivački objekt, odnosno u njoj su preopterećeni operatori * i ->.

void f()
{
    unique_ptr<BFC> a( factoryBFC(1) );
    unique_ptr<BFC> b( factoryBFC(2) );
    // ....
    cout << "a = " << a->get() << endl;
    cout << "b = " << b->get() << endl;

    // Dealokacija memorije je sada automatska
}

Na izlasku iz funkcije f() više ne moramo brinuti o dealokaciji memorije. Nju će obaviti destruktor klase unique_ptr<BFC> koji poziva delete na pokazivaču.

Pametni pokazivač

Objekt koji možemo koristiti kao pokazivač (dereferencirati ga i primijeniti operator → na njega) naziva se pametni pokazivač. Pametan je jer obično ima dodatne sposobnosti koje pokazivači nemaju. Kažemo još da su pametni pokazivači objekti sa semantikom pokazivača.

unique_ptr<T> i kopiranje

Klasa unique_ptr<T> implementira koncept ekskluzivnog vlasništva nad objektom.

To se postiže time što unique_ptr<T> nema konstruktora kopije kopiranjem niti operatora pridruživanja kopiranjem.

Konstrukcija

  • Dodijeljeni konstruktor kreira neinicijalizirani pametni pokazivač (onaj koji drži nullptr).
  • Operator konverzije u bool dozvoljava ispitivanje je li objekt prazan ili drži netrivijalni pokazivač.
std::unique_ptr<int> up1, up11(nullptr);  // objekti bez pokazivača
std::unique_ptr<int> up2(new int(4));

std::cout << "*up2 = " << *up2 << "\n";

if(up1)
    std::cout << "*up1 = " << *up1 << "\n";
else
    std::cout << "up1 je prazan.\n";
  • Ako se ivrši premještanje pametnog pokazivača, pokazivač na desnoj strani prepušta resurs pokazivaču na lijevoj strani. Pokazivač na lijevoj strani prijue toga oslobađa svoj resurse (poziva delete na svom pokazivaču).
up1 = std::move(up2);
assert(up2 == nullptr);

Ekvivalentan kod je

up1.reset(up2.release());
assert(up2 == nullptr);

Imamo sljedeće metode:

Poziv Značenje

a.get()

Vraća pokazivač koji a drži.

a.release()

Vraća pokazivač koji drži a i interno postavlja svoj pokazivač na nul-pokazivač. Objekt a se time odrekao vlasništva koje mora prihvatiti drugi objekt (varijabla ili pametni pokazivač).

a.reset()

Briše objekt na koji a pokazuje.

a.reset(p)

Briše objekt na koji a pokazuje i inicijalizira svoj pokazivač sa p.

unique_ptr<T> i funkcije

// Funkcija konzumira i uništava UP.
void f(std::unique_ptr<int> pi){
        std::cout << "pi drži adresu " <<  pi.get() << "\n";
}

// Funkcija prima goli pokazivač, što znači da NIJE vlasnik objekta.
void f(int *pi){
        std::cout << "pi drži adresu " <<  pi << "\n";
}

// Funkcija se odriče vlasništva nad UPom.
std::unique_ptr<int> g(int n){
          std::unique_ptr<int>  pn(new int(n));
          return pn;
}

Te funkcije koristimo na ovaj način:

f(std::move(up1));  // umjesto CCtora koristimo M-Ctor
assert(up1 == nullptr);

f(up2.get());  // predaj goli pokazivač
assert(*up2 == 5);

up2 = g(5);
assert(*up2 == 5);

unique_ptr<T> kao članica klase

Običan pokazivač na dinamički alociranu memoriju unutar klase ima ovaj nedostatak:

unique_ptr<T> kao članica klase umjesto golog pokazivača garantira da će memorija biti dealocirana i ako je izbačen izuzetak u konstruktoru. Problem je tada sljedeći:

class X{
        std::unique_ptr<int>  pi;
        std::unique_ptr<char> pc;
public:
        X(int i, char c) : pi(new int(i)), pc(new char(c)) {}
        X(X const & x) : pi(new int(*x.pi)), pc(new char(*x.pc)) {}
        X & operator=(X const & x){
                *pi = *x.pi; // duboko kopiranje
                *pc = *x.pc;
                return *this;
        }
        // destruktor nije potreban
};

unique_ptr<T> i polja

std::unique_ptr<double[]> polje(new double[5]);
polje[3] = 17.0;
std::cout << "Adresa  polja = " << polje.get()
          << "; polje[3] = " << polje[3] << std::endl;

unique_ptr<T>. Završne napomene

Klasa unique_ptr<T> ima dodatni argument predloška koji ima dodijeljenu vrijednost.

namespace std {
// primary template:
template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
...
T& operator*() const;
T* operator->() const noexcept;
...
};
}

Klasa default_delete<T> poziva delete na pokazivaču. Zadavanjem parametra D to je ponašanje moguće izmijeniti.

Brojanje referenci

Ponekad, radi efikasnosti, imamo više pokazivača koji referiraju na isti objekt. Kada trebamo novu kopiju samo kopiramo pokazivač.

Kada je takvo ponašanje poželjno onda trebamo riješiti problem višestruke dealokacije objekta. Ponovo ćemo pokazivač zatvoriti u jednu klasu no u destruktoru klase nećemo nužno pozivati delete na pokazivaču. Tek kada posljednji pokazivač na objekt izlazi iz dosega, i ne ostaje niti jedan koji referira na dani objekt, zovemo delete.

Tehnika koja nam omogućava držanje više pametnih pokazivača s adresom istog objekta zove se brojanje referenci i implementirana je u ovoj klasi:

class SmartBFC{
    public:
        SmartBFC(BFC *p) : ptr(p), cnt(new int(1)) {}
        SmartBFC(const SmartBFC & orig): ptr(orig.ptr), cnt(orig.cnt)
                                       { ++*cnt; }
        SmartBFC& operator=(const SmartBFC&);
        ~SmartBFC(){ if(--*cnt == 0) { delete ptr; delete cnt; } }

        // Pomoćne funkcije
        const BFC * get() const { return ptr; }
        int         use_count() const { return *cnt; }
    private:
         BFC * ptr;
         int * cnt;
};

Naša implementacija dinamički alocira brojač referenci:

brojanje_ref.png

Napomena: Klasa SmartBFC nije pametni pokazivač jer ne implementira semantiku pokazivača. Te ćemo detalje popraviti naknadno.

Brojanje referenci - konstruktor kopiranjem

Kopiranje SmartBFC objekta:

cctor_br_ref.png
SmartBFC(const SmartBFC & orig): ptr(orig.ptr), cnt(orig.cnt) { ++*cnt; }
~SmartBFC(){ if(--*cnt == 0) { delete ptr; delete cnt; } }
const BFC * get() const { return ptr; }
int         use_count() const { return *cnt; }

Brojanje referenci - operator pridruživanja

Implementacija operatora pridruživanja:

op_br_ref.png
SmartBFC& SmartBFC::operator=(const SmartBFC& rhs)
{
     ++*rhs.cnt;
     if(--*cnt == 0) { delete ptr; delete cnt; }
     ptr = rhs.ptr;
     cnt = rhs.cnt;

     return *this;
}

Ovdje, da bismo se zaštitili od "samopridruživanja" prvo povećavamo brojač desne strane, a onda smanjujemo brojač lijeve strane. Dealokaciju objekta na lijevoj strani vršimo jedino ako je umanjeni brojač jednak nuli. Nakon toga kopiramo pokazivače. Uočimo da će kod pridruživanja oblika a=a doći prvo do povećanja, a zatim smanjenja brojača, te se tako izbjegava moguća dealokacije objekta.

Primjena

int main()
{
    SmartBFC spt1(new BFC(13));
    cout << "cnt (u spt1) =" << spt1.use_count() << endl; // 1
    SmartBFC spt2 = spt1; // CCtor
    cout << "cnt (u spt1) = " << spt1.use_count() << endl; // 2

    cout <<   spt1.get()->get() << endl;

    SmartBFC spt3(new BFC(33));
    spt1 = spt3;

    return 0;
}

Primjena operatora -> kao u ovom slučaju

cout <<   spt1.get()->get() << endl;

vrlo je nepraktična, no preopterećenje operatora -> će nam omogućiti da koristimo prirodnu sintaksu spt1->get(), kao da je spt1 pokazivač.

Napomena. U standardnoj biblioteci brojanje referenci je implementirano klasom shared_ptr<T>.

std::shared_ptr<T>

std::shared_ptr<T> je pametni pokazivač koji implementira brojanje referenci. Koristimo ga kada želimo imati više pokazivača koji pokazuju na isti element i brinu o njegovom vijeku trajanja. Definiran je u zaglavlju <memory>.

Konstrukcija

  • Konstruktor uzima pokazivač čiji postaje vlasnik.
  • Dodijeljeni konstruktor kreira prazan objekt (pokazivač na objekt je nullptr).
  • std::shared_ptr<T> ima konstruktore kopije i operator pridruživanja koji implementiraju brojanje referenci.
  • std::shared_ptr<T> je moguće konstruirati preuzimanjem resursa std::unique_ptr<T> objekta.
  • Funkcija std::make_shared<T> kreira pametni pokazivač na zadanu vrijednost. Interno kreira objekt pomoću operatora new. Svoje argumente predaje konstruktoru objekta.
#include <memory>
#include <iostream>

int main()
{
   std::shared_ptr<int> p1(new int(5));
   std::unique_ptr<int> u1(new int(2));
   std::shared_ptr<int> p2(std::move(u1) ); // inicijaliziraj shared pomoću
                                            // unique
   std::shared_ptr<int> p3{p1};  // CCtor

   p2 = p3;  // OP

   std::cout << p1.use_count() << std::endl;  // daje 3
   std::cout << std::boolalpha << p1.unique() << std::endl;  // false
   std::cout << *p1 << " : " << *p2 << " : " << *p3 << std::endl;
   std::cout << "adresa = " << p1.get() << std::endl;

 // Ako imao samo vrijednost koju želimo pohraniti u
   // shared_ptr koristimo make_shared.
   std::shared_ptr<int> p4 = std::make_shared<int>(11);
   std::cout << *p4 <<  std::endl;
   std::cout << "adresa = " << p4.get() << std::endl;
   return 0;
}

Neke od metoda klase std::shared_ptr<T> su iste kao i kod klase std::unique_ptr<T>:

Poziv Značenje

a.get()

Vraća pokazivač koji a drži.

a.unique()

Vraća true ako use_count() vraća 1 odnosno false u suprotnom.

a.use_count()

Vraća broj referenci, odnosno broj std::shared_ptr<T> objekata koji drže isti pokazivač.

a.reset()

Odriče se vlasništva nad objektom.

a.reset(ptr)

Objekt kojeg je do sada držao zamijenjuje s ptr.

  • std::shared_ptr<T> drži pokazivač na dinamički alociran objekt. Pogrešno mu je dati adresu objekta koji nije dinamički alociran! Ako imamo samo objekt, a želimo ga kontrolirati pomoću std::shared_ptr<T> objekta, treba koristiti funkciju std::make_shared<T> (iz zaglavlja <memory>).
  • Destruktor klase std::shared_ptr<T> poziva operatir delete na pokazivaču koji drži.
  • Metoda release() ne postoji.

std::shared_ptr<T> i polja

Ako pokazivač na polje želimo čuvati u std::shared_ptr<T> objekt, tada pri konstrukciji trebamo zadati deleter.

 std::shared_ptr<int>  spv(new int[10], [](int *p) { delete [] p; });
   for(std::size_t i=0; i<10; ++i)
       spv.get()[i] = i;

std::shared_ptr<T> i cirkularna zavisnost

Brojanje referenci uvodi mogućnost cirkularne zavisnosti.

Primjer. Klasa koja drži pametne pokazivače:

class Employee{
    public:
        std::string name;
        std::shared_ptr<Employee> head;
        std::vector<std::shared_ptr<Employee>> staff;

        Employee(std::string const & name_,
                 std::shared_ptr<Employee> head_ = nullptr) : name(name_),
                                                              head(head_)
        {}
        ~Employee(){ std::cout << name << " izbrisan\n"; }
        // nema potrebe za ručnom dealokacijom
     // ...
};

Funkcija za kreiranje instance klase:

std::shared_ptr<Employee> init(std::string const & name){
        std::shared_ptr<Employee> boss(new Employee(name + " boss"));
        std::shared_ptr<Employee> dept_boss(new Employee(name, boss));
        boss->staff.push_back(dept_boss);
        return dept_boss;
}

U glavnom programu …

int main()
{
   // ...
   std::shared_ptr<Employee> dboss = init("Damir");
   std::cout << dboss->name << " use_count = " << dboss.use_count() << std::endl;

   // Do dealokacije neće nikad doći!
   return 0;
}

Da ne dolazi do dealokacije možemo provjeriti pomoću valgrind-a

valgrind --tool=memcheck --leak-check=yes ./a.out

std::weak_ptr<T>

Klasa weak_ptr<T> omogućava dijeljenje dinamički alociranog objekta bez vlasništva nad objektom.

std::shared_ptr<int>  sp1(new int(7));
std::shared_ptr<int>  sp2(sp1);
std::weak_ptr<int> wp1(sp2);

std::cout << wp1.use_count() << std::endl;  // 2

if(wp1.expired())
    std::cout << "wp1 drži nullptr.\n";
else
    std::cout << "wp1 pokazuje na objekt.\n";

Klasa weak_ptr<T> ima minimalno sučelje i ne može direktno dohvatiti objekt na koji pokazuje (niti za čitanje niti za pisanje). Da bi mogla dohvatiti objekt prvo mora kreirati std::shared_ptr<T> pomoću lock() metode.

Poziv Značenje

a.use_count()

Vraća broj referenci, odnosno broj std::shared_ptr<T> objekata koji drže isti pokazivač.

a.expired()

Vraća true ako je prazan. Isto što i a.use_count() == 0, ali efikasnije.

a.lock()

Vraća std::shared_ptr<T> koji sadrži isti pokazivač.

Na primjer (nastavak),

auto a =wp1.lock();
*a = 17;
std::cout << wp1.use_count() << std::endl;  // 3
std::cout << *wp1.lock() << std::endl;  // 17

weak_ptr<T> je namijenjen i za korištenje u situacijama kada nadživi objekt na koji pokazuje. Stoga prije uptrebe metode lock() treba uvijek ispitati objekt sa expired() metodom.

std::weakptr<T> i rješenje cirkularne zavisnosti

Korištenjem klase std::weak_ptr<Employee> prekida se cirkularna zavisnost i objekti se normalno dealociraju.

// std::weak_ptr<Employee> iprekida cirkularnu zavisnost.
class Employee{
    public:
        std::string name;
        std::shared_ptr<Employee> head;
        //std::vector<std::shared_ptr<Employee>> staff;
        std::vector<std::weak_ptr<Employee>> staff;

        Employee(std::string const & name_,
                 std::shared_ptr<Employee> head_ = nullptr) : name(name_),
                                                              head(head_)
        {}
        ~Employee(){ std::cout << name << " izbrisan\n"; }
         // nema potrebe za ručnom dealokacijom
};

Alokatori

Svi spremnici imaju jedan dodatni parametar predloška, tzv. alokator. Na primjer, std::vector ima deklaraciju:

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

Alokator brine o alokaciji i dealokaciji memorije. Operatori new i delete se ne koriste neposredno zbog ovog "nedostatka":

Ta svojstva ovih operatora bi dovela do gubitka efikasnosti kod klasa kao što je std::vector<T> koje alociraju više memorije no što drže elemenata. Bez alokatora sva bi memorija morala biti konstruirana (s dodijeljenim konstruktorom).

Alokator iz zaglavlja <memory> nam omogućava da razdvojimo fazu alokacije memorije od konstrukcije objekta u memoriji te, isto tako fazu destrukcije objekta i dealokaciju memorije. Dodijeljeni alokator iz standardne biblioteke je std::allocator<T>. Korisnici mogu pisati vlastite alokatore ako za tim imaju potrebe.

Ovdje su prikazane metode alokatora:

Poziv Značenje

allocator<T> a

Definira alokator a koji alocira memoriju za tip T.

a.allocate(n)

Alocira neinicijaliziranu memoriju za n objekata tip T. Vraća pokazivač na alociranu memoriju.

a.deallocate(p,n)

Dealocira memoriju koja sadrži prostor za n objekata tip T i na koju pokazuje pokazivač p. Pokazivač p mora biti dobiven od allocate i n mora biti korišten u pozivu metode allocate. Objekti koji su eventualno konstruirani u toj memoriji moraju biti uništeni metodom destroy() prije poziva deallocate.

a.construct(p, args)

p mora biti pokazivač tipa T na neinicijaliziranu memoriju; args su argumenti za konstruktor tipa T koji se koriste da bi se na mjestu na koje pokazuje p konstruirao objekt tipa T.

a.destroy(p)

p mora biti pokazivač tipa T. Pozovi destruktor na objektu na koji pokazuje p. Objekt mora prije toga biti konstruiran na tom mjestu.

Korisne mogu biti još dvije metode iz memory zaglavlja;

Poziv Značenje

uninitialized_copy(b,e,b2)

Kopira elemente iz raspona danog s iteratorima b i e u neinicijaliziranu memoriju na koju pokazuje b2, koji mora pokazivati na dovoljnu količinu memorije.

uninitialized_fill(b,e,t)

Konstruira objekte u neinicijaliziranoj memoriji omeđenoj s iteratorima b i e kopirajući element t.

Postoje i copy_n i fill_n verzije tih funkcija koje uzimaju polazni iterator i broj elemenata.