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

C++, konstruktory przenoszące i std::move ( kilka pytań )

VPS Starter Arubacloud
0 głosów
412 wizyt
pytanie zadane 15 kwietnia 2019 w C i C++ przez Jakub 0 Pasjonat (23,120 p.)

Witam, kończę książkę C++ Szkoła Programowania, jestem przy jednym z ostatnich zadań. Co prawda napisałem go bez większego problemu, jednak nasunęło mi się wiele pytań i kwestii które nie rozumiem.

Oto kod:
 

#include <iostream>
#include <string>

class Cpmv {
public:
	struct Info {
		std::string qcode;
		std::string zcode;
	};
private:
	Info* pi;
public:
	Cpmv();
	Cpmv(std::string q, std::string z);
	Cpmv(const Cpmv& cp);
	Cpmv(Cpmv&& mv);
	~Cpmv();
	Cpmv& operator=(const Cpmv& cp);
	Cpmv& operator=(Cpmv&& mv);
	Cpmv operator+(const Cpmv& obj) const;
	void Display() const;
};

///--------------------------------------------------------------------------

Cpmv::Cpmv() {
	std::cout << "start: Cpmv()\n";
	pi = new Info;
	pi->zcode = "";
	pi->qcode = "";
	std::cout << "koniec: Cpmv()\n";
}

Cpmv::Cpmv(std::string q, std::string z) {
	std::cout << "start: Cpmv(std::string q = \"" << q << "\", std::string z = \"" << z << "\")\n";
	pi = new Info;
	pi->qcode = q;
	pi->zcode = z;
	std::cout << "koniec: Cpmv(std::string q = \"" << q << "\", std::string z = \"" << z << "\")\n";
}

Cpmv::Cpmv(const Cpmv& cp) {
	std::cout << "start: Cpmv(const Cpmv& cp)\n";
	pi = new Info;
	*pi = *cp.pi;
	std::cout << "koniec: Cpmv(const Cpmv& cp)\n";
}

Cpmv::Cpmv(Cpmv&& mv) {
	std::cout << "start: Cpmv(Cpmv&& mv)\n";
	pi = std::move(mv.pi);
	std::cout << "koniec: Cpmv(Cpmv&& mv)\n";
}

Cpmv::~Cpmv() {
	std::cout << "start: ~Cpmv()\n";
	delete pi;
	std::cout << "koniec: ~Cpmv()\n";
}

Cpmv& Cpmv::operator=(const Cpmv& cp) {
	std::cout << "start: operator=(const Cpmv& cp)\n";
	if (this == &cp) {
		std::cout << "koniec ( \"nietypowy\" ): operator=(const Cpmv& cp)\n";
		return *this;
	}
	delete pi;
	pi = new Info(*cp.pi);
	std::cout << "koniec: operator=(const Cpmv& cp)\n";
	return *this; 
}

Cpmv& Cpmv::operator=(Cpmv&& mv) {
	std::cout <<"start: operator=(Cpmv&& cmv)\n";
	if (this == &mv) {
		std::cout << "koniec ( \"nietypowy\" ): operator=(Cpmv&& cmv)\n";
		return *this; 
	}
	pi = mv.pi; //--------------- (1)
	mv.pi = nullptr; 
	std::cout << "koniec: operator=(Cpmv&& cmv)\n";
	return *this;
}

Cpmv Cpmv::operator+(const Cpmv& obj) const {
	std::cout << "start: operator+(const Cpmv& obj) const\n";
	Cpmv temp(pi->qcode, pi->zcode);
	temp.pi->qcode += obj.pi->qcode;
	temp.pi->zcode += obj.pi->zcode;
	std::cout << "koniec: operator+(const Cpmv& obj) const\n";
	return Cpmv(temp); //------------------ (2)
}

void Cpmv::Display() const {
	std::cout << "start: Display() const\n";
	std::cout << "qcode: " << pi->qcode << std::endl;
	std::cout << "zcode: " << pi->zcode << std::endl;
	std::cout << "koniec: Display() const\n";
}

///--------------------------------------------------------------------------

void main2() {
	std::cout << "-------------------------:\n";
	Cpmv a;
	std::cout << "-------------------------:\n";
	Cpmv b("ala", "anna");
	std::cout << "-------------------------:\n";
	a = a + b; 
	std::cout << "-------------------------:\n";
	Cpmv c(a);
	std::cout << "-------------------------:\n";
	Cpmv d = a;
	std::cout << "-------------------------:\n";
	a.Display();
	std::cout << "-------------------------:\n";
	b.Display();
	std::cout << "-------------------------:\n";
	c.Display();
	std::cout << "-------------------------:\n";
	d.Display();
	std::cout << "-------------------------:\n";
	d = d + Cpmv(" dod1 ", " dod2 ");
	std::cout << "-------------------------:\n";
	d.Display(); 
	std::cout << "-------------------------:\n";
	Cpmv e(c + a);
	std::cout << "-------------------------:\n";
	e.Display(); 
	std::cout << "-------------------------:\n";
	c = std::move(c); // --------------- (3)
	std::cout << "-------------------------:\n";
	c.Display();
	std::cout << "-------------------------:\n";
}

