c++ - template - ¿Cómo se crea una función de miembro de plantilla estática que realiza acciones en una clase de plantilla?
template typename t c++ (6)
Estoy tratando de crear una función genérica que elimine los duplicados de un std :: vector. Como no quiero crear una función para cada tipo de vector, quiero hacer de esto 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 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, solo en caso de que alguien quiera código de trabajo para hacer 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. La disminución posterior de la comparación en un borrado soluciona ese problema. También corrigí los errores en la comparación del iterador y en la inicialización de compareIter en el segundo ciclo.
ACTUALIZACIÓN 2:
Vi que esta pregunta tenía otra votación, así que pensé que la actualizaría con un algoritmo mejor que utilizara algo de C ++ 14 bondad. El anterior solo funcionaba si el tipo almacenado en el vector implementaba operator == y requería un montón de copias y comparaciones innecesarias. Y, en retrospectiva, no hay necesidad de hacerlo 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 los 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));
}
Respuesta corta
Defina la función en el encabezado, preferiblemente dentro de la definición de la clase.
Respuesta larga
Definir la función de plantilla dentro de .cpp significa que no incluirá #include
d en ninguna unidad de traducción: solo estará disponible para la unidad de traducción en la que esté definida.
Por RemoveVectorDuplicates
tanto, RemoveVectorDuplicates
debe definirse en el encabezado, ya que esta es la única forma en que el compilador puede sustituir el texto de los argumentos de la plantilla, lo que genera una instancia de la plantilla y genera una clase utilizable.
Hay dos soluciones para este inconveniente
Primero , puede eliminar el #include "foo.h"
de .cpp y agregar otro, al final del encabezado :
#include "foo.cpp"
Esto le permite organizar sus archivos de forma coherente, pero no proporciona las ventajas habituales de la compilación por separado (dependencias más pequeñas, compilaciones más rápidas y más raras).
En segundo lugar , puede definir la función de plantilla en .cpp e instanciarla explícitamente para todos los tipos con los que se usará.
Por ejemplo, esto puede ir al final de .cpp para hacer que la función se pueda usar con int
s:
template void Foo::RemoveVectorDuplicates(std::vector<int>*);
Sin embargo, esto supone que solo utiliza plantillas para guardar algo de tipeo, en lugar de proporcionar verdadera genérica.
No creo que el código compile ...
vectorToUpdate.erase donde std :: vector * vectorToUpdate .... ¿Alguien más nota que hay un * donde debería haber un &? ese código definitivamente no se está compilando. si va a usar un puntero al vector, debe usar ''->'' en lugar de ''.'' Sé que esto es un poco quisquilloso, pero señala que al compilador ni siquiera le importa tu código ...
No puede implementar una función de plantilla en un archivo .cpp. La implementación completa debe estar visible en cualquier lugar donde se cree una instancia.
Simplemente defina la función dentro de la definición de la clase en el encabezado. Esa es la forma habitual de implementar funciones de plantilla.
No relacionado con su problema (que ya se ha explicado), ¿por qué es esto una función estática en lugar de residir globalmente en un espacio de nombres? Esto sería algo C ++ - ier.
Sugeriré usar un enfoque más "genérico", en lugar de pasar un contenedor solo recibiré dos iteradores.
Algo así como Remove_duplicates (Primero, It last), 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;
}
Una alternativa que tiene es primero std::sort()
el vector, y luego usar la función std::unique()
preexistente para eliminar duplicados. El ordenamiento toma el tiempo O (nlog n), y la eliminación de duplicados después de eso toma solo O (n) tiempo, ya que todos los duplicados aparecen en un solo bloque. Su algoritmo de comparación actual de "todos contra todos" toma O (n ^ 2) tiempo.