web-dev-qa-db-fra.com

Comment créez-vous une fonction de membre de modèle statique qui effectue des actions sur une classe de modèle?

J'essaie de créer une fonction générique qui supprime les duplicats d'un std :: vecteur. Puisque je ne veux pas créer de fonction pour chaque type de vecteur, je souhaite faire une fonction de modèle pouvant accepter des vecteurs de n'importe quel type. Voici ce que j'ai:

//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);
}

Je continue à obtenir une erreur de liaison, mais cela compile bien. Des idées quant à ce que je fais mal?

MISE À JOUR: Sur la base de la réponse donnée par Iaimbilanja, je suis allé et réécrit le code. Cependant, juste au cas où quelqu'un souhaite que le code de travail effectue la fonction d'enlevée, c'est ici:

//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);
            }
        }
    }
};

Il s'avère que si je spécifie STD :: Vecteur de la signature, les itérateurs ne fonctionnent pas correctement. Je devais donc aller avec une approche plus générique. De plus, lors de l'effacement de la compréation, la prochaine itération de la boucle produit une exception de pointeur. Le post-décrément de comparaison sur une Erase prend soin de ce problème. J'ai également corrigé les bugs dans l'itérateur Comparer et dans l'initialisation de la compréation dans la 2e boucle.

Mise à jour 2:

J'ai vu que cette question a eu un autre vote à un autre, alors figuré que je le tiendrais à la mettre à jour avec un meilleur algorithme qui utilise une bonté C++ 14. Mon précédent a fonctionné uniquement si le type stocké dans le vecteur implémenté opérateur == et nécessite un groupe de copies et de comparaisons inutiles. Et, dans le recul, il n'est pas nécessaire de faire un membre d'une classe. Ce nouvel algorithme permet un prédicat de comparaison personnalisé, réduit l'espace de comparaison comme des doublons se trouvent et constitue un nombre considérable de copies. Le nom a été changé en erase_duplicates Pour mieux être conforme aux conventions de nommage d'algorithme 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));
}
25
bsruth

Réponse courte

Définissez la fonction dans l'en-tête, de préférence à l'intérieur de la définition de la classe.

Longue réponse

Définir la fonction de modèle à l'intérieur du fichier .cpp signifie qu'il n'aura pas #includeD dans toutes les unités de traduction: il ne sera disponible que pour l'unité de traduction qu'il est défini dans.

Par conséquent, RemoveVectorDuplicates doit être défini dans l'en-tête, car il s'agit de la seule façon de remplacer par SMS-substitut des arguments de modèle, donc Instancitation Le modèle, produisant une classe utilisable.

Il y a deux solutions de contournement pour cette inconvénient

premier, vous pouvez supprimer le #include "foo.h" À partir du fichier .cpp et ajoutez un autre, dans le fin du en-tête := :=:

#include "foo.cpp"

Cela vous permet d'organiser vos fichiers de manière cohérente, mais ne fournit pas les avantages habituels de la compilation séparée (plus petites dépendances, plus rapides et plus rares).

Deuxièmement, vous pouvez simplement définir la fonction de modèle dans le fichier .cpp et l'instanciez explicitement pour tous les types que ce sera jamais utilisé.

Par exemple, cela peut aller à la fin de la .CPP pour rendre la fonction utilisable avec ints:

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

Cependant, cela suppose que vous n'utilisez que des modèles pour sauver une frappe de frappe, plutôt que de fournir une véritable généricité.

31
Iraimbilanja

Une alternative que vous avez est à première std::sort() le vecteur, puis utilisez la fonction préexistante std::unique() _ fonction pour supprimer des doublons. Le genre prend le temps O (Nlog N) et éliminer les doublons après cela prend juste O(n) Durée car tous les doublons apparaissent dans un seul bloc. Votre comparaison actuelle "All-Tous" L'algorithme prend le temps O (n ^ 2).

5
j_random_hacker

Vous ne pouvez pas implémenter une fonction de modèle dans un fichier .cpp. La mise en œuvre complète doit être visible n'importe où qu'elle est instanciée.

Définissez simplement la fonction dans la définition de la classe dans l'en-tête. C'est la façon habituelle de mettre en œuvre des fonctions de modèle.

2
jalf

Je suggère d'utiliser une approche plus "générique", au lieu de passer un conteneur, recevez simplement deux itérateurs.

Quelque chose comme ça retirer_duplicates (c'est d'abord le dernier) et retournera un itérateur, vous pouvez donc appeler comme supprimer: 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;
}
1
Ismael

Non liée à votre problème (qui a déjà été expliqué), pourquoi est-ce une fonction statique plutôt que de résider globalement dans un espace de noms? Ce serait un peu C++ - IER.

0
Konrad Rudolph

Je ne pense pas que ce code compile ....

vectoroupdate.erase où std :: vecteur * vectorupdate ... Est-ce que quelqu'un d'autre remarque-t-il qu'il y a un * où il devrait y avoir un &? Ce code n'est certainement pas compilé. Si vous allez utiliser un pointeur pour vectoriel, vous devez utiliser '->' au lieu de "." Je sais que c'est en fait un peu malade, mais cela souligne que le compilateur ne se soucie même pas de votre code ...

0
Hippiehunter