Prawidłowy sposób na dodawanie obiektów do vectora

0

Piszę aplikację w C++ współpracującą z bazą danych MySQL. Chciałbym utworzyć obiekty na podstawie danych zwróconych przez zapytanie SQL i dodać je wszystkie do vectora, a następnie zwrócić ten vector jako wynik funkcji. Jak powinienem zrobić to prawidłowo?

Na razie doszedłem do dwóch sposobów:

Sposób 1

//pobieranie obiektu bazy danych (singleton)
DB& db = DB::getInstance();
//wykonywanie zapytania (DBResult to klasa przechowująca to, co zwraca funkcja mysql_query)
DBResult res = db.query("SELECT * FROM entries");

//tu tworzymy nasz wektor
vector<Entry> list;
//rezerwujemy ile miejsca potrzeba
list.reserve(res.numRows());

//w pętli pobieramy wyniki zapytania (DBRow to coś takiego jak std::map)
while(DBRow row = res.fetchAssoc()) {
  //tworzenie obiektu
  Entry e;
  //metoda feed wypełnia obiekt danymi z zapytania
  e.feed(row);
  //dodawanie obiektu do vectora
  list.push_back(e);
}

//zwracanie vectora jako wynik funkcji
return list;

Tutaj problem jest taki, że tworzymy obiekt w pętli, jego kopię dodajemy do wektora, a sam obiekt natychmiast niszczymy. Czyli w praktyce przy każdym obiegu pętli tworzone są dwa obiekty. Jak można to zrobić lepiej?

Sposób 2 (początek tak samo j.w.)

//tworzenie wektora i tworzymy od razu tyle obiektów ile potrzeba
vector<Entry> list(res.numRows());

//w pętli wypełniamy każdy z tych obiektów danymi z bazy
for(int x=0; x<res.numRows(); x++) {
  DBRow row = res.fetchAssoc();
  list[x].feed(row);
}

Tutaj nie podoba mi się ta pętla for. Wolałbym użyć while tak jak w sposobie nr 1. No i to tworzenie pustych obiektów też nie bardzo mi pasuje. Może lepiej dorobić do klasy Entry konstruktor, który przyjmuje DBRow jako parametr?

Dodatkowe pytania:

  • Co jest lepsze: vector<Entry> czy vector<Entry*>? Lepsze w sensie wydajności i łatwości kodowania. Czy jak użyję vector<Entry*> to przy usuwaniu będzie trzeba ręcznie niszczyć obiekty?
  • Czy istnieje jakiś oficjalny, zalecany sposób na dodawanie obiektów do vectora?
  • Czy gdy usunę vector za pomocą delete to wszytkie obiekty, które on przechowuje zostaną automatycznie zniszczone?
0

Pomiar czasu wykonania kodu i podgląd
co naprawdę zostało wygenerowane pod debugerem .
Inaczej jest to tylko gdybanie co by było gdyby bo właściwie
tam gdzie przyśpieszysz działanie trzeba będzie "zwolnić" w innym
miejscu .

Og.
nie tworzyć w pętlach niepotrzebnych obiektów lokalnych (wywalić przed) ,
używać referencji ,używać wskaźników, jeśli można określić rozmiar pamięci dynamicznej potrzebnej dla kodu - przydzielić od razu potrzebną ilość jednorazowo , itp itd ,,

To nie asembler .
Kompilator na podstawie kodu C++ może wygenerować kod który działa w inny
sposób niż programista zamierzał (dając ten sam efekt).

0

Nie chodzi mi o to, co działa najszybciej, ale raczej o rozwiązanie, które jest zalecane, standardowe - czy lepiej dodawać do vectora całe obiekty czy wskaźniki do nich. A jeśli całe obiekty to w jaki sposób to zrobić, żeby nie tworzyć zbędnych obiektów? Jeśli wskaźniki to jak potem je usuwać z vectora?

Zmieniłem kod tak, żeby używał wskaźników. Oto metoda:

vector<Entry*> Entry::getList() {
	DB& db = DB::getInstance();
	DBResult res = db.query("SELECT * FROM entries");

	vector<Entry*> list;
	list.reserve(res.numRows());
	while(DBRow row = res.fetchAssoc()) {
               //konstruktor Entry przyjmuje DBRow jako parametr
               list.push_back(new Entry(row));
	}

	return list;
}

Wywołuję metodę w ten sposób:

vector<Entry*> list = Entry::getList();

I jak mam teraz skasować ten vector tak, żeby zniszczyć wszystkie obiekty? Gdy robię list.clear(); to destruktory obiektów nie są wywoływane (wiem, bo mam wyświetlanie komunikatu w destruktorze). Co zrobić aby taki vector skasować wraz z obiektami?

0
//-------------------------------------
void Entry::getList( vector<Entry*> & list) {
        DB& db = DB::getInstance();
        DBResult res = db.query("SELECT * FROM entries");

 //vector<Entry*> list;

        list.reserve(res.numRows());
 
 // while(DBRow row = res.fetchAssoc())

        DBRow row ;

        while(row = res.fetchAssoc()) {
               //konstruktor Entry przyjmuje DBRow jako parametr
               list.push_back(new Entry(row));
        }
}
//-------Wywołanie---------------------------
.....
   vector<Entry*> list ;

   Entry::getList(list);
.....
//------------------------------------------

//--------     usuwanie :
int s = list.size();
for (int i = 0 ; i < s ; i++)
{
    delete list[i] ;
}
      list.clear();
//

Prosto jest usunąć na piechotę .
Można też kombinować z auto_ptr <- google :
void Entry::getList(vector< auto_ptr<Entry> > & list)
{
.....
list.push_back(auto_ptr<Entry>(new Entry));
.....
}
wtedy po zniszczeniu listy lub użyciu clear() nastąpi automatyczne zwolnienie pamięci łącznie
z wywołaniem destruktora Entry ,ale trzeba
się z tym obchodzić delikatnie i moim zdaniem trzeba bardziej uważać niż przy
zwykłych wskaźnikach ,czyli korzyści w tym przypadku nie będzie wielkich .

Czy row = res.fetchAssoc() spełni warunek przerwania pętli ?
bo za mało kodu podałeś ,,,

0
dzejo napisał(a)

Można też kombinować z auto_ptr <- google :

Użycie auto_ptr w kontenerach STL-owych jest odradzane. Jeśli już, to boost::shared_ptr lub coś w tym stylu.

0
dzejo napisał(a)

Czy row = res.fetchAssoc() spełni warunek przerwania pętli ?

Spełni. To takie moje sprytne rozszerzenie std::map służące specjalnie do pobierania danych z bazy. Ma przeciążony operator bool.

Po kilku eksperymentach doszedłem do czegoś, co wydaje się najlepszym (najładniej wyglądającym w kodzie i moim zdaniem najbardziej logicznym z punktu widzenia programisty) rozwiązaniem:

vector<Entry> Entry::getList() {
	DB& db = DB::getInstance();
	DBResult res = db.query("SELECT * FROM entries");

	vector<Entry> list;
	list.reserve(res.numRows());
	while(DBRow row = res.fetchAssoc()) {
		list.push_back(*(new Entry(row)));
	}

	return list;
}

Kodzik ten jest na tyle sprytny, że nie tworzy zbędnych obiektów. Wywołuję go w taki sposób:

vector<Entry> list = Entry::getList();

//a potem czyszczenie:
list.clear();

i destruktor jest wywoływany tylko przy tym list.clear(). Hurra!

Co więcej, nie muszę w ogóle wywoływać list.clear(), a obiekty i tak zostaną same zniszczone w momencie niszczenia wektora. Destruktory są wywoływane poprawnie (sprawdzone).

Co myślicie o tym rozwiązaniu?

0

kolego, ten kod cieknie na potęgę.

vector<Entry> list;
...
list.push_back(*(new Entry(row)));// tu jest wyciek

