[C++] Dynamiczne tworzenie obiektów 8894 16

O temacie

Autor mathsicist

Zaczęty 1.04.2016 roku

Wyświetleń 8894

Odpowiedzi 16

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Nie jestem pewien czy to dobrze nazwałem to zagadnienie, ktoś obeznany po zerknięciu do kodu będzie wiedział na pewno o co mi chodzi. Proszę mi powiedzieć czy w przypadku wywołania employees.push_back(HR()); zostanie utworzony obiekt klasy HR czy Employee

#include <iostream>
#include <ostream>
#include <string>
#include <vector>

class Employee
{
public:
    std::string name, profession;
    std::string current_task = "NONE";
    int id, age, warrings;

    std::vector<std::string>& tasks;

    Employee::Employee(std::vector<std::string>& tasks) : tasks(tasks)
    {
        warrings = 0;
    };

    virtual void AssignNewTask(std::string input_string)
    {
        for (unsigned int i = 0; i < tasks.size(); i++)
        {
            if (input_string == tasks[i])
            {
                current_task = input_string;
                std::cout << ">> Przydzielony nowy task!" << std::endl;
                return;
            }
        }

        std::cout << input_string << "nie nalezy do listy obowiazkow " << profession << std::endl;
    }
};

class HR : public Employee
{
private:
    static std::vector<std::string> tasks;

public:
    HR::HR() : Employee(tasks)
    {
        Employee::profession = "HR Specialist";
    }
};

class Helpdesk : public Employee
{
private:
    static std::vector<std::string> tasks;

public:
    Helpdesk::Helpdesk() : Employee(tasks)
    {
        Employee::profession = "Helpdesk Technician";
    }
};

std::vector<std::string> HR::tasks = { "HR task" };
std::vector<std::string> Helpdesk::tasks = { "Helpdesk task" };

