未解决的外部使用带有复制和交换的模板类

时间:2011-09-07 08:47:43

标签: c++ templates unresolved-external copy-and-swap

我在使用模板类时遇到链接器错误,我尝试按照此处的建议实现复制和交换习惯用法:

What is the copy-and-swap idiom?

模板类,我们称之为“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:函数“public:class TemplateClass X&amp; __thiscall TemplateClass X :: operator =(class TemplateClass)”中未解析的外部符号“void __cdecl swap(类TemplateClass&amp;,类TemplateClass&amp;)”(...) “(...)在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 )
    {
        // ...
    }
};

请注意我必须删除&lt; T>事件也是如此。

2 个答案:

答案 0 :(得分:3)

使用模板与非成员函数建立联系时,这是一个常见问题。 friend内的TemplateClass声明与您的swap模板不相关,而是一个非模板化的免费函数swap,其TemplateClass<T>T TemplateClass<int> void swap( TemplateClass<int>&,TemplateClass<int>& ); 1}}模板被实例化(即,特化swap将与未模板化的自由函数swap成为朋友。

最佳解决方案是在类模板定义中提供内联的swap定义,因为这将使编译器在需要时为精确类型生成非模板化swap函数。另一个积极的副作用是swap函数只能在Argument Dependent Lookup中找到,因此对于任何不涉及模板的内容,它都不会参与重载解析。

其他替代方案是与整个T模板函数建立联系,或者在应用于已经实例化模板的同一swap时与swap函数的特定特化结合。第一个选项在代码中很简单,但它允许访问{em>所有的swap模板的特化,这可能会产生不良副作用。结合特定的swap专业化解决了这个问题,但实现起来有点复杂(你需要转发声明类模板,然后是unresolved external模板,然后定义类模板,最后定义swap(*this,other);模板)。

在其他answer中有更多内容,其中详细解释了不同的选项和语法。

关于swap的特定错误消息,这是由于标识符查找的工作原理。在成员函数中使用friend时,查找在类中开始,并尝试查找适当的swap。它首先在类上下文中查找并查找operator=自由函数的声明,因此查找不会继续向外并向该特定自由函数添加依赖项。它添加依赖关系并等待链接器找到适当的符号。因为编译器从未在命名空间级别考虑模板{{1}},所以它实际上从未实例化它,但即使它已实例化该模板,{{1}}成员函数内的依赖关系也是自由函数,而不是专业化。

答案 1 :(得分:0)

您应该将类​​模板的声明放入头文件中,或者如果您事先知道将要实例化此类模板的所有类型,请在头文件中提供显式实例化:

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 );

这很乏味但有时它是最好的选择。

关于为什么你的交换没有链接:你用非模板函数swap声明了友元,这不存在。试试这个:

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

如果您想成为一名纯粹主义者并且仅与“您的”swapswap具有相同模板参数的朋友)成为朋友,则需要额外的努力。