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

[c++] Klasy + pointery

Ostatnio zmodyfikowano 2017-03-13 00:18
Autor Wiadomość
Squashu
Temat założony przez niniejszego użytkownika
[c++] Klasy + pointery
» 2017-03-11 12:26:37
Witam:

Na zajęciach z prog. obiektowego mieliśmy do wykonania dość proste zadanie z klasami. Chciałbym troszeczkę rozszerzyć moje zrozumienie jak działają wskaźniki w klasach, dlatego proszę o małą pomoc.

Polecenie:
Zdefiniuj klasy Person(name,surname) i Address(street,number). Niech Person posiada wskaźnik do obiektu Address. Zaimplementuj metody setAddress(Address * address) i getAddress() w klasie Person. Zaimplementuj destruktor klasy Person, który niszczy Address.

Moje dodatkowe pytania:
1.Czy w klasach poprawne jest zdefiniowanie samego wskaźnika bez jego początkowego przypisania, czy obowiązują takie same reguły jak zawsze? (chodzi mi o fakt, że do wskaźnika jest później przypisywany obiekt przez new więc nie powinno być problemów z alokacją pamięci)

2.Co jest bardziej optymalne a propo metody setAddress (poprawne/stosowane) - zakładam, że chcę żeby adress został jako private:
   - metoda bez argumentow w której korzystam ze wskaźnika this na wskaźnik adress
   - tworzenie metody zwracającej pointer i ustawienie jej jako argument do metody setAdress (zastosowane niżej)

Kod:
Main.cpp
C/C++
#include <iostream>
#include "Person.hpp"

int main() {
    Person jeden( "a", "b", "c", 1 );
    Person dwa( "c", "d" );
    dwa.setAdress( dwa.getPointer() );
    jeden.getAdress();
    dwa.getAdress();
    jeden.setAdress( jeden.getPointer() );
    jeden.getAdress();
    return 0;
}

Person.hpp
C/C++
#include <iostream>
#include <string>
#include "Adress.hpp"

class Person {
   
private:
    std::string name;
    std::string surname;
    Adress * adress;
   
public:
    Person( std::string name, std::string surname, std::string street, int number );
    Person( std::string name, std::string surname );
    ~Person();
    void setAdress( Adress * adress );
    void getAdress();
    Adress * getPointer();
};

Person.cpp
C/C++
#include <iostream>
#include <string>
#include "Person.hpp"

Person::Person( std::string name, std::string surname, std::string street, int number ) {
    adress = new Adress( street, number );
    this->name = name;
    this->surname = surname;
}

Person::Person( std::string name, std::string surname ) {
    this->name = name;
    this->surname = surname;
    adress = new Adress();
}

void Person::setAdress( Adress * adress ) {
    std::string a;
    int b;
    std::cin >> a >> b;
    adress->street = a;
    adress->number = b;
}

void Person::getAdress() {
    std::cout << adress->street << " " << adress->number << std::endl;
}

Person::~Person() {
    delete adress;
}

Adress * Person::getPointer() {
    return this->adress;
}

Adress.hpp
C/C++
#include <iostream>
#include <string>

class Adress {
   
public:
    std::string street;
    int number;
public:
    Adress( std::string street, int number );
    Adress();
    ~Adress();
};

Adress.cpp
C/C++
#include <iostream>
#include <string>
#include "Adress.hpp"

Adress::Adress( std::string street, int number ) {
    std::cout << "pojawia sie adres" << std::endl;
    this->street = street;
    this->number = number;
}

Adress::Adress() {
    std::cout << "pojawia sie adres" << std::endl;
    this->street = "notSet";
    this->number = 0;
}

Adress::~Adress() {
    std::cout << "adres znika" << std::endl;
}

P-158833
Bielan
» 2017-03-11 16:10:58

Chciałbym troszeczkę rozszerzyć moje zrozumienie jak działają wskaźniki w klasach, dlatego proszę o małą pomoc.

Moja odpowiedź musi być podzielona niejednoznaczna ponieważ z treści zadania wynika, że prowadzący niespecjalnie zna dobre praktyki programowania.


Czy w klasach poprawne jest zdefiniowanie samego wskaźnika bez jego początkowego przypisania, czy obowiązują takie same reguły jak zawsze? (chodzi mi o fakt, że do wskaźnika jest później przypisywany obiekt przez new więc nie powinno być problemów z alokacją pamięci)
Wskaźnikom na początku chcesz przypisać wartość
nullptr
, chyba że masz gotową wartość, którą chcesz im przypisać (lub możesz ją stworzyć).


