¿Cómo se crea una función miembro de plantilla estática que realiza acciones en una clase de plantilla?

StackOverflow https://stackoverflow.com/questions/488959

Pregunta

Estoy tratando de crear una función genérica que elimine duplicados de un std :: vector. Como no quiero crear una función para cada tipo de vector, quiero hacer de esta una función de plantilla que pueda aceptar vectores de cualquier tipo. Esto es lo que tengo:

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

Sigo recibiendo un error de enlazador, pero se compila bien. ¿Alguna idea de lo que estoy haciendo mal?

ACTUALIZACIÓN: Basado en la respuesta dada por Iraimbilanja, fui y reescribí el código. Sin embargo, en caso de que alguien quisiera que el código de trabajo hiciera la función RemoveDuplicates, aquí está:

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

Resulta que si especifico std :: vector en la firma, los iteradores no funcionan correctamente. Así que tuve que ir con un enfoque más genérico. Además, al borrar compareIter, la siguiente iteración del bucle produce una excepción de puntero. El decremento posterior de compareIter en un borrado se ocupa de ese problema. También arreglé los errores en la comparación del iterador y en la inicialización de compareIter en el segundo bucle.

ACTUALIZACIÓN 2:

Vi que esta pregunta obtuvo otro voto positivo, así que pensé en actualizarla con un algoritmo mejor que usa algo de C ++ 14. El anterior solo funcionaba si el tipo almacenado en el vector implementaba el operador == y requería un montón de copias y comparaciones innecesarias. Y, en retrospectiva, no hay necesidad de convertirlo en miembro de una clase. Este nuevo algoritmo permite un predicado de comparación personalizado, reduce el espacio de comparación a medida que se encuentran duplicados y hace un número significativamente menor de copias. El nombre se ha cambiado a erase_duplicates para ajustarse mejor a las convenciones de nomenclatura del 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));
}
¿Fue útil?

Solución

Respuesta corta

Defina la función en el encabezado, preferiblemente dentro de la definición de clase.

Respuesta larga

Definir la función de plantilla dentro del .cpp significa que no obtendrá #include d en ninguna unidad de traducción: solo estará disponible para la unidad de traducción en la que se define.

Por lo tanto, RemoveVectorDuplicates debe definirse en el encabezado, ya que esta es la única forma en que el compilador puede sustituir por texto los argumentos de la plantilla, por lo tanto, instanciar la plantilla, produciendo una clase utilizable.

Hay dos soluciones para este inconveniente

Primero , puede eliminar el #include "foo.h" del .cpp y agregar otro, en el final del encabezado :

#include "foo.cpp"

Esto le permite organizar sus archivos de manera consistente, pero no proporciona las ventajas habituales de una compilación separada (dependencias más pequeñas, compilaciones más rápidas y raras).

Segundo , puede definir la función de la plantilla en el archivo .cpp y crear una instancia explícita para todos los tipos con los que se utilizará.

Por ejemplo, esto puede ir al final de .cpp para que la función se pueda usar con int s:

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

Sin embargo, esto supone que solo usa plantillas para guardar algunos tipos de escritura, en lugar de proporcionar un verdadero carácter genérico.

Otros consejos

Una alternativa que tiene es primero std::sort() el vector y luego usar la función std::unique() preexistente para eliminar duplicados. La ordenación toma tiempo O (nlog n), y la eliminación de duplicados después de eso lleva solo tiempo O (n) ya que todos los duplicados aparecen en un solo bloque. Su actual & Quot; all-vs-all & Quot; El algoritmo de comparación lleva tiempo O (n ^ 2).

No puede implementar una función de plantilla en un archivo .cpp. La implementación completa debe ser visible en cualquier lugar donde se instancia.

Simplemente defina la función dentro de la definición de clase en el encabezado. Esa es la forma habitual de implementar funciones de plantilla.

Sugeriré usar un más " genérico " enfoque, en lugar de pasar un contenedor solo recibe dos iteradores.

Algo así elimina remove_duplicates (primero, dura), y devolverá un iterador, por lo que puede llamar como 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;
}

Sin relación con su problema (que ya se explicó), ¿por qué es una función estática en lugar de residir globalmente en un espacio de nombres? Esto sería algo C ++ - ier.

No creo que el código se compile ...

vectorToUpdate.erase donde std :: vector * vectorToUpdate .... ¿Alguien más nota que hay un * donde debería haber un amplificador & ;? ese código definitivamente no se está compilando. si va a usar un puntero para vector, debe usar '- >' en lugar de '.' Sé que esto es realmente un poco quisquilloso, pero señala que al compilador ni siquiera le importa su código ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top