int main()
{
    std::vector<Employee> employees;
    std::cout << "Welcome message" << std::endl;

    employees.push_back(HR());

Jeżeli utworzony obiekt będzie klasy Employee to czy jest to wina konceptu czy mojej implementacji?
 

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator

inż. Avallach
Administrator

[C++] Dynamiczne tworzenie obiektów
#1 2016-04-01, 18:40(Ostatnia zmiana: 2016-04-01, 19:00)
Najpierw zostanie stworzony tymczasowy obiekt HR - bo wywołujesz przecież konstruktor "HR()".
employees.push_back oczekuje jednak argumentu typu Employee. Nie da się tam wcisnąć wartości typu HR.
Ale C++ jest "sprytny" i zamiast wywalać błąd kompilacji, sprawdzi czy da się przerobić jeden na drugi, sprawdzając między innymi czy Employee ma konstruktor który może przyjąć pojedynczy argument typu HR.
Okazuje się że ma - konstruktor kopiujący wygenerowany przez kompilator, którego sygnatura wyglądałaby tak:
Employee(const Employee& source)Ponieważ HR publicznei dziedziczy po Employee, możesz podać referencję typu HR w miejsce gdzie jest oczekiwana referencja typu Employee. Konstruktor ten skopiuje pola z twojego nowego obiektu HR które pochodzą z klasy Employee do nowego obiektu typu Employee. I to jego wstawi do wektora. Twój tymczasowy obiekt "HR()" natychmiast przestanie istnieć.

Ciężko mówić tu o "winie", ale ogólnie to jest słabość twojej implementacji. Musisz rozumieć czym są w C++ wartości. Czymś co poza typami prostymi nie występuje np w takiej Javie, a w C# ogranicza się do struktur.
std::vector<Employee> to pojemnik z "otworami" w kształcie Employee. Nie da się do nich wstawić nic innego, nie są rozciągalne.
To inna sytuacja niż z kolekcjami w Javie i C# - tam w kolekcjach trzymane są jedynie referencje. Referencja na każdy typ jest "takiej samej wielkości", więc w List<Employee> można jak najbardziej trzymać obiekty HR (a właściwie - referencje do nich).
W C++ możesz zrobić coś analogicznego - w swoim wektorze trzymać albo owrapowane referencje (samych referencji się nie da - to celowe ograniczenie języka), albo wskaźniki, albo wskaźniki inteligentne jak shared_ptr. Przykładowo możesz użyć std::vector<std::shared_ptr<Employee>>.

W sumie wywołają się tu trzy konstruktory: Employee jako konstruktor klasy bazowej dla obiektu HR, nowego obiektu HR, a wreszcie inny konstruktor Employee (tym razem "kopiujący"). Możesz to łatwo sprawdzić tym kodem:
#include <iostream>
#include <vector>
using namespace std;

class Employee
{
public:
int number;

Employee()
{
number = 666;
std::cout << "Employee::<ctor> this.number=" << number << std::endl;
}

Employee(const Employee& other)
{
std::cout << "Employee::<copy ctor> this.number=" << number << ", other.number=" << other.number << std::endl;
}
};

class HR : public Employee
{
public:
HR()
{
number = 42;
std::cout << "HR::<ctor> this.number=" << number << std::endl;
}
};

int main() {
std::vector<Employee> employees;
employees.push_back(HR());
return 0;
}
Otrzymasz:
Employee::<ctor> this.number=666
HR::<ctor> this.number=42
Employee::<copy ctor> this.number=0, other.number=42

Protip: poza niewielkimi i prostymi strukturkami nie chcesz używać w C++ trzymania czy przekazywania czegokolwiek po wartości. Do przechowywania i pilnowania czasu życia używaj shared_ptrów i unique_ptrów. Do przekazywania między funkcjami kiedy wiesz że obiekt na pewno będzie ciągle żył używaj referencji.

Pamiętaj że kiedy zrobisz coś takiego:
std::vector<Employee> pracownicy;
Employee panOdKsero {};
pracownicy.push_back(panOdKsero);
Skończysz klonując (!) panaOdKsero.
Ten w wektorze i poza nim będą identyczni, ale nie będą tym samym. Operacje wykonywane na jednym nie będą wykonywane na drugim, prowadząc do konfundujących bugów. A co gorsza takie klonowanie może być kosztowne dla pamięci i procesora. To nie jest problem z wektorem. Będzie zdarzał się zawsze kiedy przekażesz coś do funkcji po wartości lub przypiszesz do zmiennej przechowującej po wartości.

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Ok, rozumiem w czym rzecz. Nie mówię, że zrozumiałem, bo to dla mnie nowe zagadnienia, ale wiem od czego zacząć. Gdybyś mógł, wyjasnic jeszcze dlaczego przypisanie referencji tasks gryzie się z operatorem=?

Edit: głupio brzmi , tym rozumiem a zarazem nie zrozumiałem xd muszę po prostu to przetrawic i doczytac.
 

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator
dlaczego przypisanie referencji tasks gryzie się z operatorem=?
Nie pisałem niczego takiego.

Inna sprawa że nie ma czegoś takiego jak przypisanie referencji w C++. Referencja to byt na poziomie języka, który jest inicjalizowany na początku istnienia i zachowuje swoją początkową wartość do samego końca.

Referencje w C++ to coś nieco innego niż referencje w Javie i C#. Te drugie bardziej przypominają współdzielone wskaźniki.

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Nie wyraziłem się jasno. Niczego takiego nie napisałeś, po prostu podczas pracy nad kodem napotkalem na taki error: C2280 'Employee &Employee::operator =(const Employee &)': attempting to reference a deleted function, który jest spodobały wywołaniem employees.erase ().

Problem nie występowal do momentu zaimplementowania metody AssignNewTask(), więc zgaduje że gdzieś tutaj musi być problem
 


mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Rzuć okiem na to, zmieniłem tylko main():
#include <iostream>
#include <ostream>
#include <string>
#include <vector>

class Employee
{
public:
    std::string name, profession;
    std::string current_task = "NONE";
    int id, age, warrings;

    std::vector<std::string>& tasks;

    Employee::Employee(std::vector<std::string>& tasks) : tasks(tasks)
    {
        warrings = 0;
    };

    virtual void AssignNewTask(std::string input_string)
    {
        for (unsigned int i = 0; i < tasks.size(); i++)
        {
            if (input_string == tasks[i])
            {
                current_task = input_string;
                std::cout << ">> Przydzielony nowy task!" << std::endl;
                return;
            }
        }

        std::cout << input_string << "nie nalezy do listy obowiazkow " << profession << std::endl;
    }
};

class HR : public Employee
{
private:
    static std::vector<std::string> tasks;

public:
    HR::HR() : Employee(tasks)
    {
        Employee::profession = "HR Specialist";
    }
};

class Helpdesk : public Employee
{
private:
    static std::vector<std::string> tasks;

public:
    Helpdesk::Helpdesk() : Employee(tasks)
    {
        Employee::profession = "Helpdesk Technician";
    }
};

std::vector<std::string> HR::tasks = { "HR task" };
std::vector<std::string> Helpdesk::tasks = { "Helpdesk task" };

bool operator==(const Employee & obj, const std::string & std)
{
    if ((obj.name == std) || (std == obj.name))
    {
        return true;
    }
    else
    {
        return false;
    }
}

int main()
{
    std::vector<Employee> employees;
    std::cout << "Welcome message" << std::endl;

    // dziala
    employees.push_back(HR());
    employees.push_back(Helpdesk());

    // wciaz dziala
    employees.pop_back();
    employees.push_back(Helpdesk());

    // wysypuje blad!
    employees.erase(employees.begin() + 1);

    system("pause");
}

Domyślam się, że employees.erase(); "nie-wprost" używa operatora=. Szukając odpowiedzi znalazłem na stackoverflow coś takiego: http://stackoverflow.com/questions/26946201/why-do-reference-type-members-cause-implicitly-declared-copy-assignment-operator Mniej więcej rozumiem odpowiedź, ale nie potrafię jej zaimplementować. Jak powinien wyglądać mój kod, jak powinienem przeciązyć operator= aby błąd przestał występował? I czy mógłbyś po krótce wytłumaczyć dlaczego w ogóle muszę przeciążać operator=?  Nie rozumiem w ogóle idei różnej postaci operatora= (kopiowania, przypisywania etc.). Może masz gdzieś jakiś ciekawy artykuł na ten temat?

Jeżeli w tym momencie wychodzi brak podstaw z mojej strony to daj znać, postaram się to nadrobić :)