2.Co jest bardziej optymalne a propo metody setAddress (poprawne/stosowane) - zakładam, że chcę żeby adress został jako private:
   - metoda bez argumentow w której korzystam ze wskaźnika this na wskaźnik adress
   - tworzenie metody zwracającej pointer i ustawienie jej jako argument do metody setAdress (zastosowane niżej)

Tak naprawdę to surowe wskaźniki nie powinny w ogóle ani wchodzić do klasy ani wychodzić z klasy. Aby to osiągnąć używamy inteligentnych wskaźników, o których możesz poczytać na internecie.


C/C++
dwa.setAdress( dwa.getPointer() );

Absolutnie nie. Nie musisz przekazywać tego jako argument, skoro istnieje jako pole w klasie.

C/C++
this->street = "notSet";

Może lepiej pozostawić pusty string? Tak naprawdę, trzeba się zastanowić czy w ogóle domyślny konstruktor jest sensowny dla tego obiektu?


C/C++
std::string street;
int number;

Fajnie jakby te zmienne były ukryte za funkcjami. Staramy się nie eksponować zmiennych poza klasę. 


C/C++
void Person::setAdress( Adress * adress ) {
    std::string a;
    int b;
    std::cin >> a >> b;
    adress->street = a;
    adress->number = b;
}

Czy to jest metoda klasy Person? Czy tego się spodziewasz po klasie Person? Że przy ustawianiu adresu będzie go wczytywać od użytkownika?


Są to moje uwagi i wykładowca może się z nimi nie zgodzić. Nie będzie sensu się kłócić z prowadzącym, że coś powinno być tak a nie inaczej. Uwagi możesz pamiętać a zaaplikować te co do których prowadzący nie będzie miał pretensji.


 Zaimplementuj metody setAddress(Address * address) i getAddress() w klasie Person.
Tutaj prowadzący miał na myśli metodę, która przyjmie wskaźnik z address i przypisze go do swojej prywatnej zmiennej.

Sugestii mogłoby być jeszcze kilka, ale na razie zobaczmy co zrobisz z tymi :)
P-158847
mokrowski
» 2017-03-11 17:56:51
Prócz podanych uwag od @BIelan'a, kilka z mojej strony:
1. Dlaczego wczytujesz <iostream> w plikach nagłówkowych jeśli nie używasz żadnych definicji z tych nagłówków w kodzie w samym nagłówku?
2. Dlaczego wczytujesz w plikach *.cpp <string> jeśli masz go w odpowiednich nagłówkach *.hpp ?
3. W plikach nagłówkowych brakuje strażników.
4. Z jakiego powodu nie używasz list inicjalizacyjnych?
5. Zakładam że zdajesz sobie sprawę z wykonywanej kopii std::string przy przekazaniu argumentów? Dopuszczam że możesz jeszcze nie znać składni (stałej) referencji. Ważne żebyś zdawał sobie sprawę że tam string jest kopiowany.
6. Metoda nazywa się getAdress() a nie zwraca żadnego adresu. Albo nazwij ją inaczej albo zwróć adres.
7. Jeśli metodę nazwiesz getAdress() to powinna mieć ona sygnaturę const bo nie zmienia zawartości obiektu.
8. Jeśli nie znasz wskaźników inteligentnych to podejmij decyzję kto zajmuje się (w całym krótkim programie) opieką nad zasobem Adress. Najlepiej żeby była to klasa Person.
9. Metoda publiczna getPointer() to zły pomysł bo nie przedstawia intencji w jakim celu istnieje (getPointer czego?). Dodatkowo upublicznia stan wewnętrzny obiektu. Lepiej nazwać ją getAdress() i zwrócić wyłuskany Adress.
10. To zły pomysł upubliczniać atrybuty klasy Adress.