Zamiast tego powinien być tam obiekt automatyczny:

list.push_back(Entry(row));
0

Próbowałem robić coś takiego, jak piszesz:

list.push_back(Entry(row));

ale ten kod robi następującą rzecz:

  1. tworzy nowy obiekt Entry na podstawie row
  2. kopiuje go i tą kopię dodaje do kontenera
  3. natychmiast kasuje obiekt Entry utworzony w punkcie 1 (wywoływany jest destruktor Entry - sprawdzone, widzę to w konsoli).
  4. kopia utworzona w punkcie 2 siedzi sobie w kontenerze aż do skasowania kontenera

Takiego tworzenia zbędnych obiektów chcę uniknąć.

Sprawdziłem natomiast, że mój kod

list.push_back(*(new Entry(row)));
  1. tworzy nowy obiekt (new Entry)
  2. dodaje ten właśnie obiekt do kontenera (widzę, że nie wywołuje żadnego destruktora, więc obiektu nie usuwa)
  3. ten obiekt siedzi sobie w kontenerze aż do skasowania kontenera

Gdy kasuję kontener, kasowane są wszystkie obiekty (wywoływany jest destruktor - sprawdzone). Sądzę wiec, że nie ma żadnego wycieku. Chyba, że jest jeszcze gdzieś, gdzie nie zauważyłem. Jeśli tak, proszę o informacje.

To kiedy wywoływany jest destruktor klasy Entry wiem, bo zapisałem w nim, że ma wyświetlać komunikat na konsoli. Komunikaty wyświetlam również w konstruktorach.

0

Jest wyciek, bo to wygląda tak:

  1. Tworzony jest obiekt (new Entry)
  2. Zostaje wywołany konstruktor kopiujący.
  3. Zostaje zniszczony tylko obiekt w wektorze a nieprzypisany wskaźnik na new Entry wycieka.
0

Kodzik ten jest na tyle sprytny, że nie tworzy zbędnych obiektów

Oj ,,
nie bałdzo :-( ,podałem Ci wer fun wcześniej :

 return list;

lista lokalna w funkcji jest zbędna , jeszcze dodatkowo następuje zwrot
obiektu przez wartość co wymaga kopiowania wszystkich danych listy .
No i to co napisali poprzednicy ,,, wyciek ...

0

Hmm pytanie może głupie ale chcę mieć pewność... czy da się zatem w ogóle dodać istniejący obiekt (obiekt, nie wskaźnik na niego) do vectora nie kopiując go? Po prostu obiekt sobie istnieje w pamięci i chcę go dodać do vectora. Jakoś przez referencję albo jakkolwiek?

I pytanie nr 2: czy w takim przypadku również będzie wywołany konstruktor kopiujący?

Entry* ptr = new Entry(row);
list.push_back(*ptr);
0
  1. nie.
  2. tak.

A nie możesz zrobić tak?

vector<Entry> v(res.numRows());
DBRow row;
		
for(int i = 0; row = res.fetchAssoc(); ++i) 
{
        v[i].init(row); //<--- 'init' robi to samo co konstruktor
}
0

Zasada jest taka. Wkładając coś do kontenera, zawsze jest wywoływany konstruktor kopiujący dla danego typu. Jeśli tym typem jest wskaźnik to tym konstruktorem jest po prostu przypisanie wartości i tyle.
Aby uniknąć niepotrzebnych copy konstruktorów rozwiązania są 3.

  1. Kontener wskaźników (to jest najlepsze rozwiązanie bo można wykorzystać polimorfizm).
  2. Kontener obiektów, które inicjujesz za pomocą metody (to co jest powyżej).
  3. Kontener obiektów, które wykorzystują wzorzec copy-on-write (technicznie rzecz biorąc jest to wariacja rozwiązania 1 taka, że wygląda ona jak rozwiązanie 2). Niestety to wymaga nieco więcej świadomości i więcej klepania kodu.
0
0x666 napisał(a)
  1. nie

niecalkiem.. temat do poszukania:
boost, container, in-place factories

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