Domanda

Sto provando a creare una funzione generica che rimuove i duplicati da uno std :: vector. Dal momento che non voglio creare una funzione per ogni tipo di vettore, voglio renderlo una funzione modello che può accettare vettori di qualsiasi tipo. Ecco quello che ho:

//foo.h

Class Foo {

template<typename T>
static void RemoveVectorDuplicates(std::vector<T>& vectorToUpdate);

};

//foo.cpp

template<typename T>
void Foo::RemoveVectorDuplicates(std::vector<T>& vectorToUpdate) {
for(typename T::iterator sourceIter = vectorToUpdate.begin(); (sourceIter != vectorToUpdate.end() - 1); sourceIter++) {
        for(typename T::iterator compareIter = (vectorToUpdate.begin() + 1); compareIter != vectorToUpdate.end(); compareIter++) {
            if(sourceIter == compareIter) {
                vectorToUpdate.erase(compareIter);
            }
        }
    }
}

//SomeOtherClass.cpp

#include "foo.h"

...

void SomeOtherClass::SomeFunction(void) {
    std::vector<int> myVector;

    //fill vector with values

    Foo::RemoveVectorDuplicates(myVector);
}

Continuo a ricevere un errore del linker, ma si compila bene. Qualche idea su cosa sto facendo di sbagliato?

AGGIORNAMENTO: in base alla risposta data da Iraimbilanja, sono andato a riscrivere il codice. Tuttavia, nel caso in cui qualcuno volesse che il codice di lavoro esegua la funzione RemoveDuplicates, eccolo qui:

//foo.h

Class Foo {

    template<typename T>
    static void RemoveVectorDuplicates(T& vectorToUpdate){
        for(typename T::iterator sourceIter = vectorToUpdate.begin(); sourceIter != vectorToUpdate.end(); sourceIter++) {
            for(typename T::iterator compareIter = (sourceIter + 1); compareIter != vectorToUpdate.end(); compareIter++) {
            if(*sourceIter == *compareIter) {
                compareIter = vectorToUpdate.erase(compareIter);
            }
        }
    }
};

Si scopre che se nella firma si specifica std :: vector, gli iteratori non funzionano correttamente. Quindi ho dovuto seguire un approccio più generico. Inoltre, quando si cancella compareIter, la successiva iterazione del ciclo produce un'eccezione puntatore. Il post decremento di compareIter su una cancellazione si occupa di quel problema. Ho anche corretto i bug nel confronto iteratore e nell'inizializzazione di compareIter nel 2 ° ciclo.

AGGIORNAMENTO 2:

Ho visto che questa domanda ha ottenuto un altro voto, quindi ho pensato di aggiornarla con un algoritmo migliore che utilizza un po 'di bontà C ++ 14. Il mio precedente funzionava solo se il tipo archiviato nel vettore implementava l'operatore == e richiedeva un sacco di copie e confronti inutili. E, con il senno di poi, non è necessario renderlo membro di una classe. Questo nuovo algoritmo consente un predicato di confronto personalizzato, riduce lo spazio di confronto man mano che vengono trovati duplicati e crea un numero significativamente inferiore di copie. Il nome è stato modificato in erase_duplicates per meglio conformarsi alle convenzioni di denominazione dell'algoritmo STL.

template<typename T>
static void erase_duplicates(T& containerToUpdate) 
{
    erase_duplicates(containerToUpdate, nullptr);
}

template<typename T>
static void erase_duplicates(T& containerToUpdate, 
  std::function<bool (typename T::value_type const&, typename T::value_type const&)> pred) 
{
    auto lastNonDuplicateIter = begin(containerToUpdate);
    auto firstDuplicateIter = end(containerToUpdate);
    while (lastNonDuplicateIter != firstDuplicateIter) {
        firstDuplicateIter = std::remove_if(lastNonDuplicateIter + 1, firstDuplicateIter, 
            [&lastNonDuplicateIter, &pred](auto const& compareItem){
            if (pred != nullptr) {
                return pred(*lastNonDuplicateIter, compareItem);
            }
            else {
                return *lastNonDuplicateIter == compareItem;
            }
        });
        ++lastNonDuplicateIter;
    }
    containerToUpdate.erase(firstDuplicateIter, end(containerToUpdate));
}
È stato utile?

Soluzione

Risposta breve

Definisce la funzione nell'intestazione, preferibilmente all'interno della definizione della classe.

Risposta lunga

La definizione della funzione template all'interno di .cpp significa che non inserirà #include d in nessuna unità di traduzione: sarà disponibile solo per l'unità di traduzione in cui è definita.

Quindi RemoveVectorDuplicates deve essere definito nell'intestazione, poiché questo è l'unico modo in cui il compilatore può sostituire testualmente gli argomenti del template, quindi istanziare il template, producendo una classe utilizzabile.

Esistono due soluzioni alternative per questo inconveniente

Prima , puoi rimuovere #include "foo.h" da .cpp e aggiungerne un altro, alla fine dell'intestazione :

#include "foo.cpp"

Ciò consente di organizzare i file in modo coerente, ma non offre i soliti vantaggi di una compilazione separata (dipendenze più piccole, compilazioni più veloci e più rare).

Secondo , puoi semplicemente definire la funzione del modello in .cpp e creare un'istanza esplicita per tutti i tipi con cui verrà mai usata.

Ad esempio, questo può andare alla fine di .cpp per rendere utilizzabile la funzione con int s:

template void Foo::RemoveVectorDuplicates(std::vector<int>*);

Tuttavia, questo presuppone che tu usi solo modelli per salvare un po 'di battitura, piuttosto che per fornire una vera genericità.

Altri suggerimenti

Un'alternativa che hai è prima di tutto std::sort() il vettore, quindi utilizzare la funzione std::unique() preesistente per rimuovere i duplicati. L'ordinamento richiede O (nlog n) tempo e la rimozione dei duplicati richiede solo O (n) tempo poiché tutti i duplicati vengono visualizzati in un singolo blocco. Il tuo attuale & Quot; all-vs-all & Quot; l'algoritmo di confronto richiede tempo O (n ^ 2).

Non è possibile implementare una funzione modello in un file .cpp. L'implementazione completa deve essere visibile ovunque sia istanziata.

Basta definire la funzione all'interno della definizione della classe nell'intestazione. Questo è il solito modo per implementare le funzioni del modello.

Suggerirò di usare un più " generico " approccio, invece di passare un contenitore basta ricevere due iteratori.

Qualcosa del genere remove_duplicates (prima per tutto, per ultimo) e restituirà un iteratore, in modo da poter chiamare come remove: v.erase(remove_duplicates(v.begin(), v.end()), v.end()).

template <typename It>
It remove_duplicate(It first, It last)
{
  It current = first;
  while(current != last) {
    // Remove *current from [current+1,last)
    It next = current;
    ++next;
    last = std::remove(next, last, *current);
    current = next;
  }
  return last;
}

Non correlato al tuo problema (che è già stato spiegato), perché questa è una funzione statica piuttosto che risiedere globalmente in uno spazio dei nomi? Questo sarebbe in qualche modo C ++ - ier.

Non credo che il codice venga compilato ....

vectorToUpdate.erase dove std :: vector * vectorToUpdate .... chiunque altro nota che esiste un * dove dovrebbe esserci un & amp ;? quel codice non viene sicuramente compilato. se hai intenzione di usare un puntatore per il vettore devi usare '- >' invece di '.' So che in realtà è un po 'schizzinoso ma sottolinea che il compilatore non si preoccupa nemmeno del tuo codice ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top