Co do Twoich pytań:
1. Przypisanie wykonaj jak najwcześniej. Najlepiej w liście inicjalizacyjnej. Jeśli zdecydujesz się najpierw przypisywać nullptr a nie adres prawidłowo alokowanej struktury, pamiętaj że będzie to komplikowało logikę kodu w samej klasie lub (co gorsza) w programie.
2. I jedno i drugie jest groźne bo wymaga podjęcia decyzji kto i kiedy będzie odpowiedzialny za obsłużenie cyklu życia obiektu Adress reprezentowanego przez wskaźnik. Patrz uwagi 6-9.
P-158858
Squashu
Temat założony przez niniejszego użytkownika
» 2017-03-12 21:14:58
Na początku dziękuje za odpowiedź @Bielan @mokrowski.

Kod wklejam poniżej tutaj odpowiem na wasze pytania.

Wskaźnikom na początku chcesz przypisać wartość nullptr, chyba że masz gotową wartość, którą chcesz im przypisać (lub możesz ją stworzyć).
Dodałem, po prostu nie byłem pewny czy w klasach powinno się też od razu przypisywać wartość do wskaźnika.

Tak naprawdę to surowe wskaźniki nie powinny w ogóle ani wchodzić do klasy ani wychodzić z klasy. Aby to osiągnąć używamy inteligentnych wskaźników, o których możesz poczytać na internecie.
 i
Tutaj prowadzący miał na myśli metodę, która przyjmie wskaźnik z address i przypisze go do swojej prywatnej zmiennej. 
 i
Absolutnie nie. Nie musisz przekazywać tego jako argument, skoro istnieje jako pole w klasie. 
Też się zdziwiłem opisem tego zadania i nie do końca rozumiałem po co argumentem w tej metodzie miałby być wskaźnik... - poprawione.
Apropo inteligentnych wskaźników nic nie wiem, zaraz poszukam - jeśli masz jakąś konkretną stronę, gdzie jest to przyzwoicie opisane to poproszę :).

Może lepiej pozostawić pusty string? Tak naprawdę, trzeba się zastanowić czy w ogóle domyślny konstruktor jest sensowny dla tego obiektu? 
Masz rację, przypisanie bez sensu, poprawiłem. Pusty konstruktor jest dla mnie, chciałem widzieć kiedy jest on wywoływany. Trzymam go tylko dla tego.

Fajnie jakby te zmienne były ukryte za funkcjami. Staramy się nie eksponować zmiennych poza klasę. 
Zmienione, dodana przyjaźń, zmienne klasy Adress do private; 3 metody na operowanie danymi.

Czy to jest metoda klasy Person? Czy tego się spodziewasz po klasie Person? Że przy ustawianiu adresu będzie go wczytywać od użytkownika? 
Na początku w ramach po prostu testu dla funkcji setAdressAll ustawiłem sobie tą metodę, gdy Person jest tworzony bez adresu.

1,Dlaczego wczytujesz <iostream> w plikach nagłówkowych jeśli nie używasz żadnych definicji z tych nagłówków w kodzie w samym nagłówku?
Nie mam nic na usprawiedliwienie, głupi błąd - poprawione.

2.Dlaczego wczytujesz w plikach *.cpp <string> jeśli masz go w odpowiednich nagłówkach *.hpp ?
Jak wyżej.

3. W plikach nagłówkowych brakuje strażników.
Krótki plik, nie używam ifndef/prima once bo mam pewność że żaden plik nie zostanie wczytany dwa razy. Nie będę na nim dalej pracował.

4. Z jakiego powodu nie używasz list inicjalizacyjnych?
Krótki, nierozbudowany plik; nie używam const-ów nigdzie. Czy używanie takiej listy w takich małych zadankach faktycznie przykłada się na zauważalną szybkość działania programu?

5. Zakładam że zdajesz sobie sprawę z wykonywanej kopii std::string przy przekazaniu argumentów? Dopuszczam że możesz jeszcze nie znać składni (stałej) referencji. Ważne żebyś zdawał sobie sprawę że tam string jest kopiowany.
Nie mam na obecną chwilę pojęcia o czym mówisz, proszę o rozjaśnienie jeśli można.

6. Metoda nazywa się getAdress() a nie zwraca żadnego adresu. Albo nazwij ją inaczej albo zwróć adres.
Znaczy się getAdress wypisuje mi adres, o to mi chodziło.

7. Jeśli metodę nazwiesz getAdress() to powinna mieć ona sygnaturę const bo nie zmienia zawartości obiektu.
Pytanie - czy warto używać conts-ów do "getter'ów" skoro nie maja one wpływ na wartość zmiennej?

