Подтвердить что ты не робот

Как создать статическую функцию члена шаблона, которая выполняет действия в классе шаблона?

Я пытаюсь создать общую функцию, которая удаляет дубликаты из 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));
}
4b9b3361

Ответ 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, но он указывает, что компилятор даже не заботится о вашем коде...