edit: rozumiem (zapewne powierzchownie) przeciążenie operatorów >>, == itp., problem mam tylko z operatorem=
 

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator

inż. Avallach
Administrator

[C++] Dynamiczne tworzenie obiektów
#7 2016-04-02, 12:29(Ostatnia zmiana: 2016-04-02, 12:35)
Już wyjaśniam.
std::vector::erase usuwa element z wektora. Ty używasz wektora nie "referencji", ale wartości. To oznacza że element który był po usuniętym, trzeba "przesunąć" na miejsce tego który usunąłeś. Ten który był po nim, także o jedno miejsce wstecz - i tak dalej.
To znaczy że dla każdego z nich musisz wykonać operację przypisania. Przykładowo usuwając z wektora "ABCD" literę "B" musisz wykonać następujące przypisania: "B=C", "C=D", a następnie zmniejszyć od końca rozmiar o 1. Jak możesz zgadnąć, jeśli te obiekty są "ciężkie", a usuwasz element po którym jest jeszcze wiele, to cała operacja będzie bardzo pracochłonna. Wtedy najlepiej tego nie robić, albo użyć innej struktury danych, jak listy.

Kompilator nie może napisać tutaj za ciebie potrzebnego do powyższej czynności operatora =, bo tak jak pisałem dwa posty temu referencje są w C++ czymś co ustawia się zawsze na początku ich istnienia i nie da się już zmienić. Jeśli miałeś w swoim obiekcie Employee pole tasks będące referencją na jakiś wektor w pamięci, to za nic nie da się już potem sprawić żeby wskazywała na inny. Taka jest zasada działania referencji w C++. A co za tym idzie, mając Alice z jednym zestawem tasków, i Boba z innym zestawem, w żaden oczywisty sposób nie da sie zaprogramować operatora przypisania Alice = Bob. Bo taski nadal pozostałyby te z Alice, nawet gdyby imię się zmieniło. Musiałbyś w swoim własnym operatorze przypisania zdecydować co z tym fantem zrobić - zapewne przekopiować taski Boba w miejsce tasków Alice, ale to nie jest coś co kompilator może bezpiecznie zgadywać, zwłaszcza że te taski mogą być nie kopiowalne.

Trzymanie membera jako referencji to często błąd - chyba że jesteś naprawdę pewien co do czasu życia bytu na który wskazujesz. Zamiast tego mógłyś trzymać po wartości albo jako shared_ptr. Referencje są najbardziej przydatne do przekazywania parametrów do funkcji, żeby uniknąć zbędnego i najczęściej niepożądanego kopiowania.