8. Jeśli nie znasz wskaźników inteligentnych to podejmij decyzję kto zajmuje się (w całym krótkim programie) opieką nad zasobem Adress. Najlepiej żeby była to klasa Person.
Rozumiem, że powinienem ustawić metody adress na private,przyjaźń i potem działać na metodach Person zeby działać na zmiennych Adress, ale po prostu chciałem mieć dostęp bezpośrednio do metod Adress - w czasie pisania testowałem czy wszystko działa. Dopiero się uczę programowania obiektowego i jest mi tak wygodniej.
Apropo inteligentnych wskaźników nic nie wiem, zaraz poszukam - jeśli masz jakąś konkretną stronę, gdzie jest to przyzwoicie opisane to poproszę :).

9. Metoda publiczna getPointer() to zły pomysł bo nie przedstawia intencji w jakim celu istnieje (getPointer czego?). Dodatkowo upublicznia stan wewnętrzny obiektu. Lepiej nazwać ją getAdress() i zwrócić wyłuskany Adress.
Poprawione, błąd wynikał z niezrozumienia o co chodziło prowadzącemu - jak wyżej.

10. To zły pomysł upubliczniać atrybuty klasy Adress.
'
Zmienione, jak wyżej.

Dziękuję jeszcze raz, za pomoc. Jeśli macie jeszcze jakieś sugestie, albo porady to ja bardzo chętnie je ogarnę.

Main.cpp
C/C++
#include "Person.hpp"

int main() {
    Person jeden( "a", "b", "c", 1 );
    Person dwa( "c", "d" );
    dwa.setAdress();
    jeden.getAdress();
    dwa.getAdress();
    jeden.setAdress();
    jeden.getAdress();
    return 0;
}

Person.hpp
C/C++
#include <string>
#include "Adress.hpp"

class Person {
   
    friend class Adress;
   
private:
    std::string name;
    std::string surname;
    Adress * adress = NULL;
   
public:
    Person( std::string name, std::string surname, std::string street, int number );
    Person( std::string name, std::string surname );
    ~Person();
    void setAdress();
    void getAdress();
    Adress * getPointer();
};

Person.cpp
C/C++
#include <iostream>
#include "Person.hpp"

Person::Person( std::string name, std::string surname, std::string street, int number ) {
    adress = new Adress( street, number );
    this->name = name;
    this->surname = surname;
}

Person::Person( std::string name, std::string surname ) {
    this->name = name;
    this->surname = surname;
    adress = new Adress();
}

void Person::setAdress() {
    std::string a;
    int b;
    std::cin >> a >> b;
    adress->setAdressAll( a, b );
}

void Person::getAdress() {
    std::cout << adress->getStreet() << " " << adress->getNumber() << std::endl;
}

Person::~Person() {
    delete adress;
}

Adress.hpp
C/C++
#include <string>

class Adress {
   
private:
    std::string street;
    int number;
public:
    Adress( std::string street, int number );
    Adress();
    ~Adress();
    void setAdressAll( std::string adress, int number );
    const std::string getStreet();
    const int getNumber();
};

Adress.cpp
C/C++
#include <iostream>
#include "Adress.hpp"

Adress::Adress( std::string street, int number ) {
    std::cout << "pojawia sie adres" << std::endl;
    this->street = street;
    this->number = number;
}

Adress::Adress() { // chce tylko miec informacje ow ywo³aniu konstruktora
    std::cout << "pojawia sie adres" << std::endl;
}

Adress::~Adress() {
    std::cout << "adres znika" << std::endl;
}

void Adress::setAdressAll( std::string adress, int number ) {
    this->street = adress;
    this->number = number;
}

const std::string Adress::getStreet() {
    return street;
}

const int Adress::getNumber() {
    return number;
}
P-158933
Bielan
» 2017-03-12 22:13:45
Zerknąłem na szybko i jedna rzecz rzuca się mocno w oczy. Po co przyjaźń w kodzie?
P-158935
mokrowski
» 2017-03-13 00:04:30
Ad. 3. Jeśli nie masz ochoty wpisywać strażników jako makra, wystarczy
C/C++
#pragma once
To nie boli a w przyszłości jeśli nawet przez przypadek wczytasz w cyklu nagłówek, unikniesz błędu. Zresztą, robią strażników za Ciebie IDE.

