Przetłumaczenie kodu z Javy na C++ – tablica obiektów jako odpowiednik javovego ArrayList

0

Mam za zadanie przetłumaczyć swój kod z Javy na C++. W pierwotnym programie mam utworzoną tablicę obiektów ArrayList, do której zapisuję w pętli anonimowe obiekty utworzonej przeze mnie klasy w następujący sposób:

static ArrayList lista = new ArrayList();
[...]
for (...)
   if (...)
       lista.add(new Klasa(0,2,4,"tekst");

Jak utworzyć coś takiego w C++?

4

Jeśli korzystasz z polimorfizmu to powinno to wyglądać tak:

std::vector<std::shared_ptr<ITwojInterface>> mV;

mV.push_back(std::make_shared<Klasa>(0, 2, 4,"tekst"));

BTW: ty na serio używasz polskiego w kodzie? Czy to tylko przykład?

0

To przykład ;) Dzięki za pomoc!

0

Mam teraz problem z pobieraniem zmiennych z tych anonimowych obiektów.

Mam klasę (przykładowo):

class Klasa {
   int x;
   string tentekst;
 
   Klasa (int X, string tekst)
   {
         this->x = X;
         this->tentekst = tekst;
   }
}

i tablicę obiektów, do której dodaję obiekty następująco

static std::vector<std::shared_ptr<Klasa>> Tablica;

Tablica.push_back(make_shared<Klasa>(1,"tekst"));
Tablica.push_back(make_shared<Klasa>(5,"tekst2"));

Do sprawdzenia kilku warunków potrzebuję pobrać jedną ze zmiennych takiego obiektu anonimowego. Powiedzmy, że definiuję i jako jakiś indeks tego obiektu w tablicy obiektów i wrzucam

((shared_ptr<Kratka>)Tablica.at(i)).x

Dostaję przy tym błąd:

class "std::shared_ptr:<Klasa>" has no member "x"

Jak mogę pobrać te zmienne z obiektów anonimowych znajdujących się w tablicy?

1

Nie możesz tak castować, od tego masz std::static_pointer_cast i std::dynamic_pointer_cast.

A do odwołania się do elementu klasy pod wskaźnikiem służy operator ->

1

Po co rzutowanie(cały ten interfejs jest dziwny), jak chcesz mieć polimorfizm to dodaj metodę
int getX();
pamiętaj że at() na pewno rzuci wyjątkiem a []
If the container size is greater than n, the function never throws exceptions (no-throw guarantee).
Otherwise, the behavior is undefined.

edit:
statyczny vector. Ogólnie co ty próbujesz osiągnąć? Podrzuć coś to wymyślimy wspólnie.

0

Okeej... A jak będzie wyglądać zastosowanie tego na tym przykładzie? :D

1

Może lepiej pokaż ten kod Java, który próbujesz przetłumaczyć na C++.
Będzie łatwiej zrozumieć co chcesz osiągnąć.

0

Kodu jest 350 linijek, trochę go pogmatwałem. Chodzi o implementację algorytmu A* poruszającego się po mapie będącej dwuwymiarową tablicą typu integer. Mam utworzoną klasę definiującą pojedyncze kratki, zawierającą ok. 10 zmiennych których wartości przypisuje konstruktor domyślny. Tworzę dwie listy obiektów, które program stopniowo wypełnia anonimowymi obiektami definiującymi kolejne kratki, po których się porusza. Każda z tych anonimowych kratek ma indywidualne parametry, które musi przechowywać do końca działania programu, i muszę w międzyczasie pobierać pojedyncze parametry tych kratek (odwołując się do indeksu ArrayList, w której się znajdują) by dokonywać różnych porównań i w ostatecznej fazie działania programu wrócić do punktu wyjściowego odczytując z kolejnych kratek wartości zmiennych określających współrzędne rodziców każdej z kratek. W Javie wyciągałem te zmienne rzutując ArrayList z metodą get() na klasę "Kratka" i po kropce dodając nazwę pobieranej zmiennej, np. ((Kratka)listaOtwarta.get(i)).xRodzica

5

Dobra, uporządkujmy trochę informacje.

Interfejsy i klasy abstrakcyjne w C++

W C++ domyślnie przekazujesz instancje klas przez wartości, a nie przez referencje.
Jakie są tego skutki:

class Klasa
{
public:
    Klasa(int x) { 
        this->x = x; 
    }

    int getX() { 
        return x; 
    }

    void setX(int x) { 
        this->x = x; 
    }
private:
    int x;
};

Klasa a(8);
Klasa b(9);

a = b;
b.setX(6);

Przypisanie b do a powoduje skopiowanie b do a.

a.getX() != b.getX()

Co zrobić, jeśli mamy klasę abstrakcyjną? Musimy do jej instancji odwoływać się przez wskaźnik lub referencję.
Wskaźnik w C++ to z grubsza to samo, co referencja w Javie.

