Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Przesyłanie do funkcji przez referencje wektora wskaźników

Ostatnio zmodyfikowano 2017-05-19 16:55
Autor Wiadomość
michal11
» 2017-05-18 10:19:29
W dużym uproszczeniu vector można napisać tak:
C/C++
template < typename T >
class vector
{
public:
    //functions
   
private:
   
    T * Array;
    int Size;
    int Capacity;
};

jak widzisz jest to w zasadzie zwykła tablica, teraz jak mogłaby wyglądać funckja push_back
C/C++
template < typename T >
void vector < T > push_back( T & arg )
{
    if( Size == Capacity )
    {
        T * OldArray;
       
        copy( Array, OldArray ); //copy - jakas funkcja do kopiowania pamieci (niesistotne jaka)
        delete[] Array;
       
        Capacity *= 2;
        Array = new T[ Capacity ];
       
        copy( OldArray, Array );
    }
   
    Array[ Size++ ] = arg;
}

widzisz tutaj, że jeżeli osiągniemy limit elementów w tablicy to zostanie ona rozszerzona co wiąże się z zrobieniem kopii oryginalnej tablicy, skasowaniem jej, zaalokowanej nowej, większej tablicy i przekopiowaniem elementów do nowej tablicy, na końcu jest zapisywany argument do kontenera. Stąd prosty wniosek, że dodawanie elementów do vectora wiąże się z (potencjalnie) częstym kopiowaniem elementów vectora (w tym uproszczonym przykładzie pomijam przenoszenie).
Teraz wyobraź sobie, że mamy taką klasę którą chcemy umieścić w vectorze
C/C++
class MyClass
{
private:
    int a, b, c, d, e, f, g;
    double p1, p2, p3, p4, p5, p6, p7, p8;
    std::string s1, s2, s3, s4, s5, s6, s7, s8;
    SomeBigStruct ss1, ss2, ss3, ss4, ss5;
};

jak widzisz jest to klasa z masą różnych składowych, w tym z kilkoma stringami (które same w sobie są tablicą charów). Jak pewnie możesz sobie wyobrazić kopiowanie takiej klasy jest dość kosztowne dlatego chcielibyśmy tego uniknąć, jeżeli jednak będziesz trzymał taką klasę w vectorze przez wartość to nie unikniesz tego kopiowania i co kilka wstawień nowego elementu będziesz miał kilkanaście wywołań konstruktora kopiującego. Dlatego lepiej taką klasę trzymać w vectorze korzystając ze wskaźników. Wskaźniki to nic innego jak liczby, kopiowanie liczb jest szybkie, na pewno dużo szybsze od kopiowania takiej klasy. Kopiując wskaźniki nie tracisz aderu obiektu na który one wskazują bo ten adres się nie zmienia, nie przenosisz nigdzie obiektu wcześniej zaalokowanego, on zawsze jest w tym samym miejscu. Dlatego właśnie często używa się vectora wskaźników na obiekty, żeby pozbyć się tego kosztownego kopiowania, zresztą niektórych klas nie da się kopiować albo chcemy mieć pełną kontrolę nad tym kiedy jest wykonywana kopia, wtedy wskaźniki są jedynym rozwiązaniem.

Teraz jeżeli chodzi o zwalnianie pamięci. Vector nie wie jakie dane trzyma, nie wie czy ma wskąźniki na obiekty (i czy wskaźnik na coś faktycznie pokazuje), nie wie czy trzyma obiekty przez wartość, nie wie czy trzyma klasę, czy strukturę czy typ wbudowany, to jest szablon i musi być maksymalnie elastyczny. Z tego powodu vector sam nic nie zwalnia, oczywiście nie licząc swojej wewnętrznej tablicy, dlatego jeżeli trzymasz obiekty w vectorze przez wartość to w destruktorze vectora, przy kasowaniu wew. tablicy automatycznie wywoływane są destruktory obiektów T, jeżeli trzymamy tam wskaźniki to jedyne co się dzieje to usunięcie tych wskaźników (ale nie tego na co wskazują!), dlatego w takim wypadku trzeba samemu zadbać o zwolnienie pamięci. W tym miejscu warto też powiedzieć o smart pointerach które załatwią to za nas (smart pointer w swoim destruktorze usuwa obiekt na który wskazuje).
Dlatego lepiej pisać
C/C++
std::vector < std::unique_ptr < MyClass >> vec;

//...

vec.push_back( std::make_unique < MyClass >() );
i tyle, destruktor vectora wywoła destruktor unique_ptr a ten z kolei wywoła delete które usunie dynamicznie zaalokowany obiekt klasy MyClass.


I na koniec, nie
std::vector < int >
 to nie jest vector wskaźników na int i nie dodajemy do niego elementów poprzez new
C/C++
std::vector < int > vec1;
vec1.push_back( 5 );

std::vector < int *> vec2;
vec2.push_back( new int( 10 ) );