Operator = miał pierwotnie tylko jedną postać, kopiującą. Ona jest wystarczająca do poprawnego działania: "skopiuj wszystko co ma prawa strona i ustaw to po lewej stronie".
Ale czasami robimy coś takiego:
alice = new HR("Bob");Skopiowanie wszystkiego z Boba zadziała, tylko po co kopiować, skoro Bob jest obiektem tymczasowym i umrze po średniku? Kopiowanie może być kosztowne. Dlatego w C++ 11 doszedł przenoszący operator przypisania. Ustawi Alice to co miał Bob, ale zamiast to kopiować, zabierze to od Boba. Nie przejmujemy się tym, bo Bob nie jest nam już dłużej potrzebny.
To nie jest brak podstaw z twojej strony - semantaka przenoszenia jest w C++ dość świeża, pojawiła się w ciągu kilku ostatnich lat, jej implementacja nie jest tez niezbędna do działania programów (ma głównie znaczenie optymalizacyjne).

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Przepraszam, powinienem sam sprawdzić, ale jestem w drodze. Jak na pewno zauważyłeś pole tasks służy mi jedynie jako lista obowiązków, które obiekty danej klasy mogą wykonywać. Czy mogę referencje zamienić na wskaźnik? Mam na myśli to, aby obiekty posiadały wskaźnik do tasks, to chyba powinno rozwiązać problem z żywotnościa
 

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator
Zamiana referencji na surowy wskaźnik nie rozwiązuje problemu z żywotnością - żadne z nich jej nie utrzymuje. Czas życia obiektu wynika zawsze albo z tego że jest zaalokowany na stosie jako wartość, albo na stercie przez smart pointer (jest jeszcze alokacja na stercie przez "new" z ręcznym zarządzaniem czasem życia, ale to odradzam).

W każdym razie zamiana na wskaźnik, czy to surowy, czy inteligentny, czy nawet na reference wrapper, wyeliminuje ci problem z operatorem przypisania.

Wciąz w twoim kodzie jest trochę absurdów - jak prywatne statyczne pole-wartość tasks w klasie HR przysłaniające publiczne instancyjne pole-referencję tasks z klasy bazowej Employee. To wyglda jakbyś chciał tam na siłę wcisnąć jakąś najgorszą bzdurę żeby sprawdzić czy kompilator to w ogóle przepuści, albo spłatać wredny żart komuś kto będzie musiał po tobie coś z tym kodem zrobić. Alternatywnie, jesteś po prostu masochistą i chcesz narobić problemów przyszłemu sobie :F

W profesjonalnej pracy trzeba mieć odwrotne podejście - dbać o to żeby kod był jak najbardziej oczywisty do zrozumienia. Pamiętam jak kolega z zespołu wczoraj commitując coś naprawdę niefajnego, mówił że ma ochotę dopisać komentarz w stylu "dear future me, I'm so sorry for this but I had no choice" :F

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Cytuj
Wciąz w twoim kodzie jest trochę absurdów - jak prywatne statyczne pole-wartość tasks w klasie HR przysłaniające publiczne instancyjne pole-referencję tasks z klasy bazowej Employee.

No cóż... do tej pory sądziłem że to sprytne posunięcie z mojej strony w osiągnieciu celu xd wobec tego jak mi radzisz zaimplementowac te metodę w taki sposób, aby była dziedziczona, ale przyjmowała inny parametr (oddzielnny vector tasks dla każdej podklasy)?

a, i oczywiście dzięki za wszystko. Zdecydowanie lepiej to rozumiem

 


mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
okej, i tak jestem tobie ogromnie wdzięczny
 

Adanos

Adanos

Administrator
Szara eminencja
posty5204
Propsy3870
ProfesjaProgramista
  • Administrator
  • Szara eminencja
Cytuj
Wciąz w twoim kodzie jest trochę absurdów - jak prywatne statyczne pole-wartość tasks w klasie HR przysłaniające publiczne instancyjne pole-referencję tasks z klasy bazowej Employee.

No cóż... do tej pory sądziłem że to sprytne posunięcie z mojej strony w osiągnieciu celu xd wobec tego jak mi radzisz zaimplementowac te metodę w taki sposób, aby była dziedziczona, ale przyjmowała inny parametr (oddzielnny vector tasks dla każdej podklasy)?

a, i oczywiście dzięki za wszystko. Zdecydowanie lepiej to rozumiem
Nie lepiej po prostu zrobić, by w klasie bazowej jaką jest Employee uczynić pole tasks chronione? Wtedy w podklasie tasks będzie prywatnym polem.
Dlaczego chcesz mieć to pole statyczne? Dlaczego chcesz mieć wszystkie zadania wszystkich pracowników, a nie wszystkie zadania dla konkretnego pracownika? Czy nie lepiej uczynić klasę Employee abstrakcyjną?

