• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

C++ templaty. Różne typy danych w liście.

VPS Starter Arubacloud
0 głosów
853 wizyt
pytanie zadane 13 października 2018 w C i C++ przez Kurczak Użytkownik (940 p.)

Cześć,
W ramach odświeżenia wiedzy na temat tworzenia listy i zaznajomienia się z templatami postanowiłem połączyć te 2 rzeczy i stworzyć listę na templatach. Ogólnie wyszło OK, jeszcze nie przychodzi mi wszystko intuicyjnie, ale obyło się bez większych problemów. Piszę ten post odnośnie dwóch spraw:
1) Ocena kodu. Czego nie powinienem robić? Co można poprawić?  Czy konceptualnie projekt jest poprawny? Konwencja pisowni jest poprawna? To tylko taki teaser, planuję dodać kilka przydatnych funkcji, ale zanim to zrobię, chcę się upewnić, że idę w dobrym kierunku.
2) Chciałbym za pomocą takiej listy zrobić zrobić np. Strukturę "Książka", która będzie przechowywała listę jej parametrów jak np. Tytuł jako łańcuch znaków, ID jako liczbę całkowitą itp. Niestety w chwili obecnej nie mogę stworzyć listy, która nie będzie miała z góry określonego typu. Mogę zrobić listę int'ów, która przechowa mi np. cenę i ID, ale już tytułu tam nie wrzucę i vice versa. Da się to jakoś obejść? Może istnieje jakieś inne podejście niż templata w takim wypadku?

Poniżej wrzucam kod:
Plik nagłówkowy:

#pragma once
#include "pch.h"
#include <iostream>

template <class T>
class Element //AKA Node
{
private:
	Element<T> *next;
	char * name; //name of our data container
	T value;    //stored value
public:
	Element(const char *_name, T _value, Element<T> *_next);
	~Element();
	const char * getName();
	T getValue();
	void setName(const char *_name);
	void setValue(T _value);
	void setNext(Element<T> *_next);
	Element<T>* getNext();
	void deleteElement();
};

template <class T>
class ElementList
{
private:
	Element<T> *head;
public:
	ElementList();
	~ElementList();
	void addElement(const char *name, T value);
	void deleteList();
	void printList();
};

Plik źródłowy + test
 

#include "pch.h"
#include "LinkedList.h"


using namespace std;

template <class T>
Element<T>::Element(const char *_name, T _value, Element<T> *_next)
{
	setName(_name);
	value = _value;
	setNext(_next);
}

template <class T>
Element<T>::~Element()
{
	deleteElement();
	delete this;
}

template <class T>
const char *Element<T>::getName()
{
	return name;
}

 template <class T>
T Element<T>::getValue()
{
	return value;
}

template <class T>
void Element<T>::setName(const char *_name)
{
	delete[] name; //better safe than sorry
	name = new char[strlen(_name) + 1]; //let's allocate some memory for our string
	strcpy_s(name, strlen(_name) + 1, _name); //time to copy
}

template <class T>
void Element<T>::setValue(T _value)
{
	if (isdigit(_value))
		value = _value;
	else //if inserted value is not a digit, it's apparently a string of characters
	{
		delete[] value;
		value = new char(strlen(_value) + 1);
		strcpy_s(value, strlen(_value) + 1, _value);
	}
}

template <class T>
void Element<T>::setNext(Element<T> *_next)
{
	next = _next;
}

template <class T>
Element<T>* Element<T>::getNext()
{
	return next;
}

template <class T>
void Element<T>::deleteElement()
{
	delete[] name;
	//if (!isdigit(value))
		//delete[] value;
}


/*List's methods from now on*/

template <class T>
ElementList<T>::ElementList()
{
	head = nullptr;
}

template <class T>
ElementList<T>::~ElementList()
{
	deleteList();
}

template <class T>
void ElementList<T>::addElement(const char *name, T value)
{
	if (!head) //No elements in the list
	{
		head = new Element<T>(name, value, nullptr);
	}
	else //Allright, list is not empty, thus we're throwing the element to the last position
	{
		Element<T> *current = head;
		while (current->getNext())
		{
			current = current->getNext();
		}
		Element<T> *temp = new Element<T>(name, value, nullptr);
		current->setNext(temp);

	}
}

