Salve, recentemente mi sono imbattuto nell'implementazione della classe lista e ho provato a renderla una classe template, così da non dover creare 700 classi diverse solo per immagazzinare un dato diverso (che sia una persona, un int o altro).
so che i template andrebbero definiti tutti in un unico file (nel .h appunto), ma preferirei per quanto possibile separare l'implementazione, quindi ho provato a definirli tutti in un file .cpp esterno. ovviamente ho avuto i soliti problemi di linking e ho cercato/letto/provato vari metodi, alcuni funzionanti, altri no, e ho una confusione assurda riguardante questa cosa.
uno dei metodi che avevo trovato (e che sembra funzionante) era quello di includere il file .cpp dentro al file .h, tramite un "gioco" di include guard.
// list.h:
class List
{ // qualcosa };
#define LIST_IMPLEMENT
#include "list.cpp"
// list.cpp
#ifndef LIST_IMPLEMENT
#include "list.h"
#else
// definizione delle varie funzioni
ma volevo provare a cercare qualche altro metodo che evitasse l'uso di queste define.
pocciando un po' il codice, per caso ho scoperto un metodo che (sembra..) funziona: anche senza alcuna define, vado semplicemente a chiamarmi il .cpp delle funzioni che mi servono (anzichè il .h) e le utilizzo normalmente, senza nemmeno dover includere il file .cpp nel file .h.
ma mi sorge un dubbio: questa scoperta "per caso" è davvero logicamente corretta o è possibile definire in file esterni i template in modi molto migliori?
vi lascio qui sotto i 5 file del progettino (se notate cose commentate a caso portate pazienza per favore...) se servono per capire ciò che sto dicendo (notare l'inclusione dei file nodo.cpp e list.cpp (anziché i relativi .h) nei rispettivi list.cpp e main.cpp).
list.h:
#pragma once
#include "nodo.h"
#include <iostream>
template <class T>
class List
{
protected:
Nodo<T>* head; // puntatore alla testa della lista
/* Restituisce il puntatore all'elemento di index-esima posizione.
Se index non è valido viene lanciata un'out_of_range exception.
*/
Nodo<T>* operator[] (const unsigned int index);
// Effettua lo swap delle informazioni contenute nei due nodi.
friend void swap(Nodo<T>* a, Nodo<T>* b);
public:
// Crea una lista vuota.
List();
/* Costruttore copia.
* copy = lista di cui effettuare la copia.
*/
List(const List<T>& copy);
/* Aggiunge un elemento in testa dato il puntatore al nodo.
* node = puntatore al nodo da aggiungere alla lista - verrà modificato il nodo successivo di node. Usare push_front(T& item) per aggiungere soltanto il valore.
*/
void push_front(Nodo<T>* node);
/* Aggiunge un elemento in testa dato il suo valore.
* item = valore del nodo da aggiungere alla testa della lista
*/
void push_front(const T& item);
/* Aggiunge un elemento in coda dato il puntatore al nodo.
* node = nodo da aggiungere alla coda della lista.
*/
void push_back(Nodo<T>* node);
/* Aggiunge un elemento in coda dato il suo valore.
* item = valore da aggiungere in coda alla lista.
*/
void push_back(const T& item);
/* Rimuove un elemento all'inizio della lista.
* Se la lista è vuota viene lanciata un'out_of_range exception.
*/
void pop_front(void);
/* Rimuove un elemento alla fine della lista.
* Se la lista è vuota viene lanciata un'out_of_range exception.
*/
void pop_back(void);
// Restituisce il numero di elementi contenuti nella lista.
int size(void);
/* Restituisce il riferimento al nodo all'index-esima posizione.
* index = indice del nodo da prelevare.
* Se la lista non ha abbastanza elementi viene lanciata un'out_of_range exception.
*/
Nodo<T>& elementAt(const unsigned int index);
/* Restituisce il puntatore all'index-esimo nodo.
* index = indice del nodo da prelevare.
* Se la lista non ha abbastanza elementi viene lanciata un'out_of_range exception.
*/
Nodo<T>* pointerAt(const unsigned int index);
/* Rimuove l'elemento di index-esima posizione dalla lista.
* index = indice dell'elemento da rimuovere.
* Se la lista non ha abbastanza elementi viene lanciata un'out_of_range exception.
*/
void eraseAt(const unsigned int index);
/* Cancella la prima occorrenza del valore passato.
* item = valore dell'elemento da eliminare.
*/
bool eraseItem(const T& item);
// Rimuove tutti gli elementi dalla lista.
void clear(void);
/* Controlla se un dato valore è presente almeno una volta all'interno della lista.
* item = valore da cercare.
*/
bool searchIf(const T& item);
/* Restituisce la posizione della prima occorrenza di item all'interno della lista (-1 se item non è presente).
* item = valore da cercare.
*/
int searchFirstIndex(const T& item);
// Controlla se la lista è vuota.
bool isEmpty(void);
/* Effettua un inserimento ordinato all'interno della lista.
È necessario che T fornisca l'overloading degli operatori > e <.
*/
void sortedInsert(const T& item);
/* Applica l'algoritmo naive sort agli elementi della lista.
È necessario che T fornisca l'overloading degli operatori > e <.
*/
void naiveSort(void);
// Restituisce una lista che ha gli elementi invertiti rispetto all'istanza chiamante.
List<T> reverse(void);
/* Stampa il contenuto della lista.
È necessario che T fornisca l'overloading dell'operator<<.
*/
friend std::ostream& operator<< (std::ostream& output, const List<T>& lista);
};
list.cpp:
#include "list.h"
#include "nodo.cpp"
#include <string>
template <typename T>
Nodo<T>* List<T>::operator[](const unsigned int index)
{
if (index >= size())
throw out_of_range(string("operator[]: La lista non ha abbastnaza elementi. \n") + string("Dimensione della lista: " + to_string(size() + '\n')) + string("index = " + to_string(index) + '\n'));
else // scorre fino ad arrivare all'elemento specificato
{
Nodo<T>* aux = head;
for (int i = 0; i < index; i++)
aux = aux->getNext();
return aux;
}
}
template <typename T>
void swap(Nodo<T>* a, Nodo<T>* b)
{
T temp = a->getValue();
a->setValue(b->getValue());
b->setValue(temp);
}
template <typename T>
List<T>::List()
{
head = NULL;
}
template <typename T>
List<T>::List(const List<T>& copy)
{
head = NULL;
Nodo<T>* aux = copy.head;
while (aux != NULL)
{
this->push_back(aux->getValue());
aux = aux->getNext();
}
}
template <typename T>
void List<T>::push_front(Nodo<T>* node)
{
node->setNext(head); // il nodo aggiunto diventa la testa della lista (utile per concatenare due liste)
head = node;
}
template <typename T>
void List<T>::push_front(const T& item)
{
push_front(new Nodo<T>(item));
}
template <typename T>
void List<T>::push_back(Nodo<T>* node)
{
if (isEmpty())
push_front(node);
else
{
Nodo<T>* aux = head;
while (aux->getNext() != NULL)
aux = aux->getNext();
aux->setNext(node);
}
}
template <typename T>
void List<T>::push_back(const T& item)
{
push_back(new Nodo<T>(item));
}
template <typename T>
void List<T>::pop_front(void)
{
if (isEmpty())
throw std::out_of_range("pop_front(): La lista e' vuota.");
else
{
Nodo<T>* second = head->getNext();
delete head;
head = second;
}
}
template <typename T>
void List<T>::pop_back(void)
{
if (isEmpty())
throw out_of_range("pop_back(): La lista e' vuota.");
else
{
Nodo<T> *aux = head;
while (aux->getNext() != NULL)
aux = aux->getNext();
delete aux->getNext();
aux->setNext(NULL);
}
}
template <typename T>
int List<T>::size(void)
{
int count = 0;
Nodo<T>* aux = head;
while (aux != NULL)
{
++count;
aux = aux->getNext();
}
return count;
}
template <typename T>
Nodo<T>* List<T>::pointerAt(const unsigned int index)
{
return (*this)[index];
}
template <typename T>
Nodo<T>& List<T>::elementAt(const unsigned int index)
{
return *(this->pointerAt(index));
}
template <typename T>
void List<T>::eraseAt(const unsigned int index)
{
if (index >= size() || size() == 0)
throw out_of_range(string("eraseAt(): La lista non contiene abbastanza elementi. \n") + string("size = " + to_string(size()) + '\n') + string("index = ") + to_string(index) + '\n');
else
{
if (index == 0)
pop_front();
else // non è necessario effettuare il controllo index == size() - 1 poiché verrebbe comunque effettuato un ciclo while
{
Nodo<T>* prec = head; // puntatore all'elemento precedente rispetto a quello da eliminare
Nodo<T>* toDelete = head->getNext(); // puntatore all'elemento da eliminare
for (int i = 0; i < index; i++)
{
prec = prec->getNext();
toDelete = toDelete->getNext();
}
prec->setNext(toDelete->getNext());
delete toDelete;
toDelete = NULL;
}
}
}
template <typename T>
bool List<T>::eraseItem(const T& item)
{
bool flag = false; // valore restituito dal metodo
int index = searchFirstIndex(item);
if (index != -1)
{
flag = true;
eraseAt(index);
}
return flag;
}
template <typename T>
void List<T>::clear(void)
{
while (head != NULL)
pop_front();
}
template <typename T>
bool List<T>::searchIf(const T& item)
{
bool flag = false;
if (!isEmpty())
{
Nodo<T>* aux = head;
while (aux != NULL && aux->getValue() != item)
aux = aux->getNext();
if (aux != NULL)
flag = true;
}
return flag;
}
template <typename T>
int List<T>::searchFirstIndex(const T& item)
{
int index = -1;
if (!isEmpty() && searchIf(item))
{
int count = 0;
Nodo<T>* aux = head;
while (aux->getValue() != item)
{
aux = aux->getNext();
++count;
}
index = count;
}
return index;
}
template <typename T>
bool List<T>::isEmpty(void)
{
return (head == NULL);
}
template <typename T>
void List<T>::sortedInsert(const T& item)
{
if (isEmpty())
push_front(item);
else
{
if (head->getValue() > item)
push_front(item);
else
{
Nodo<T>* prec = head;
Nodo<T>* succ = head->getNext();
while (succ != NULL && succ->getValue() < item)
{
prec = prec->getNext();
succ = succ->getNext();
}
Nodo<T>* add = new Nodo<T>(item);
add->setNext(succ);
prec->setNext(add);
}
}
}
template <typename T>
void List<T>::naiveSort(void)
{
if (head != NULL && head->getNext() != NULL) // effettua l'ordinamento solo se ci sono almeno 2 elementi nella lista
{
Nodo<T>* a = head; // nodo che viene confrontato mano a mano con tutti gli altri
while (a->getNext() != NULL) // il nodo si ferma alla coda
{
Nodo<T>* b = a->getNext(); // nodo che verrà confrontato a ogni iterazione con il nodo a
while (b != NULL) // b deve scorrere tutta la lista
{
if (b->getValue() < a->getValue())
swap(a, b);
b = b->getNext();
}
a = a->getNext();
}
}
}
template <typename T>
List<T> List<T>::reverse(void)
{
List<T> result;
Nodo<T>* aux = head;
while (aux != NULL)
{
result.push_front(aux->getValue());
aux = aux->getNext();
}
return result;
}
template <typename T>
ostream& operator<< (ostream& output, const List<T>& lista)
{
Nodo<T>* aux = lista.head;
if (aux == NULL)
output << "Lista vuota.";
else
{
while (aux != NULL)
{
output << aux->getValue() << " ";
aux = aux->getNext();
}
}
return output;
}
nodo.h:
#pragma once
#include <iostream>
template <typename T>
class Nodo
{
private:
T value; // valore contenuto all'interno del nodo
Nodo<T>* next; // puntatore all'elemento successivo
public:
/* Costruttore di default. */
Nodo();
/* Costruttore con parametro l'informazione del nodo. */
Nodo(const T& info);
/* Costruttore con parametri. */
Nodo(const T& info, Nodo<T>* next);
/* Costruttore copia. */
Nodo(const Nodo<T>& copy);
/* Costruttore copia. */
Nodo(Nodo<T>* copy);
// Imposta il valore di T.
void setValue(const T& value);
// Imposta il puntatore al nodo successivo.
void setNext(Nodo<T>* next);
// Ottiene il valore del nodo.
T getValue(void);
// Ottiene il puntatore al nodo successivo.
Nodo<T>* getNext(void);
Nodo<T>& operator=(const Nodo<T>*);
};
nodo.cpp:
#include "nodo.h"
using namespace std;
template <typename T>
Nodo<T>::Nodo() : value()
{
next = NULL;
}
template <typename T>
Nodo<T>::Nodo(const T& info)
{
value = info;
next = NULL;
}
template <typename T>
Nodo<T>::Nodo(const T& info, Nodo<T>* next)
{
value = info;
this->next = next;
}
template <typename T>
Nodo<T>::Nodo(const Nodo<T>& copy)
{
value = copy.value;
next = copy.next;
}
template <typename T>
Nodo<T>::Nodo(Nodo<T>* copy)
{
value = copy->getValue();
next = copy->getNext();
}
template <typename T>
void Nodo<T>::setValue(const T& value)
{
this->value = value;
}
template <typename T>
void Nodo<T>::setNext(Nodo<T>* next)
{
this->next = next;
}
template <typename T>
T Nodo<T>::getValue(void)
{
return value;
}
template <typename T>
Nodo<T>* Nodo<T>::getNext(void)
{
return next;
}
template <typename T>
Nodo<T>& Nodo<T>::operator=(const Nodo<T>* node)
{
value = node->value;
next = node->next;
}
main.cpp:
#include "list.cpp"
#include <iostream>
using namespace std;
int menu(void);
int main (void)
{
List<int> list;
int scelta;
int pari = 0;
int dispari = 0;
int info;
do
{
scelta = menu();
switch (scelta)
{
/*case 1:
cout << "La somma della lista e': " << list.somma() << endl;
break;
case 2:
cout << "La media degli elementi della lista e': " << list.media() << endl;
break;
case 3:
cout << "L'elemento massimo della lista e': " << list.max() << endl;
break;
case 4:
list.sommaPariDispari(&pari, &dispari);
cout << "La somma dei pari e': " << pari << ", dei dispari: " << dispari << endl;
break;
case 5:
if (list.eraseIfEven() == 0)
cout << "Elemento non cancellato." << endl;
else
cout << "Elemento cancellato." << endl;
break;
case 6:
list.abs();
cout << "Lista positivizzata." << endl;
break;*/
case 7:
cout << "Inserisci l'elemento da aggiungere in coda: ";
cin >> info;
list.push_back(info);
break;
case 8:
cout << list << endl;
break;
case 9:
cout << "Lista prima dell'ordinamento: " << list << endl;
list.naiveSort();
cout << "Lista dopo l'ordinamento: " << list << endl;
break;
case 10:
cout << "Lista attuale (non verra' modificata): " << list << endl;
cout << "Lista invertita: " << list.reverse() << endl;
break;
}
} while (scelta != 20);
return 0;
}
int menu(void)
{
int scelta;
cout << "1. Somma gli elementi della lista." << endl;
cout << "2. Calcola la media della lista." << endl;
cout << "3. Massimo elemento della lista." << endl;
cout << "4. Somma elementi pari e dispari." << endl;
cout << "5. Cancella il primo elemento se e' pari." << endl;
cout << "6. Positivizza tutta la lista." << endl;
cout << "7. Aggiungi un elemento in coda." << endl;
cout << "8. Stampa il contenuto della lista." << endl;
cout << "9. Riordina la lista." << endl;
cout << "10. Riordina la lista (con i for)." << endl;
cout << "11. Inverti gli elementi della lista." << endl;
cin >> scelta;
return scelta;
}