Ad. 4. Szybkość kodu jest drugorzędna. Liczy się czytelność, poprawność i daleko dalej na tej liście szybkość.
Optymalizacje przeprowadza się później. Listy inicjalizacyjne dla definiowanych typów pozwalają uniknąć niepotrzebnego wołania domyślnego konstruktora, dają możliwość delegowania konstrukcji do rodzica i dużą kontrolę nad poprawnością kodu. Np. diagnozę że pomijasz inicjalizację któregoś z pól.
Jednym słowem stosuj je domyślnie tym bardziej że w większości wypadków działają dodatnio także na szybkość wykonywanego kodu :-)
Przypadki kiedy nie są wygodne odkryjesz w miarę doświadczenia. Źle myślisz. Listy inicjalizacyjne służą nie tylko do "inicjalizowania constów"!

Ad. 5. Definiując metodę lub konstruktor z X(std::string x), wyrażasz chęć (i tak się dzieje) by std::string był kopiowany. Jeśli chcesz by nie był, podajesz X(std::string& x) co jest referencją (w polskiej literaturze widziałem słowo odniesienie). Przez taką referencję możesz zmienić oryginalnie przekazywany string z wnętrza metody (co czasem jest pożądane).
Jeśli tego nie chcesz (czyli tak kopiowania jak i zmiany string'a z wnętrza metody), podajesz X(const std::string& x). const zawsze Twoim przyjacielem.

Ad. 6. Jak wypisuje adres to nie getX() a showX(). Nazwij ją showAdress(). W kodzie który umieściłem tak zrobiłem.

Ad. 7. Tak warto. Dają czytelnikowi Twojego kodu (a więc i Tobie), pewność że metoda nie zmieni stanu obiektu. Stosuj zawsze. Gettrer (polska nazwa akcesor) nie jest przecież tworzony przez nazwanie go getCoś() a przez wymuszenie poprawności stałej (ang. const-correctness) co określa że tą metodą nie zmienisz stanu obiektu (nawet niechcący bo kompilator Ci tego nie skompiluje).

Ad. 8. Żadne friend. Tu nie jest potrzebne. W przykładzie masz poprawiony kod. Wskaźniki inteligentne (ang. smart pointers), to dalszy etap nauki jak zobaczysz i "zaboli Cię" trochę niewygoda "wskaźników gołych". Zobaczysz wtedy jak pomogą wskaźniki inteligentne :-) W smart-pointerach jest użytych kilka koncepcji które łatwiej poznać bez nich jeśli chcesz je zrozumieć oraz warto zderzyć się wcześniej z problemami które rozwiązują.

PS. To teraz co do pkt 3, usuń w plikach *.hpp #pragma once i zobacz jakie błędy będzie zgłaszał kompilator a sam się przekonasz czy dobrze radzę :-)

Podejmij jeszcze decyzję co do kopii. Szczególnie Person.

Co do formatowania kodu.. niestety ale formater forum "rozwala" listy inicjalizacyjne.... :-/

Adress.hpp
C/C++
#pragma once
#include <string>

class Adress {
public:
    Adress();
    Adress( const std::string & street, int number );
    ~Adress();
    void setAdressAll( const std::string & adress, int number );
    // XXX: Znów lepiej zwrócić referencję stałą bo po co kopiować std::string?
    const std::string & getStreet() const;
    // XXX: Tu zwrócimy kopię bo int nie ma istotnego kosztu kopiowania
    int getNumber() const;
   
private:
    std::string street;
    int number;
};

Person.hpp
C/C++
#pragma once
#include <string>
#include "Adress.hpp"

class Person {
public:
    Person( const std::string & name, const std::string & surname,
    const std::string & street, int number );
    Person( const std::string & name, const std::string & surname );
    ~Person();
    // XXX: Metoda przejmuje na własność adres. Jest odpowiedzialna za jego usunięcie.
    // To jest właśnie ta decyzja ale bez smart ptr niejawna.
    void setAdress( Adress * newAdress );
    void showAdress() const;
    // XXX: Znów decyzja. Zwracam kopię tego adresu ale zachowuję orginał w Person.
    Adress getAdress() const;
   
private:
    std::string name;
    std::string surname;
    Adress * adress;
};

Adress.cpp
C/C++
#include <iostream>
#include "Adress.hpp"

