Неразрешен външен използващ шаблонен клас с копиране и размяна

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

Какво представлява идиомът за копиране и размяна?

Класът на шаблона, нека го наречем "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, така че търсенето не продължава да излиза навън и добавя зависимост към тази конкретна безплатна функция. Той добавя зависимостта и изчаква линкерът да намери подходящия символ. Тъй като компилаторът никога не е разглеждал шаблонния 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/libs на трети страни), може би там е имало проблем. Направих пълно възстановяване в някакъв момент след премахването на ‹ 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
Мислил съм за това, но искам да го използвам като общ клас в различни проекти, така че това не е опция за мен за съжаление. Някаква представа защо компилаторът не компилира friend void, дори ако декларацията е в заглавния файл? - person Tim Meyer; 07.09.2011