template <class T>
void ElementList<T>::deleteList()
{
	if (!head->getNext() && head) //just one element in the list
		head->deleteElement();
	else if (head->getNext()) //more then one element in the list
	{
		Element<T> *current = head->getNext();
		Element<T> *previous = head;
		while (current)
		{
			previous->deleteElement();
			previous = current;
			current = current->getNext();
		}
		previous->deleteElement();
	}
}

template <class T>
void ElementList<T>::printList()
{
	Element<T> *current = head; //Just to keep track
	while (current->getNext())
	{
		cout << current->getName() << endl;
		cout << current->getValue() << endl;
		cout << endl;
		current = current->getNext();
	}
	cout << current->getName() << endl;
	cout << current->getValue() << endl;
	cout << endl;
}


int main()
{
	ElementList<double> Book1;
	Book1.addElement("Price in zl", 28.50);
	Book1.addElement("ID", 12345);
	Book1.addElement("Height", 15.3);
	Book1.addElement("Width", 8.4);
	//Book1.addElement("Title", "C++ programming");//I'd like it to work this way as well
	Book1.printList();
}

 

komentarz 13 października 2018 przez mokrowski Mędrzec (156,220 p.)
Masz jeszcze kilka błędów, konstrukcji niebezpiecznych i niezręczności. Nie wiem jednak jaki jest stan bieżący kodu po uwagach koleżanek/kolegów.
komentarz 13 października 2018 przez Kurczak Użytkownik (940 p.)
przeniesione 13 października 2018 przez draghan

Okej, postanowiłem polecieć w nieco innym kierunku. Chcę stworzyć szablon Listy, której elementy nie będą miały ani nazwy, ani żadnych wartości. Następnie zrobię klasę "Książka", która będzie dziedziczyć z klasy element i tam dodam sobie zmienne, których potrzebuję jak int ID, char *title itp. 
Tak wygląda plik nagłówkowy z deklaracją elementu:

template <class T>
class Element //AKA Node
{
private:
	Element *next;
public:
	Element(Element *_next);
	~Element();
	void setNext(Element *_next);
	Element* getNext();
	void deleteElement();
	void printElement();
};

Czy muszę jednak używać tego parametru "class T"? W tym podstawowym szablonie nie będę używać żadnych nieznanych mi typów, wszystkie potrzebne mi typy zmiennych określę w klasie "Książka"? Istnieje zapis na wzór template <class> lub template <>, na zasadzie funkcji, która nie przyjmuje żadnych argumentów?

 

//Edit
Pytanie głupie. Uparłem się na templaty, bo chcę się tego nauczyć, a z braku doświadczenia wychodzą takie dziwne rozkminy.

komentarz 13 października 2018 przez draghan VIP (106,230 p.)

Chcę stworzyć szablon Listy, której elementy nie będą miały ani nazwy, ani żadnych wartości. Następnie zrobię klasę "Książka", która będzie dziedziczyć z klasy element i tam dodam sobie zmienne, których potrzebuję jak int ID, char *title itp

W takim wypadku to nie jest szablon, tylko klasyczna klasa bazowa. Tak też jest w porządku, tylko wymuszasz dziedziczenie po typie Element.

komentarz 14 października 2018 przez mokrowski Mędrzec (156,220 p.)

... tylko wymuszasz dziedziczenie po typie Element.

Co sensu większego nie ma (wraz z dziedziczeniem Ksiazka po Element) bo lepiej w takim przypadku trzymać listę (czyli Element) z polem data wskazującą na T które jest Książką. Naturalnie wtedy będziesz miał listę książek gdzie obiekt z klasy Książka nie będzie wiedział o innych w liście.

komentarz 14 października 2018 przez draghan VIP (106,230 p.)

Co sensu większego nie ma (...) Naturalnie wtedy będziesz miał listę książek gdzie obiekt z klasy Książka nie będzie wiedział o innych w liście.