Jest to nic innego jak prosta testowa klasa, zaznaczyłem numerkami rzeczy które nie do końca rozumiem w 100% :

(1) - Jeśli zamiast:

pi = mv.pi;
mv.pi = nullptr; 

napiszę:

pi = std::move(mv.pi);

to mam taki wyjątek:

ja rozumiem że przez to jest wyciek pamięci, ale co to ma wspólnego z długością łańcucha w obiekcie klasy std::string ?!

W ogólne nie wiem co w tym kontekście robi std::move(), również w konstruktorze przenoszącym:

Cpmv::Cpmv(Cpmv&& mv) {
	std::cout << "start: Cpmv(Cpmv&& mv)\n";
	pi = std::move(mv.pi);
	std::cout << "koniec: Cpmv(Cpmv&& mv)\n";
}

ponoć funkcja ta zamienia l-wartości na r-wartości, ale przecież mv to już r-wartość. Zastosowałem w tym konstruktorze std::move() dlatego że ogólnie widzę to w kodzie innych, również w dokumentacji: https://en.cppreference.com/w/cpp/utility/move

(2) - w tej metodzie:

Cpmv Cpmv::operator+(const Cpmv& obj) const {
	std::cout << "start: operator+(const Cpmv& obj) const\n";
	Cpmv temp(pi->qcode, pi->zcode);
	temp.pi->qcode += obj.pi->qcode;
	temp.pi->zcode += obj.pi->zcode;
	std::cout << "koniec: operator+(const Cpmv& obj) const\n";
	return Cpmv(temp); 
}

jeśli nie dam przy return nowego obiektu tymczasowego:

Cpmv(temp)

to program się wysypuje z powodu braku dostępu do pamięci, dlaczego? Przecież automatycznie powinna wykonać się kopia temp.

(3)

c = std::move(c);

tą instrukcję dałem dla testu, co tu się dokładnie dzieje?

ustawiłem nawet w operatorze przypisania dla r-wartości zabezpieczenie przed tym:


Cpmv& Cpmv::operator=(Cpmv&& mv) {
	std::cout <<"start: operator=(Cpmv&& cmv)\n";
	if (this == &mv) {
		std::cout << "koniec ( \"nietypowy\" ): operator=(Cpmv&& cmv)\n";
		return *this; 
	}
	pi = mv.pi; 
	mv.pi = nullptr; 
	std::cout << "koniec: operator=(Cpmv&& cmv)\n";
	return *this;
}

czy jest to uzasadnione?

Z góry dziękuje za pomoc, mam nadzieje że rozumiecie moje pytania... jak coś to pytajcie :)

komentarz 15 kwietnia 2019 przez Hiskiel Pasjonat (22,830 p.)
//offtop

Nie wiedziałem, że MS stworzył C++..

1 odpowiedź

+1 głos
odpowiedź 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)
wybrane 15 kwietnia 2019 przez Jakub 0
 
Najlepsza

To ja może tylko na pierwszym się skupię:

pi = mv.pi;
mv.pi = nullptr;

To ustawia pi oraz ustawia w poprzednim obiekcie wskaźnik na null - czyli efektywnie "przenosi" właściciela wskaźnika na nowy obiekt.

pi = std::move(mv.pi);

To... w zasadzie nic nie robi ciekawego. `pi` jest wskaźnikiem, to wynik końcowy tej linii niczym nie różni się od `pi = mv.pi`. To oznacza że poprzedni obiekt wciąż ma wskaźnik na ten obiekt, więc wywoła na nim `delete`, więc nowy obiekt będzie potencjalnie miał martwy wskaźnik.

Przy czym, dokładnie ten błąd robisz w `Cpmv::Cpmv(Cpmv&& mv)`. (Możliwe, że naprawienie tego naprawi też inne punkty? Ale temu się nie przyglądałem)

komentarz 15 kwietnia 2019 przez Jakub 0 Pasjonat (23,120 p.)

Tak pomyślałem że samo:

pi = mv.pi;
mv.pi = nullptr;

w operatorze przypisania to za mało, no bo jeśli 'pi' wskazywało na coś wcześniej to dostęp do tej pamięci zostanie utracony ( wyciek ). Więc powinienem wcześniej zwolnić pamięć na którą wskazywało 'pi' lub wykorzystać jakąś zmienną pomocniczą do zamiany wskaźników miejscami ( lub funkcje std::swap ). Kolejny problem polega na tym że chyba już sam nie rozumiem jak działa std::move(), np w tej sytuacji:

pi = std::move(mv.pi);

myślałem że głównym problemem jest utrata dostępu do danych wskazywanych przez wcześniejsze 'pi' i wyciek z tej strony. A tu się okazuje że problem tkwi w mv.pi, myślałem że std::move powoduje przeniesienie , tzn. mv.pi automatycznie straci swoje dane.

komentarz 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)

w operatorze przypisania to za mało, no bo jeśli 'pi' wskazywało na coś wcześniej to dostęp do tej pamięci zostanie utracony ( wyciek ).

Chwila moment, ale przecież w operatorze przypisania obiekt dostaje wartość wskaźnika z drugiego obiektu - gdzie tutaj coś jest tracone?

komentarz 15 kwietnia 2019 przez Jakub 0 Pasjonat (23,120 p.)
no ale jego wcześniejsza wartość zostanie nadpisana ( a tą wartością jest adres "bloku pamięci" przydzielonego operatorem new którą pasuje zwolnić )
komentarz 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)
No i? Wartość nie jest gubiona, bo teraz pierwszy obiekt (ten, do którego przenieśliśmy) ma dokładnie ten wskaźnik.
komentarz 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)
Wait... ah, teraz zrozumiałem, przepraszam. Myślałem że mówisz o drugim obiekcie. Tak, w operatorze przypisania wypada opcjonalnie zrobić `delete` na pierwszym obiekcie przed nadpisaniem `pi`.
komentarz 15 kwietnia 2019 przez Jakub 0 Pasjonat (23,120 p.)

Zmodyfikowałem to tak:

Cpmv& Cpmv::operator=(Cpmv&& mv) {
	std::cout <<"start: operator=(Cpmv&& cmv)\n";
	if (this == &mv) {
		std::cout << "koniec ( \"nietypowy\" ): operator=(Cpmv&& cmv)\n";
		return *this; 
	}
	std::swap(pi, mv.pi);
	std::cout << "koniec: operator=(Cpmv&& cmv)\n";
	return *this;
}

i tu jak najbardziej się zgadza. Ale tak jak było wcześniej:

Cpmv& Cpmv::operator=(Cpmv&& mv) {
	std::cout <<"start: operator=(Cpmv&& cmv)\n";
	if (this == &mv) {
		std::cout << "koniec ( \"nietypowy\" ): operator=(Cpmv&& cmv)\n";
		return *this; 
	}
	pi = mv.pi; 
	mv.pi = nullptr; 
	std::cout << "koniec: operator=(Cpmv&& cmv)\n";
	return *this;
}

jest wadliwe, ponieważ:

musiał bym dać albo delete pi przed pi = mv.pi albo wykorzystać to swap.

komentarz 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)

musiał bym dać albo delete pi przed pi = mv.pi albo wykorzystać to swap.

Tak, trzeba to zrobić.

(sposób z std::swap też pewnie by spełnił zadanie i w sumie jest fajną mikrooptymalizacją, ale `delete` jest czytelniejsze)

komentarz 15 kwietnia 2019 przez Jakub 0 Pasjonat (23,120 p.)
Mam jeszcze dość nietypowe pytanie, jak coś to nie musisz na nie absolutnie odpowiadać ;)

Więc ostatnio założyłem taki temat: https://forum.pasja-informatyki.pl/425353/qt-program-szyfrujacy-problem-z-xorowaniem-plikow-png-i-prawdopodobnie-innych-tez#c425519

Tam mi pomogłeś z kilkoma rzeczami, temat jednak się urwał. Piszę o tym bo nie wiem czy czekać czy może zaktualizować pytanie, zadać nowe... nie wiem czy w ogóle wypada zadać dwa razy to samo pytanie lub uaktualnić swoją odpowiedzią czy może lepiej skorzystać z innego forum. Bo mi bardzo zależy :)

* Przepraszam jeśli to wygląda jak bym się naprzykrzał, po prostu nie jestem pewny co mam zrobić.
komentarz 15 kwietnia 2019 przez adrian17 Ekspert (344,100 p.)
(Nie miałem okazji odpisać, ale widziałem kod i domyślam się że przyczyna jest prosta - nie powinieneś przechodzić przez stringi/pola tekstowe do operacji na plikach binarnych; co najwyżej do prezentacji)
komentarz 15 kwietnia 2019 przez Jakub 0 Pasjonat (23,120 p.)
No właśnie tak nie robię, nawet dla pola tekstowego ustawiłem readOnly(true).

Podobne pytania

+4 głosów
1 odpowiedź 2,377 wizyt
+2 głosów
2 odpowiedzi 224 wizyt
pytanie zadane 14 czerwca 2016 w C i C++ przez ElPiwo Początkujący (370 p.)
0 głosów
3 odpowiedzi 430 wizyt

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,853 pasjonatów

Motyw:

Akcja Pajacyk

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

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...