Adress::Adress( const std::string & street, int number )
    : street( street )
     , number( number )
{
    std::cout << "Adress: Konstruktor z argumentami: "
    << "street = " << street << " number = "
    << number << std::endl;
}

Adress::Adress()
    : street()
     , number()
{
    std::cout << "Adress: Konstruktor bez argumentów." << std::endl;
}

Adress::~Adress() {
    std::cout << "Adress: Destruktor z parametrami: "
    << "street = " << street << " number = " << number << std::endl;
}

void Adress::setAdressAll( const std::string & newStreet, int newNumber ) {
    street = newStreet;
    number = newNumber;
}

const std::string & Adress::getStreet() const {
    return street;
}

int Adress::getNumber() const {
    return number;
}

Person.cpp
C/C++
#include <iostream>
#include "Person.hpp"

Person::Person( const std::string & name, const std::string & surname,
const std::string & street, int number )
    : name( name )
     , surname( surname )
     , adress( new Adress( street, number ) )
{ }

Person::Person( const std::string & name, const std::string & surname )
    : name( name )
     , surname( surname )
     , adress( nullptr )
{ }

void Person::setAdress( Adress * newAdress ) {
    Adress * old_adress = adress;
    adress = newAdress;
    delete old_adress;
}

void Person::showAdress() const {
    std::cout << "Person: showAdress(): street = " << adress->getStreet()
    << ", number = " << adress->getNumber() << std::endl;
}

Person::~Person() {
    std::cout << "Person: Destruktor z adresem: street = " << adress->getStreet()
    << ", number = " << adress->getNumber() << std::endl;
    delete adress;
}

Adress Person::getAdress() const {
    return * adress;
}

Main.cpp
C/C++
#include <iostream>
#include "Person.hpp"
#include "Adress.hpp"

int main() {
    std::cout << "Main: Tworzenie obiektów\n";
    Person jeden( "John", "Smith", "7'th Road", 65 );
    Person dwa( "Marry", "Witten" );
   
    std::cout << "Main: Tworzenie adresu\n";
    Adress * adress1 = new Adress( "Pulasky", 42 );
   
    std::cout << "Main: Ustawienie adresu\n";
    // XXX: Tu jest ta decyzja o przejęciu własności. W kodzie main() nie
    // będzie usunięcia adress
    dwa.setAdress( adress1 );
    std::cout << "Main: Prezentacja adresów...\n";
    jeden.showAdress();
    dwa.showAdress();
   
    std::cout << "Main: Tworzenie i ustawienie nowego adresu...\n";
    Adress * adress2 = new Adress( "Fifth Road", 101 );
    // XXX: Tu znowu decyzja...
    jeden.setAdress( adress2 );
   
    std::cout << "Main: Prezentacja ustawionego adresu\n";
    jeden.showAdress();
}
P-158938
michal11
» 2017-03-13 00:18:01
Deklaracje przyjaźni w twoim kodzie są nie potrzebne.


3. W plikach nagłówkowych brakuje strażników.
Krótki plik, nie używam ifndef/prima once bo mam pewność że żaden plik nie zostanie wczytany dwa razy. Nie będę na nim dalej pracował.

tak się właśnie tworzą złe nawyki, od początku powinieneś pisać poprawny kod.


4. Z jakiego powodu nie używasz list inicjalizacyjnych?
Krótki, nierozbudowany plik; nie używam const-ów nigdzie. Czy używanie takiej listy w takich małych zadankach faktycznie przykłada się na zauważalną szybkość działania programu?

jak wyżej, plus listy inicjalizacyjne mogą zmniejszyć ilość wywoływanych funkcji np.

C/C++
MyClass::MyClass()
{
    someString = "test";
}

tutaj mamy dwie funkcje wywoływane na someString, najpierw domyślny (bezargumentowy) konstruktor a później operator przypisania (co teoretycznie może spowodować nawet dwie alokacje pamięci dla wewnętrznego bufora stringa).

C/C++
MyClass::MyClass()
    : someString( "test" )
{ }

natomiast tutaj mamy tylko jedno wywołanie funkcji, czyli konstruktor z jednym parametrem.


5. Zakładam że zdajesz sobie sprawę z wykonywanej kopii std::string przy przekazaniu argumentów? Dopuszczam że możesz jeszcze nie znać składni (stałej) referencji. Ważne żebyś zdawał sobie sprawę że tam string jest kopiowany.
Nie mam na obecną chwilę pojęcia o czym mówisz, proszę o rozjaśnienie jeśli można.