We wszystkim można znaleźć odrobinę sensu i nie wszędzie trzeba szablony "pchać" (co warto zaznaczyć, jeśli ktoś stara się ich nauczyć) - tutaj mógłby to być przypadek, w którym widoczność (z wnętrza obiektu na liście) pozostałych elementów z listy nie jest głupim pomysłem.

Dla tego konkretnego przypadku "Książka" nie powinna wiedzieć, że jest "Element"em i szablon spełniłby swoją rolę znakomicie.

4 odpowiedzi

0 głosów
odpowiedź 13 października 2018 przez j23 Mędrzec (195,220 p.)
edycja 13 października 2018 przez j23
  • Dlaczego char*, a nie std::string? Piszesz w C z klasami, czy w C++?
  • Linia 45: isdigit służy do testowania znaków, a nie liczb. Jeśli chcesz wiedzieć, czy T jest typem liczbowym, użyj std::is_arithmetic (np. w warunku if constexpr).

 

komentarz 13 października 2018 przez monika90 Pasjonat (22,940 p.)

Konstruktor Elementu robi delete na niezanicjalizowanej składowej, destruktor wywołuje delete this.

komentarz 13 października 2018 przez j23 Mędrzec (195,220 p.)
edycja 13 października 2018 przez j23

Po pierwsze, dyskusje na temat odpowiedzi powinieneś kontynuować w komentarzu. To nie jest klasyczne forum. Po drugie, pomimo że zaproponowałem rozwiązanie z std::is_arithmetic, to nie jest najlepsze rozwiązanie, bo przecież T może być czymkolwiek, nawet jeśli będzie to wskaźnik char*, to nie masz gwarancji, że będzie wskazywał na c-string. Dlatego najlepszym rozwiązaniem będzie po prostu:

template <class T>
void Element<T>::setValue(const T& _value)
{
        value = _value;
}

Jak chcesz, żeby T trzymał tekst, to niech T = std::string. Proste i bezpieczne.

 

Po trzecie, chyba jestem nieprzytomny dzisiaj, bo nie bardzo rozumiem, o co pytasz w drugim pytaniu, ale jeśli chodzi Ci o listę heterogeniczną, to masz coś takiego jak std::any lub std::variant.

komentarz 13 października 2018 przez Kurczak Użytkownik (940 p.)
Zależy mi na tym, aby nie korzystać z std, tylko poniekąd stworzyć samodzielnie bibliotekę na jej podobieństwo. Nie chodzi mi o to, aby wyważać otwarte drzwi, tylko raczej dobrać się do zamka i poznać jego mechanikę działania.
komentarz 13 października 2018 przez j23 Mędrzec (195,220 p.)

Implementacja any nie jest jakaś strasznie skompilowana, ale nie wiem, czy jest sens robić coś, co już jest zrobione. Inna opcja to jest to, co zaproponował Criss, czyli polimorfizm. Ja jeszcze mogę dodać takie rozwiązanie: trzymaj te dane w formie tekstowej, np. w std::map<std::string, std::string>. Kluczem niech będzie nazwa parametru, a wartością... wartość :P

0 głosów
odpowiedź 13 października 2018 przez Kurczak Użytkownik (940 p.)
edycja 13 października 2018 przez Kurczak
Okej, zastąpiłem funkcję isdigit funckją is_arithmetic. Używam char'ów, w formie praktyki z alokacją i czyszczeniem pamięci oraz pracy ze wskaźnikami. Co z drugą częścią pytania? Ona mnie nurtuje najbardziej.
komentarz 13 października 2018 przez j23 Mędrzec (195,220 p.)
0 głosów
odpowiedź 13 października 2018 przez criss Mędrzec (172,590 p.)

Niestety w chwili obecnej nie mogę stworzyć listy, która nie będzie miała z góry określonego typu.

I nigdy nie będziesz mógł traktując to w dosłowny sposób.

Mogę zrobić listę int'ów, która przechowa mi np. cenę i ID, ale już tytułu tam nie wrzucę i vice versa. Da się to jakoś obejść? 

Istnieje polimorfizm. Zrób sobie klase dla każdej property i spróbuj zrobić jakąś klasę bazową-interfejs która będzie unifikować dostęp do wszystkich properties. Wtedy możesz sobie zrobić liste typu wskaźnika na ten interfejs.

