Как создать статическую функцию члена шаблона, которая выполняет действия в классе шаблона?
Я пытаюсь создать общую функцию, которая удаляет дубликаты из std::vector. Поскольку я не хочу создавать функцию для каждого типа вектора, я хочу сделать эту функцию шаблона, которая может принимать векторы любого типа. Вот что я имею:
//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);
}
Я продолжаю получать ошибку компоновщика, но он компилируется отлично. Любые идеи относительно того, что я делаю неправильно?
ОБНОВЛЕНИЕ. Основываясь на ответе, полученном Iraimbilanja, я пошел и переписал код. Однако на всякий случай кому-то нужен рабочий код для функции RemoveDuplicates, вот он:
//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);
}
}
}
};
Оказывается, что если я укажу std::vector в сигнатуре, итераторы работают неправильно. Поэтому мне пришлось пойти с более общим подходом. Кроме того, при стирании compareIter, следующая итерация цикла создает исключение указателя. Пост-декремент сравнения по стиранию заботится об этой проблеме. Я также исправил ошибки в сравнении итератора и инициализацию compareIter во втором цикле.
ОБНОВЛЕНИЕ 2:
Я видел, что этот вопрос получил еще один голос, поэтому я решил обновить его с помощью лучшего алгоритма, который использует некоторую доброту С++ 14. Мой предыдущий работал только в том случае, если тип, хранящийся в векторе, реализовал оператор ==, и потребовал кучу копий и ненужных сравнений. И, задним числом, нет необходимости делать его членом класса. Этот новый алгоритм позволяет использовать специальный предикат сравнения, сжимает пространство сравнения, поскольку дубликаты найдены и значительно уменьшают количество копий. Имя было изменено на erase_duplicates
, чтобы лучше соответствовать соглашениям об именах алгоритмов 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));
}
Ответы
Ответ 1
Краткий ответ
Определите функцию в заголовке, предпочтительно внутри определения класса.
Длинный ответ
Определение функции шаблона внутри .cpp означает, что он не получит #include
d в любые единицы перевода: он будет доступен только для единицы перевода, в которой он определен.
Следовательно, RemoveVectorDuplicates
должен быть определен в заголовке, так как это единственный способ, которым компилятор может текстовым образом заменить аргументы шаблона, а затем создать экземпляр шаблона, создав класс, который можно использовать.
Для этого неудобства существует два метода обхода
Сначала, вы можете удалить #include "foo.h"
из .cpp и добавить еще один в конце заголовка:
#include "foo.cpp"
Это позволяет вам упорядочивать ваши файлы последовательно, но не дает обычных преимуществ отдельной компиляции (меньшие зависимости, быстрее и реже компилируются).
Второй, вы можете просто определить функцию шаблона в .cpp и явно создать экземпляр для всех типов, с которыми он когда-либо будет использоваться.
Например, это может пойти в конце .cpp, чтобы сделать функцию полезной с помощью int
s:
template void Foo::RemoveVectorDuplicates(std::vector<int>*);
Однако это предполагает, что вы используете только шаблоны для сохранения некоторой типизации, а не для предоставления истинной общности.
Ответ 2
У вас есть первый вектор std::sort()
, а затем используйте ранее существовавшую функцию std::unique()
для удаления дубликатов. Сорт принимает время O (nlog n), а удаление дубликатов после этого занимает только время O (n), поскольку все дубликаты появляются в одном блоке. Ваш текущий алгоритм сравнения "все-все-все" принимает время O (n ^ 2).
Ответ 3
Вы не можете реализовать функцию шаблона в файле .cpp. Полная реализация должна быть видимой в любом месте, где она была создана.
Просто определите функцию внутри определения класса в заголовке.
Это обычный способ реализации функций шаблона.
Ответ 4
Я предлагаю использовать более "общий" подход, вместо того, чтобы передавать контейнер, просто получите два итератора.
Что-то вроде It remove_duplicates (Сначала, Последнее) и вернет итератор, поэтому вы можете вызвать как 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;
}
Ответ 5
Не связанный с вашей проблемой (что уже объяснено), почему это статическая функция, а не глобальное размещение в пространстве имен? Это будет несколько С++ - ier.
Ответ 6
Я не думаю, что код компилируется....
vectorToUpdate.erase, где std::vector * vectorToUpdate.... кто-нибудь еще замечает, что есть *, где должен быть &? этот код определенно не компилируется. если вы собираетесь использовать указатель на вектор, вы должны использовать '- > ' вместо '.' Я знаю, что это на самом деле немного nit picky, но он указывает, что компилятор даже не заботится о вашем коде...