Domyślnie w C++ argumenty funkcji są przekazywane przez wartość, tzn. że  są kopiowane i dopiero te kopie są przekazywane do funkcji=. O ile przy typach wbudowanych zwykle nie ma to większego znaczenia o tyle przy klasach już ma ponieważ zazwyczaj kopiowanie klas jest dość kosztowne. Weźmy na przykład taki kod
C/C++
void fun( std::string str );
void fun2( const std::stirng & str );

funkcja fun zachowuje się domyślnie, tzn. przy każdym jej wywołaniu jakiś obiekt przekazywany do niej będzie kopiowany, więc za każdym razem będziesz kopiował napisy, pomijając to, że zwykle chcemy pracować na oryginalnym obiekcie to mamy dodatkowe funkcje związane z kopiowaniem (i np. w przypadku stirnga z kosztowną alokacją pamięci na bufor). Przy wywołaniu funkcji fun2 przekazujemy obiekt przez stałą referencję to znaczy, że nie kopiujemy obiektu, dodatkowo informujemy, że nie zmienimy przekazanego na argumentu (co także pozwala na przekazywanie tymczasowych obiektów do naszej funkcji). Więcej o referencjach możesz przeczytać albo w tutejszym kursie » Kurs C++ » Poziom 3Przekazywanie argumentów funkcji przez referencję lekcja albo gdzieś w internecie. Pro tip, praktycznie zawsze powinieneś używać referencji.


6. Metoda nazywa się getAdress() a nie zwraca żadnego adresu. Albo nazwij ją inaczej albo zwróć adres.
Znaczy się getAdress wypisuje mi adres, o to mi chodziło.

no to nie getAdress tylko printAdress.
Moim zdaniem to twoje klasy w ogóle nie powinny wykonywać żadnych funkcji na cout i cin. Powinny być maksymalnie hermetyczne i ew. dane do wypisania powinny być pobierane odpowiednimi funkcjami (ew. można dodać funkcję do wypisywania całego obiektu) a dane wcześniej wczytane ustawiane przez odpowiednie funkcje, tak aby twoja klasa nie była zależna od jakiejś biblioteki do IO.


7. Jeśli metodę nazwiesz getAdress() to powinna mieć ona sygnaturę const bo nie zmienia zawartości obiektu.
Pytanie - czy warto używać conts-ów do "getter'ów" skoro nie maja one wpływ na wartość zmiennej?

tak bo może wtedy wywoływać te funkcje na const obiektach (albo częściej na const referencjach).

Co do smart pointerów to najprościej mówiąc są to wskaźniki które same dbają o zwalnianie pamięci i dokładnie precyzują kto zarządza obiektem.
Moim zdaniem klasa Person powinna mieć std::shared_ptr do klasy Adress ponieważ wielu ludzi może mieć ten sam adres.
Masz wtedy coś takiego
C/C++
class Person {
       
private:
        std::string name;
        std::string surname;
        std::shared_ptr < Adress > adress;
public:
   
        void setAdress( const std::shared_ptr < Adress >& pAdress ); // assign new adress
        void setAdress( const Person & otherPerson ); // share other person adress

C/C++
void Person::setAdress( const std::shared_ptr < Adress >& pAdress )
{
    adress = pAdress;
}

pamiętaj, że teraz nie musisz już ręcznie kasować tego wskaźnika, on sam to zrobi.
Stworzyć nowy adres możesz tak
C/C++
std::shared_ptr < Adress > newAdress = std::make_shared < Adress >( street, 5 );

firstPerson.setAdress( newAdress );

//albo od razu
firstPerson.setAdress( std::make_shared < Adress >( street, 5 ) );

secondPerson.setAdress( firstPerson );

to jakie chcesz mieć API klasy Person zależy już od ciebie, czy chcesz mieć kopiowanie adresu od innej osoby, czy tylko proste przypisywanie, czy adres przekazujesz jako const referencję na sthared_ptr czy na obiekt Adress (
void setAdress( const Adress & pAdress );
 i już w tej funkcji tworzysz dynamicznie obiekt itp.

Moim zdaniem powinieneś też zdefiniować konstruktor kopiujący w klasie Adress.
P-158939
« 1 »
  Strona 1 z 1