Istnieje też std::variant z c++17, ale o ile to możliwe, starałbym się wszystko ogarnąć polimorfizmem.

komentarz 13 października 2018 przez Kurczak Użytkownik (940 p.)
Dzięki za odpowiedź! W wolnej chwili wypróbuję sposób. Czy tak się ogólnie robi czy jest to trochę praktyka na siłę, robiona pod moją implementację listy?
komentarz 13 października 2018 przez criss Mędrzec (172,590 p.)
Polimorfizm? Jasne, że się robi :D std::variant? Cóż, po coś istnieje, ale moim zdaniem brzydkie to..
komentarz 13 października 2018 przez draghan VIP (106,230 p.)

Niestety w chwili obecnej nie mogę stworzyć listy, która nie będzie miała z góry określonego typu.

I nigdy nie będziesz mógł traktując to w dosłowny sposób.

Well, technically...

0 głosów
odpowiedź 15 października 2018 przez Kurczak Użytkownik (940 p.)

Okeej, posiedziałem chwilę nad projektem i:
-Zrezygnowałem z templatki klasy Element
-Zrobiłem prostą listę, elementy posiadają jedynie ID
-Stworzyłem klasę "Book", która dziedziczy z klasy "Element". Na poczatku książka będzie miała jedynie ID  (zmienna z klasy element) oraz nowo dodaną zmienną - int cena. 
-Chciałbym zrefaktoryzować kod tak, aby klasa "ElementList" była albo templatką albo utworzyć klasę "BookList", która dziedziczyłaby z "ElementList". 
Używając templat, zamiast tego fragmentu:
 

class ElementList 
{
private:
	Element *head; 
public:
	ElementList();
	~ElementList();
	Element* addElement(int _ID);
}

Myślałem, aby uzyskać taką templatkę:

template <class T>
class ElementList  
{
private:
	T *head; 
public:
	ElementList();
	~ElementList();
	T* addElement(int _ID);
}
template <class T>
Element<T>* ElementList<T>::addElement(int _ID) 
{
	if (!head) //No elements in the list
	{
		head = new T(_ID);
		return head;
	}
	else //Allright, list is not emptyn
	{
		T *current = head;
		while (current->getNext() && _ID>current->getNext()->getId()) //either there's no more elements
			current = current->getNext();                                         //or we've encountered a smaller ID
			if (head->getId() > _ID) //First let's check if the head has bigger ID
			{
				T *temp = new T(_ID, head);
				head = temp;
				return temp;
			}
			else if (!current->getNext())//If head has smaller id check for next element's existance
			{
				T *temp = new T(_ID);
				current->setNext(temp);
				return temp;
			}
			else if (_ID > current->getNext()->getId()) //if the next element exists it's having a bigger ID
			{
				T *temp = new T(_ID, current->getNext());
				current->setNext(temp);
				return temp;
			}
	}
}

No, ale wtedy pojawia się między innymi problem z konstruktorami.


Drugi pomysł (klasa "BookList" dziedzicząca z "ElementList")
Tutaj niestety nie mam pomysłu jak ją napisać, by nie przepisywać funkcji "addElement(int ID)". Funkcja dodająca książkę byłaby praktycznie taka sama, jedynie zamiast obiektów klasy Element używałbym dziedziczących od nich obiektów klasy Book i ustawiał cenę książki, co zajmuje dosłownie linijkę kodu.

LinkedList.h
LinkedList.cpp


 

Podobne pytania

0 głosów
1 odpowiedź 2,001 wizyt
pytanie zadane 20 października 2019 w C i C++ przez kalczur Gaduła (4,320 p.)
0 głosów
2 odpowiedzi 230 wizyt
pytanie zadane 6 listopada 2022 w Python przez Ichbinda Nowicjusz (230 p.)
0 głosów
1 odpowiedź 323 wizyt
pytanie zadane 13 września 2022 w Python przez Ziom Początkujący (430 p.)

92,775 zapytań

141,703 odpowiedzi

320,570 komentarzy

62,109 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...