• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

kopia płytka vs kopia głęboka w C++

0 głosów
2,253 wizyt
pytanie zadane 9 grudnia 2016 w C i C++ przez Evelek Nałogowiec (28,830 p.)
edycja 9 grudnia 2016 przez Evelek
#include <iostream>
#include <string>
using namespace std;

class Liczby
{
private:
    int liczba;
    string nazwa_liczby;
public:
    static int liczba_obiektow;

    Liczby();   //konstruktor domyslny
    Liczby(int liczba_jakas, string nazwa);   //konstruktor z parametrem
    Liczby(const Liczby &);     //konstruktor kopiujacy
    ~Liczby();  //destruktor

    //metody przeciazajace operatory
    Liczby operator+(const Liczby & liczba_zmienna) const;    //obiekt + obiekt
    Liczby operator+(double liczba_zmienna);  //obiekt + liczba
    Liczby operator*(const Liczby & liczba_zmienna) const;    //obiekt * obiekt
    Liczby operator*(double liczba_zmienna); //obiekt + liczba

    Liczby & operator=(const Liczby & przypisanie);


    //funkcje zaprzyjaznione przeciazajace operatory
    friend Liczby operator+(double liczba_zmienna, const Liczby & liczba_zmienna_druga); //liczba + obiekt
    friend Liczby operator*(double liczna_zmienna, const Liczby & liczba_zmienna_druga); //liczba * obiekt
    friend ostream & operator<<(ostream & wyswietlenie, const Liczby & liczba_zmienna); //wyswietlenie obiektu

    //metoda statyczna
    static int ile_obiektow();
};

//inicjalizacja statycznej skladowej klasy
int Liczby::liczba_obiektow = 0;

//metoda statyczna
int Liczby::ile_obiektow()
{
    return liczba_obiektow;
}

Liczby::Liczby()
{
    liczba = 0;
    nazwa_liczby = "";
    liczba_obiektow++;
}

Liczby::Liczby(int liczba_jakas, string nazwa)
{
    liczba = liczba_jakas;
    nazwa_liczby = nazwa;
    liczba_obiektow++;
}

Liczby::Liczby(const Liczby & liczba_zmienna)
{
    cout << "WYWOLANIE KONSTRUKTORA KOPIUJACEGO" << endl;
    liczba = liczba_zmienna.liczba;
    nazwa_liczby = liczba_zmienna.nazwa_liczby;
    liczba_obiektow++;
}

Liczby::~Liczby()
{
    cout << "WYWOLANIE DETSRUKTORA" << endl;
    liczba_obiektow--;
    cout << "Obiektow pozostalo: " << liczba_obiektow << endl;
}

Liczby Liczby::operator+(const Liczby & liczba_zmienna) const
{
    Liczby dodawanie;
    dodawanie.liczba = liczba + liczba_zmienna.liczba;
    return dodawanie;
}

Liczby Liczby::operator+(double liczba_zmienna)
{
    Liczby dodawanie;
    dodawanie.liczba = liczba + liczba_zmienna;
    return dodawanie;
}

Liczby Liczby::operator*(const Liczby & liczba_zmienna) const
{
    Liczby mnozenie;
    mnozenie.liczba = liczba * liczba_zmienna.liczba;
    return mnozenie;
}

Liczby Liczby::operator*(double liczba_zmienna)
{
    Liczby mnozenie;
    mnozenie.liczba = liczba * liczba_zmienna;
    return mnozenie;
}

Liczby & Liczby::operator=(const Liczby & przypisanie)
{
    cout << "WYWOLANIE PRZYPISANIA DEEP COPY" << endl;
    if (this == &przypisanie)
        return *this;
    liczba = przypisanie.liczba;
    return *this;
}

Liczby operator+(double liczba_zmienna, const Liczby & liczba_zmienna_druga)
{
    Liczby dodawanie;
    dodawanie.liczba = liczba_zmienna + liczba_zmienna_druga.liczba;
    return dodawanie;
}

Liczby operator*(double liczba_zmienna, const Liczby & liczba_zmienna_druga)
{
    Liczby mnozenie;
    mnozenie.liczba = liczba_zmienna * liczba_zmienna_druga.liczba;
    return mnozenie;
}

ostream & operator<<(ostream & wyswietlenie, const Liczby & liczba_zmienna)
{
    wyswietlenie << "Liczba wynosi: " << liczba_zmienna.liczba;
}

