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 :)