mathsicist

mathsicist

Użytkownicy
posty209
Propsy269
NagrodyV
Profesjabrak
  • Użytkownicy
Nie lepiej po prostu zrobić, by w klasie bazowej jaką jest Employee uczynić pole tasks chronione? Wtedy w podklasie tasks będzie prywatnym polem.
Jezu, to było takie proste. Niepotrzebnie się gimnastykowałem. Rozwiązałem problem, dzięki :)

Dlaczego chcesz mieć to pole statyczne? Dlaczego chcesz mieć wszystkie zadania wszystkich pracowników, a nie wszystkie zadania dla konkretnego pracownika?

Moim kaprysem jest stworzyć listę obowiązków dla danej grupy pracowników, tak, aby przydzielając zadanie pracownikowi mógłbym przydzielić tylko te, które zostało dla tej grupy określone.

Czy nie lepiej uczynić klasę Employee abstrakcyjną?
Nie wiem czemu tego nie zrobiłem, już się poprawiłem.


Wracając do problemu z przetrzymywaniem obiektów podklas klasy Employee w jednym pojemniku std::vector<std::shared_ptr<Employee>> employees:
posłuchałem się twojej rady, Avallach, i stworzyłem std::vector z inteligentnymi wskaźnikami. Zagadnienie wskaźników zaledwie liznąłem, więc proszę o wyrozumiałość, lecz jak, używając wskaźnika do obiektu klasy dziedziczonej, mogę uzyskać dostęp do metod zdefiniowanych w tej podklasie? Na stackoverflow znalazłem takie zdanie:
Cytuj
A pointer of type Person can only be used to access data/addresses(functions) that are part of the Person object. The compiler simply has no way of knowing what all classes could be deriving from Person and hence which operations are legal.
Wobec tego pytanie: czy da się to ominąć?

edit: sugerowane odpowiedzi wydają się bardzo zawiłe i wykraczające poza moje zdolności (chociaż te chyba już dawno zostało przekroczone). Kod miał być tylko projektem na uczelnie, gdzie wcale (niestety) tak dużo ode mnie nie wymagają. W tym miejscu poprosić o opinię czy w ogóle powinienem brnąć w to dalej czy zmienić kurs? Oczywiście nadal chcę wiedzieć jak schludnie rozwiązać problem, i prosiłbym o wytłumaczenie zagadnienia z wskaźnikami, jednak prosiłbym również o jakieś bardziej prymitywne zaimplementowanie "dynamicznego tworzenia obiektów", ale łatwiejszego do napisania przez laika

edit2: korzystając z waszej uprzejmości chciałbym się upewnić: nie definiując destruktorów dla klas pochodnych to niszcząc obiekt podklasy wywoływany jest destruktor głównej klasy, tak?
 

Adanos

Adanos

Administrator
Szara eminencja
posty5204
Propsy3870
ProfesjaProgramista
  • Administrator
  • Szara eminencja
edit2: korzystając z waszej uprzejmości chciałbym się upewnić: nie definiując destruktorów dla klas pochodnych to niszcząc obiekt podklasy wywoływany jest destruktor głównej klasy, tak?
Kolejność wywoływania destruktorów jest odwrotna do wywołania konstruktorów. Tak więc najpierw z klasy pochodnej jest wywoływany destruktor, a dopiero po nim z klasy bazowej. Co jeśli nie zadeklarujesz destruktora? Wydaje mi się, że najpierw wywoływany jest destruktor klasy pochodnej, która wywołuje destruktory członków klasy, a dopiero później destruktor klasy bazowej.

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator
edit2: korzystając z waszej uprzejmości chciałbym się upewnić: nie definiując destruktorów dla klas pochodnych to niszcząc obiekt podklasy wywoływany jest destruktor głównej klasy, tak?
Klasa bazowa powinna mieć destruktor zawsze zdefiniowany i to jako wirtualny, nawet gdyby miał być pusty: "public: virtual ~Employee() = default;". Inaczej czekają cię problemy.

Wyjaśnienie: http://stackoverflow.com/questions/5873515/why-should-the-destructor-of-base-classes-be-virtual


0 użytkowników i 1 Gość przegląda ten wątek.
0 użytkowników
Do góry