Referencja w C++ to taki wskaźnik, którego nie możemy przestawiać i ogólnie korzystamy z niego jak ze zwykłej zmiennej, bez tych wszystkich gwiazdek i strzałek.

Czas życia zmiennych i zakresy

Nie możemy jednak zrobić czegoś takiego:

KlasaAbstrakcyjna *nowa_instancja()
{
    KlasaAbstrakcyjnaImpl result;
    return &result;
};

W C++ są 2 typy zmiennych: alokowane na stosie (stack) i na stercie (heap). W Javie jest podobnie - prymitywy, takie jak int, są alokowane na stosie, a instancje klas są alokowane na stercie. Zmienne alokowane na stosie są niszczone po wyjściu poza zakres. Oznacza to, że po wykonaniu funkcji nowa_instancja wskaźnik, który zwróciła, będzie wskazywać na wyczyszczoną pamięć.
Dzieje się tak, gdyż w C++ nie ma garbage collectora i wszystko jest domyślnie alokowane na stosie.

Żeby zmienna przeżyła zakończenie funkcji, musi zostać zaalokowana na stercie. Jednak pamięć zaalokowaną na stercie musimy czyścić ręcznie.
Tradycyjnie robiono to tak:

KlasaAbstrakcyjna* instancja = new KlasaAbstrakcyjnaImp();
.. // zrób coś ze zmienną instancja
delete instancja;

Bardzo łatwo zapomnieć o zniszczeniu zmiennej. Wtedy nastąpi wyciek pamięci.

Jednak C++ oferuje mechanizm bardzo ułatwiający wiele zadań, w tym zarządzanie pamięcią: destruktory. Destruktor to funkcja wywoływana po wyjściu zmiennej poza zakres, czyli tuż przed zniszczeniem obiektu.

class Destruktor
{
public:
    ~Destruktor()
    {
        cout << "Wywołano destruktor" << endl;
    }
};

int main()
{
    Destruktor d;
    // wypisze "Wywołano destruktor" w konsoli
}

I teraz to połączmy: nie możemy korzystać z klas abstrakcyjnych alokując ich instancje na stosie, ale możemy wskaźniki opakować w klasy, które wyczyszczą pamięć za nas!

unique_ptr i shared_ptr

Tu już sprawa jest dosyć prosta:

  • unique_ptr wyczyści pamięć, kiedy zmienna wyjdzie poza zakres:
int main()
{
    unique_ptr<KlasaAbstrakcyjna> instancja = make_unique<KlasaAbstrakcyjnaImpl>();
    // <- tutaj `instancja` zostanie zniszczona
}
  • shared_ptr zlicza referencje i czyści pamięć wtedy, kiedy żaden obiekt nie odwołuje się do danej instancji - działa więc podobnie do garbage collectora (ale nie tak samo, polecam poczytać o różnicach. Hasło: garbage collection vs reference counting)
int main()
{
    shared_ptr<KlasaAbstrakcyjna> instancja1 = make_shared<KlasaAbstrakcyjnaImpl>(); // 1 referencja
    shared_ptr<KlasaAbstrakcyjna> instancja2 = instancja1; // 2 referencje

    instancja1 = nullptr; // 1 referencja
    instancja2 = nullptr; // <- 0 referencji: tutaj zaalokowana zmienna zostanie zniszczona
}

Oczywiście nie trzeba przypisywać nullptr do zmiennych instancja i instancja2 - shared_ptr ma zdefiniowany destruktor, który zrobi to za nas.

Działa to dlatego, że instancje unique_ptr i shared_ptr są alokowane na stosie, więc są niszczone automatycznie.

vector

Kolekcje w STL-u nie są tak fajnie zaprojektowane jak w Javie (nie ma interfejsów i ich implementacji, tylko od razu implementacje. Na szczęście są szablony). W C++ można przeładowywać operatory, więc vector ma przeładowany operator [].

vector<int> liczby { 1, 2, 3, 4 };
liczby.push_back(5); // { 1, 2, 3, 4, 5 }

Zmienne dodawane do vectora są przekazywane przez wartość, więc są kopiowane.
Nie możemy jednak kopiować klas abstrakcyjnych. Dlatego powinniśmy zrobić coś takiego:

vector<shared_ptr<KlasaAbstrakcyjna>> instancje {
    make_shared<KlasaAbstrakcyjnaImpl>(1),
    make_shared<KlasaAbstrakcyjnaImpl>(2)
};

instancja[0]->zrobCos();

Zmienne pobierane z vectora są przekazywane przez referencję, więc kopiowanie nie następuje.

Dobra, mam nadzieję, że nic nie pomieszałem - od dłuższego czasu siedzę w Javie, więc mogłem coś pomylić. Powinno być OK, bo sprawdzałem kody w http://cpp.sh/ i wszystko się kompilowało ;)

0

Dzięki za tak obszerne wyjaśnienie! Potrzebuję chwili żeby to przyswoić, jutro mam nadzieję dokończyć te tłumaczenie :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1