int main()
{
    Liczby liczba1(5, "number_1");
    Liczby liczba2(12, "number_2");
    cout << endl;

    cout << "liczba1 + 2" << endl;
    liczba1 = liczba1 + 2;
    cout << endl;

    cout << "3 + liczba1" << endl;
    liczba1 = 3 + liczba1;
    cout << endl;

    cout << "liczba2 * 2" << endl;
    liczba2 = liczba2 * 2;
    cout << endl;

    cout << "3 * liczba2" << endl;
    liczba2 = 3 * liczba2;
    cout << endl;

    cout << "liczba1 + liczba2" << endl;
    Liczby liczba3 = liczba1 + liczba2; //BRAK KONSTRUKTOROW???
    cout << endl;

    cout << "liczba1 * liczba2" << endl;
    Liczby liczba4 = liczba1 * liczba2; //BRAK KONSTRUKTOROW???
    cout << endl;

    cout << "Teraz zajmiemy sie konstruktorem kopiujacym" << endl;
    Liczby liczba11(20, "number_11");
    Liczby liczba12 = liczba11; // konstruktor kopiujacy
    Liczby liczba13 (liczba11); // konstruktor kopiujacy
    Liczby liczba14 = Liczby(liczba11); // konstruktor kopiujacy
    Liczby * liczba15 = new Liczby(liczba11); // konstruktor kopiujacy
}

Witam serdecznie po raz kolejny. Tym razem przychodzę z powyższym kodem. Mam pytania dotyczące wykonywania płytkiej i głębokiej kopii danego obiektu.

1) W tej części poniżej program korzysta z głębokiej kopii obiektu. Moje pierwsze pytanie: Dlaczego w tym miejscu korzysta akurat z głębokiej kopii obiektu a nie z konstruktora kopiującego?

    cout << "liczba1 + 2" << endl;
    liczba1 = liczba1 + 2;
    cout << endl;

    cout << "3 + liczba1" << endl;
    liczba1 = 3 + liczba1;
    cout << endl;

    cout << "liczba2 * 2" << endl;
    liczba2 = liczba2 * 2;
    cout << endl;

    cout << "3 * liczba2" << endl;
    liczba2 = 3 * liczba2;
    cout << endl;

 

2) Zaś w tej części poniżej program nie korzysta ani z głębokiej kopii obiektu, ani z konstruktora kopiującego. To jest moje drugie pytanie: Dlaczego nie jest wywoływany ani konstruktor kopiujący ani nie jest wykonywana głęboka kopia?

    cout << "liczba1 + liczba2" << endl;
    Liczby liczba3 = liczba1 + liczba2; //BRAK KONSTRUKTOROW???
    cout << endl;

    cout << "liczba1 * liczba2" << endl;
    Liczby liczba4 = liczba1 * liczba2; //BRAK KONSTRUKTOROW???
    cout << endl;

 

3) Za to w tych linijkach poniżej kopia dokonywana jest za pomocą konstruktora kopiującego. I tu trzecie pytanie: Dlaczego właśnie w tej sytuacji program korzysta z konstruktora kopiującego a nie głębokiej kopii obiektu?

    Liczby liczba11(20, "number_11");
    Liczby liczba12 = liczba11; // konstruktor kopiujacy
    Liczby liczba13 (liczba11); // konstruktor kopiujacy
    Liczby liczba14 = Liczby(liczba11); // konstruktor kopiujacy
    Liczby * liczba15 = new Liczby(liczba11); // konstruktor kopiujacy

4) Program korzysta z licznika obiektów. Zauważyłem, że przy obecnym kodzie ilość obiektów na końcu wykonania programu wynosi 1. Czyli tak, jakby nie został wykonany jeden destruktor. Zauważyłem też, że jest to spowodowane ostatnią linijką kodu:

Liczby * liczba15 = new Liczby(liczba11); // konstruktor kopiujacy

Jeśli usuniemy ją z kodu, to ilość obiektów na końcu wykonania programu jest poprawna i wynosi 0. Pytanie moje brzmi więc: Jak powinien wyglądać destruktor dla tak tworzonego obiektu? Wiem, że można go stworzyć jawnie, dopisując na końcu kodu linijkę: delete liczba15. Ale może jest sposób praktyczniejszy napisany w destruktorze.

1 odpowiedź

