Неразрешенный внешний класс с использованием класса шаблона с копированием и обменом

Я получаю сообщение об ошибке компоновщика при использовании класса шаблона, в котором я пытался реализовать идиому копирования и замены, как это предлагается здесь:

Что такое идиома копирования и подкачки?

Класс шаблона, назовем его "TemplateClass", частично определен следующим образом:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    friend void swap( TemplateClass< T >& first, TemplateClass< T >& second );
    // ...
};

Я поместил реализации в отдельный TemplateClass.cpp, который включен в файл .h. (Редактировать: у меня такая же проблема, если все находится в файле .h)

Оператор присваивания определяется как:

template< class T >
TemplateClass< T >& TemplateClass< T >::operator= ( TemplateClass< T > other )
{
    // copy-and-swap idiom
    swap( *this, other );
    return *this;
}

и метод подкачки определяется как:

template< class T >
void swap( TemplateClass< T >& first, TemplateClass< T >& second )
{
    using namespace std;
    swap( first.member1, second.member1 );
    swap( first.member2, second.member2 );
    // ...
}

(Не волнуйтесь, на самом деле я не называю своих участников «member1» и т. д.)

У меня есть аналогичный класс, который определяется таким же образом, но не является классом-шаблоном. Там все работает нормально. Однако, если у меня есть класс TestClass, в котором есть член TemplateClass< HandledClass > member, и я делаю вызов в одном из его методов, например

void TestClass::setMember( TemplateClass< HandledClass > newObject )
{
    member = newObject;
}

Я получаю неразрешенную внешнюю ошибку:

LNK2019: Неразрешенный внешний символ "void __cdecl swap( class TemplateClass &, class TemplateClass &)" (...) в функции "public: class TemplateClass X & __thiscall TemplateClass X::operator=(class TemplateClass)" (...) в TestClass.obj

Или, другими словами: что-то в TestClass вызывает TemplateClass<HandledClass>::operator=, но не находит void swap( TemplateClass<HandledClass>, TemplateClass<HandledClass> ).

Итак, мой вопрос: почему оператор не находит метод подкачки?

Похоже, он не был скомпилирован для аргумента шаблона. Возможно ли, чтобы компилятор также компилировал дружественные пустоты?

Вероятно, я мог бы отказаться от подхода friend void и определить внутриклассовый метод подкачки плюс внеклассовый метод подкачки плюс один в пространстве имен std, но я не знаю, будет ли это работать таким образом, и я хотел бы избежать этого. если можно все таки.


Решение:

это сделало работу:

template< class t >
class TemplateClass
{
    friend void swap( TemplateClass& first, TemplateClass& second )
    {
        // ...
    }
};

Обратите внимание, как мне пришлось удалить вхождения ‹ T >.


person Tim Meyer    schedule 07.09.2011    source источник
comment
У меня есть реализации в другом файле, да, но они включены в заголовочный файл, так что в основном они находятся в заголовочном файле.   -  person Tim Meyer    schedule 07.09.2011
comment
Ну, это немного текстовый пост, но я постарался предоставить как можно больше информации ;-)   -  person Tim Meyer    schedule 07.09.2011


Ответы (2)


Это распространенная проблема при подружке функций, не являющихся членами, с шаблонами. Объявление friend внутри TemplateClass не дружит с вашим шаблоном swap, а скорее со свободной функцией swap без шаблона, которая принимает TemplateClass<T> для любого T экземпляра шаблона (т. е. специализация TemplateClass<int> будет дружить со свободной функцией void swap( TemplateClass<int>&,TemplateClass<int>& );, которая не является шаблоном).

Лучшее решение — предоставить определение swap, встроенное в определение шаблона класса, так как это заставит компилятор генерировать нешаблонную функцию swap для точного типа всякий раз, когда это необходимо. В качестве еще одного положительного побочного эффекта эта функция swap будет найдена только во время поиска, зависящего от аргумента, поэтому она не будет участвовать в разрешении перегрузки для всего, что не связано с вашим шаблоном.