w pierwszym przykładzie nie musimy martwić się o pamięć, wszystko jest zarządzane automatycznie, nic dynamicznie nie alokowaliśmy, vector trzyma int przez wartość i przy kopiowaniu będzie kopiowana liczba 5. W drugim przykładzie musimy martwić się o pamięć bo sami ją dynamicznie zaalokowaliśmy, więc musimy ją też zwolnić (destruktor vectora tego nie zrobi automatycznie), vector trzyma adresy intów i przy kopiowaniu te adresy będą kopiowane, czyli np. new int(10) mogło stworzyć liczbę 10 w pamięci i zwrócić jej adres 0x231af15cd351, wtedy ten adres (a nie wartość liczby) będzie kopiowany.
P-161242
Elaine
» 2017-05-18 12:40:41
Jeśli według ciebie std::vector przy powiększaniu kopiuje elementy, to dlaczego std::vector<std::unique_ptr<T>> działa, skoro std::unique_ptr nie jest kopiowalne?
P-161244
michal11
» 2017-05-18 13:36:42
(w tym uproszczonym przykładzie pomijam przenoszenie)
P-161245
latajacaryba
Temat założony przez niniejszego użytkownika
» 2017-05-18 18:12:39
Teraz wszystko jest dla mnie jasne, ogromne dzięki :))
Mam tylko problem z przekazaniem do funkcji wektora wskaźników :c
C/C++
void wczytywanie_z_kategorii( Kategoria & kat, vector < Fiszka *> & zapis ) // deklaracja funkcji

vector < Kategoria *> kategorie;
vector < Fiszka *> zapis;

wczytywanie_z_kategorii( & kategorie[ i ], & zapis ); //wywolanie i blad


error: invalid initialization of non-const reference of type 'Kategoria&' from an rvalue of type 'Kategoria**'|

note: in passing argument 1 of 'void wczytywanie_z_kategorii(Kategoria&, std::vector<Fiszka*>&)'|

Nie wiem, co tu jest źle. Stawiałbym, że &kategorie[i] ale kompilator wskazuje, że nieprawidłowy jest &zapis.
P-161252
Monika90
» 2017-05-18 18:58:56
Tak będzie dobrze
C/C++
wczytywanie_z_kategorii( * kategorie[ i ], zapis );
P-161255
michal11
» 2017-05-18 19:03:24
operator & służy do uzyskania adresu obiektu, w takiej linijce
wczytywanie_z_kategorii( & kategorie[ i ], & zapis );
 ty jako pierwszy argument chcesz przekazać adres wskaźnika na Kategorię a jako drugi argument chcesz przekazać adres vectora z fiszkami, natomiast twoja funkcja oczekuje dwóch obiektów (które pobierzesz przez referencję), w przypadku drugiego argumentu wystarczy usunąć ten operator natomiast w przypadku pierwszego argumentu musisz zdereferencjować element vectora tak aby uzyskać obiekt ( do tego służy operator *), pamiętaj tylko, że dereferencja nulla spokoduje crash aplikacji.
P-161256
latajacaryba
Temat założony przez niniejszego użytkownika
» 2017-05-18 22:59:56
Przyznaję, trochę zbiło mnie to z tropu. Mógłbym teraz to zaimplementować i mieć to z głowy ale... ;)
Ale wolę to zrozumieć. Obiecuję, ostatnie pytanie :D

Skoro funkcja oczekuje referencji do obu obiektów, to jak zastosowanie:
C/C++
funkcja( * wskaznik );

Może być poprawne, skoro to odniesienie się do obiektu (dereferencja) na który wskazuje wskaźnik (czyli jakby przesyłamy przez kopię??).

Jeszcze bardziej zastanawiające jest przesyłanie samego
zapis
, skoro to wektor wskaźników. A funkcja chce przesyłania przez referencję.
jako drugi argument chcesz przekazać adres vectora z fiszkami [...] twoja funkcja oczekuje dwóch obiektów (które pobierzesz przez referencję)
Czy więc nie odpowiada to tej sytuacji:
C/C++
void funkcja( T * ref );

int a;
funkcja( a );
?
Jakby nie było, wektor to też obiekt. Dlaczego więc nie przekazujemy go przez referencję?
P-161275
jankowalski25
» 2017-05-19 08:47:19
Skoro funkcja oczekuje referencji do obu obiektów, to jak zastosowanie:
C/C++
funkcja( * wskaznik );
Może być poprawne, skoro to odniesienie się do obiektu (dereferencja) na który wskazuje wskaźnik (czyli jakby przesyłamy przez kopię??).
Nie. Domyślnie masz referencję. Obiekt powstaje wtedy, kiedy nie można użyć referencji.
C/C++
int liczba = 5; //masz jakąś liczbę
liczba; //to domyślnie daje referencję do liczby
& liczba; //to daje referencję do wskaźnika
int inna_liczba = liczba; //tutaj będzie kopiowanie, nie można użyć referencji
int & referencja = liczba; //a tutaj można, więc oczywiście referencja
int * wskaznik = & liczba; //tutaj znajdzie się adres wskaźnika
int *& ref_do_wsk = & liczba; //a tutaj referencja do wskaźnika
Jeszcze bardziej zastanawiające jest przesyłanie samego
zapis
, skoro to wektor wskaźników. A funkcja chce przesyłania przez referencję.
Jeśli funkcja chce referencji, to tak będzie. A jeśli to nie jest referencja do stałej, to nie można tworzyć obiektów "w locie". Przykład:
C/C++
void wartosc( int liczba );
void referencja( int & liczba );
wartosc( 5 );
//!referencja( 5 ); //error: cannot bind non-const lvalue reference of type
//!                 //       'int&' to an rvalue of type 'int'
int liczba = 5;
wartosc( liczba );
referencja( liczba );
W przypadku każdego typu danych (wskaźników również) domyślnie masz referencję, czyli na przykład
int *&
. Wpisanie
int *
 wymusza utworzenie zmiennej przechowującej wartość wskaźnika zamiast referencji do niego.
P-161282
1 « 2 » 3
Poprzednia strona Strona 2 z 3 Następna strona