+1 głos
odpowiedź 9 grudnia 2016 przez adrian17 Mędrzec (194,600 p.)
wybrane 9 grudnia 2016 przez Evelek
 
Najlepsza

Dlaczego w tym miejscu korzysta akurat z głębokiej kopii obiektu a nie z konstruktora kopiującego?

Kompletnie mylisz pojęcia. "głęboka kopia" i konstruktor kopiujący to kompletnie niezależne tematy. W dodatku tutaj Twoja klasa nie ma wskaźników, więc temat kopii płytkiej/głębokiej w ogóle jej nie dotyczy. Tutaj tylko porównujesz wywołanie konstruktora kopiującego i operatora przypisania. Wtedy wszystko staje się proste:

1.

cout << "liczba1 + 2" << endl;
liczba1 = liczba1 + 2;
cout << endl;

Przypisujesz do liczba1 => operator przypisania.

2.

cout << "liczba1 + liczba2" << endl;
Liczby liczba3 = liczba1 + liczba2; //BRAK KONSTRUKTOROW???
cout << endl;

Masz konstruktor, jest tutaj:

Liczby Liczby::operator+(double liczba_zmienna)
{
    Liczby dodawanie; // konstruktor

3.

Liczby liczba12 = liczba11; // konstruktor kopiujacy
Liczby liczba13 (liczba11); // konstruktor kopiujacy

No... konstruujesz nowe obiekty kopiując stare,, więc konstruktor kopiujący.

4.

Jak powinien wyglądać destruktor dla tak tworzonego obiektu?

To wskaźnik. Z definicji wskaźnik sam siebie nie usunie. Żeby zniszczyć obiekt pod wskaźnikiem, trzeba użyć delete.

komentarz 9 grudnia 2016 przez Evelek Nałogowiec (28,830 p.)

Dziękuję za rozpisanie tego.

Czy jest metoda na zapamiętanie kiedy się korzysta z operatora przypisania a kiedy z konstruktora kopiującego? Bo w moich przykładach użyte zostało i jedno i drugie do konkretnych przykładów.

Mniej więcej zrozumiałem różnicę między podpunktem pierwszym a trzecim. W pierwszym przypisujemy jakiś obiekt do już istniejącego, zaś w trzecim przypisujemy jakiś obiekt do nowo tworzonego. Czy to jest ta główna różnica i sposób na zapamiętanie tego?

 

Co do punktu drugiego:

Liczby liczba3 = liczba1 + liczba2; //BRAK KONSTRUKTOROW???

Przypisujemy sumę dwóch obiektów do nowo tworzonego obiektu liczba3. Czyli biorąc pod uwagę to co wcześniej napisałem, skoro do nowo tworzonego, to powinien program korzystać z operatora przypisania, a tu nie korzysta ani z tego, ani z konstruktora kopiującego. Korzysta zaś ze zwykłego konstruktora, który pokazałeś mi w definicji metody i pokazałeś nie ten co potrzeba, bo gdy sumujemy obiekt + obiekt to wtedy korzystamy z tego:

Liczby Liczby::operator+(const Liczby & liczba_zmienna) const
{
    Liczby dodawanie;
    dodawanie.liczba = liczba + liczba_zmienna.liczba;
    return dodawanie;
}

a nie z tego:

Liczby Liczby::operator+(double liczba_zmienna)
{
    Liczby dodawanie;
    dodawanie.liczba = liczba + liczba_zmienna;
    return dodawanie;
}

Zgadza się? Tu też w takim razie możemy wyróżnić konstruktor, jest nim:

Liczby Liczby::operator+(const Liczby & liczba_zmienna) const
{
    Liczby dodawanie; //KONSTRUKTOR
    dodawanie.liczba = liczba + liczba_zmienna.liczba;
    return dodawanie;
}

Ale co to za konstruktor? Zwykły. Żaden kopiujący. Dlaczego więc, w przypadku gdy dodajemy obiekt + obiekt to nie korzysta program z konstruktora kopiującego ani z operatora przypisania?

 

+Ostatnie pytanie na koniec: Na czym polega więc kopia głęboka? Bo w moim odczuciu i z tego co zrozumiałem, to metoda głęboka zawarta jest w metodzie przypisania czyli tutaj:

Liczby & Liczby::operator=(const Liczby & przypisanie)
{
    cout << "WYWOLANIE PRZYPISANIA DEEP COPY" << endl;
    if (this == &przypisanie)
        return *this;
    liczba = przypisanie.liczba;
    return *this;
}

 

komentarz 9 grudnia 2016 przez adrian17 Mędrzec (194,600 p.)

W pierwszym przypisujemy jakiś obiekt do już istniejącego, zaś w trzecim przypisujemy jakiś obiekt do nowo tworzonego. Czy to jest ta główna różnica i sposób na zapamiętanie tego?

Konstruktor konstruuje (nowy obiekt), operator przypisania przypisuje (do istniejącego).

 a tu nie korzysta ani z tego, ani z konstruktora kopiującego. 

Masz rację, "intuicyjnie" powinien się odpalić konstruktor kopiujący. To, co widzisz, to optymalizacja. Kiedy masz coś takiego:

Liczby func(){
    Liczby obiekt;
    return obiekt;
}
int main() {
    Liczby obiekt = func();
}

Obiekt jest tworzony w funkcji func(), ale ten obiekt jest "zwracany" do funkcji main bez używania konstruktorów. (wygugluj Return Value Optimization / Copy Elision)

 Na czym polega więc kopia głęboka?

Ten termin ma znaczenie tylko gdy obiekty mają wskaźniki/referencje.

struct S{
	int *x;
};
void f(S &s) {
	S kopia = s; // kopia plytka - oba obiekty maja wskaznik na ta sama liczbe
	kopia.x = new int(s.x); // teraz to kopia gleboka - nowy obiekt ma wskaznik na kopie liczby, nie na ta sama liczbe
}

 

komentarz 9 grudnia 2016 przez Evelek Nałogowiec (28,830 p.)


No dobra, to myślę, że wyczerpałeś temat operatora przypisania i konstruktora kopiującego. Dziękuje za wytłumaczenie.

Ale co do kopii głębokiej... definicja ze strony http://157.158.12.11/oop/index.php?sect=nauka&subSect=prototype/prototype_02

W języku C++ podstawowym sposobem implementacji kopiowania głębokiego jest jawne zdefiniowanie konstruktora kopiującego i operatora przypisania. Niestety bardzo często powyższe rozwiązanie jest niewystarczające – jeśli zawierany przez obiekt wskaźnik wskazuje na pewien konkretny typ pochodny użycie konstruktora kopiującego jest niemożliwe.

Mam w swojej klasie zdefiniowany konstruktor kopiujący oraz operator przypisania. I na chwilę obecną, na to co się dzieje w programie, jest to wystarczające. Czyli kopia głęboka zachodzi już teraz, zgadza się? Ale w przypadku, gdy wejdziemy na wskaźniki i referencje będziemy musieli korzystać tylko i wyłącznie z kopii głębokiej. Ale czy kopią głęboką nie nazywamy sytuacji, gdy korzystamy właśnie z konstruktora kopiującego i operatora przypisania?

komentarz 9 grudnia 2016 przez adrian17 Mędrzec (194,600 p.)

Ale czy kopią głęboką nie nazywamy sytuacji, gdy korzystamy właśnie z konstruktora kopiującego i operatora przypisania?

Nie. Przeczytaj tekst jeszcze raz. Napisanie (nie byle jakiego) konstruktora kopiującego i operatora przypisania jest sposobem implementacji głębokiego kopiowania (które, jak mówiłem i jak mówi tekst trochę wyżej, ma sens tylko z wskaźnikami i referencjami). Nie ma implikacji w drugą stronę - że jeśli napiszesz *jakiś* konstruktor kopiujący i operator=, to magicznie osiągasz "głębokie kopiowanie".

komentarz 10 grudnia 2016 przez Evelek Nałogowiec (28,830 p.)
No dobra, rozumiem teraz. Dzięki za całą pomoc. :)

Podobne pytania

0 głosów
0 odpowiedzi 53 wizyt
pytanie zadane 25 maja w C i C++ przez mats19 Nowicjusz (180 p.)
0 głosów
0 odpowiedzi 50 wizyt
pytanie zadane 25 marca w Systemy operacyjne, programy przez Xarti Obywatel (1,200 p.)
0 głosów
0 odpowiedzi 58 wizyt
Porady nie od parady
Zadając pytanie postaraj się o odpowiedni tytuł, kategorię oraz tagi.Tagi

65,755 zapytań

112,393 odpowiedzi

237,319 komentarzy

46,700 pasjonatów

Przeglądających: 123
Pasjonatów: 2 Gości: 121

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...