Другими альтернативами являются дружба со всей функцией шаблона swap или дружба с конкретной специализацией функции swap при применении к тому же T, с которым был создан экземпляр шаблона. Код первого варианта прост, но он предоставляет доступ ко всем специализациям шаблона swap, что может иметь нежелательные побочные эффекты. Подружка с конкретной специализацией swap решает эту проблему, но ее немного сложнее реализовать (вам нужно предварительно объявить шаблон класса, затем шаблон swap, затем определить шаблон класса и, наконец, определить шаблон swap).

Подробнее об этом в этом другом ответе, где различные варианты и синтаксис объясняются более подробно.

Что касается конкретного сообщения об ошибке unresolved external, это связано с тем, как работает поиск идентификатора. Когда вы использовали swap(*this,other); внутри функции-члена, поиск начинается внутри класса и пытается найти соответствующий swap. Сначала он просматривает контекст класса и находит объявление функции friend free, поэтому поиск не продолжается дальше и добавляет зависимость к этой конкретной функции free. Он добавляет зависимость и ожидает, пока компоновщик найдет соответствующий символ. Поскольку компилятор никогда не рассматривал шаблонный swap на уровне пространства имен, он фактически никогда не создавал его экземпляр, но даже если он создал экземпляр этого шаблона, зависимость внутри функции-члена operator= находится на свободной функции, а не на этой специализации.

person David Rodríguez - dribeas    schedule 07.09.2011
comment
Встраивание само по себе не помогло, но по ссылке, которую вы разместили, я понял, что у них нет материала ‹ T › в объявлении о недействительности друга. Проверьте редактирование в моем посте, чтобы увидеть код, который я выбрал, если вам интересно. - person Tim Meyer; 07.09.2011
comment
@Tim Meyer: Если вы ничего не ошиблись, простое встраивание определения функции в определение шаблона класса должно было работать с <T> или без него (если T является фактическим аргументом шаблона). Внутри области шаблона класса имя шаблона класса является кратким обозначением специализации создаваемого шаблона, что означает, что TemplateClass и TemplateClass<T> являются точными синонимами, если аргумент шаблона равен T. - person David Rodríguez - dribeas; 07.09.2011
comment
У меня активны предварительно скомпилированные заголовки (хотя файл заголовка исключает только библиотеки Windows/сторонних производителей), возможно, там была проблема. В какой-то момент я сделал полную перестройку после удаления ‹ T › - person Tim Meyer; 07.09.2011

Вы должны либо поместить объявление шаблона класса в заголовочный файл, либо, если вы заранее знаете все типы, с которыми будет создан экземпляр этого шаблона класса, предоставьте явное создание экземпляра в заголовочном файле:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    friend void swap( TemplateClass< T >& first, TemplateClass< T >& second );
    // ...
};

template class TemplateClass<FirstType>;
template class TemplateClass<SecondType>;
// ...

// and the same for swap function
template void swap<FirstType>( TemplateClass<FirstType>& first, TemplateClass<FirstType>& second );
template void swap<SecondType>( TemplateClass<SecondType>& first, TemplateClass<SecondType>& second );

Это утомительно, но иногда это лучший вариант.

Относительно того, почему ваш своп не связан: вы объявляете друзей с обменом функциями без шаблона, которого не существует. Попробуйте это:

template< class T >
class TemplateClass
{
    // ...
    TemplateClass< T >& operator= ( TemplateClass< T > other );
    template < class U > friend void swap( TemplateClass< U >& first, TemplateClass< U >& second );
    // ...
};

Дополнительные усилия потребуются, если вы хотите быть пуристом и дружить только со «своим» swap (swap с теми же параметрами шаблона).

person Alexander Poluektov    schedule 07.09.2011
comment
Я думал об этом, но я хочу использовать его как общий класс в разных проектах, поэтому, к сожалению, для меня это не вариант. Любая подсказка, почему компилятор не компилирует друга void, даже если объявление находится в заголовочном файле? - person Tim Meyer; 07.09.2011