Abh*_*yan 2 c++ templates class friend-function
我已经在Turbo-C中编译了第一个版本的代码,它编译时没有任何错误.但是当我在Visual Studio中编译它或从CommandLine编译普通g ++时,我会在帖子中看到错误.
我通过互联网搜索并阅读了一些StackOverflow问题和MSDN LNK1120问题文档.我找到了修复下面代码的方法.但是,没有明确的原因.如何定义摆脱该错误.从语法上讲,容易出错的代码看起来也很好.
1) Error 2 error LNK1120: 1 unresolved externals
2) Error 1 error LNK2019: unresolved external symbol "void __cdecl
totalIncome(class Husband<int> &,class Wife<int> &)" (?totalIncome@@YAXAAV?
$Husband@H@@AAV?$Wife@H@@@Z) referenced in function _main
Run Code Online (Sandbox Code Playgroud)
注意:请注意程序中的箭头.我明确地说过了.所以,可能不会通过完整的程序.
错误易错代码:
#include <iostream>
using namespace std;
template<typename T> class Wife; // Forward declaration of template
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);
public:
Husband() = default;
Husband(T new_salary): salary{new_salary} {}
private:
T salary;
};
template<typename T>
class Wife{
friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);
public:
Wife() = default;
Wife(T new_salary): salary{new_salary} {}
private:
T salary;
};
template<typename T> void totalIncome(Husband<T> &hobj, Wife<T> &wobj) __
{ |
cout << "Total Income of Husband & Wife: "; |
cout << hobj.salary + wobj.salary; |
} |
---
int main()
{
Husband<int> h(40000);
Wife<int> w(90000);
totalIncome(h, w);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但是在下面的例子中,通过在类中移动定义,它运行得非常好.为什么会这样?
固定代码:
#include <iostream>
using namespace std;
template<typename T> class Wife; // Forward declaration of template
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj);
public:
Husband() = default;
Husband(T new_salary): salary{new_salary} {}
private:
T salary;
};
template<typename T>
class Wife{
friend void totalIncome(Husband<T> &hobj, Wife<T> &wobj) __
{ |
cout << "Total Income of Husband & Wife: "; | -- definition moved up here
cout << hobj.salary + wobj.salary; |
} __|
public:
Wife() = default;
Wife(T new_salary): salary{new_salary} {}
private:
T salary;
};
/*
template<typename T> void totalIncome(Husband<T> &hobj, Wife<T> &wobj) __
{ |
cout << "Total Income of Husband & Wife: "; |
cout << hobj.salary + wobj.salary; |
} |
---
*/
int main()
{
Husband<int> h(40000);
Wife<int> w(90000);
totalIncome(h, w);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
首先,让我们将其简化为一个重现问题的最小例子:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &);
};
template<typename T> void totalIncome(Husband<T> &hobj)
{}
int main()
{
Husband<int> h;
totalIncome(h);
}
Run Code Online (Sandbox Code Playgroud)
这导致类似的链接器错误.例如,使用clang ++:
/tmp/main-fb41c4.o: In function `main': main.cpp:(.text+0xd): undefined reference to `totalIncome(Husband&)'
底层问题与模板上的重载算术运算符中的问题相同,导致未解决的外部错误和模板化的奇怪行为operator<<.因此,我的答案将基于第二个问题的回答.
在类模板中Husband,有一个函数的friend-declaration totalIncome:
friend void totalIncome(Husband<T> &);
Run Code Online (Sandbox Code Playgroud)
友元函数声明totalIncome在周围的作用域中查找声明的函数的名称(此处为:),直到并包括最内部的封闭名称空间.如果找到该名称的声明,则声明的实体是友好的:
class Husband;
void totalIncome(Husband&); // (A)
class Husband{
friend void totalIncome(Husband&); // befriends (A)
};
Run Code Online (Sandbox Code Playgroud)
如果未找到名称声明,则在最内层的封闭命名空间中声明一个新函数:
class Husband{
friend void totalIncome(Husband&); // declares and befriends ::totalIncome
};
void totalIncome(Husband&); // friend of class Husband
Run Code Online (Sandbox Code Playgroud)
如果函数仅通过friend函数声明声明(并且封闭命名空间中没有后续声明),则只能通过Argument-Dependent Lookup找到该函数.
OP代码中的问题是涉及到一个类模板:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &);
};
Run Code Online (Sandbox Code Playgroud)
规则保持不变:totalIncome未找到声明.由于friend声明的签名取决于类模板的模板参数,因此每个实例Husband都会在封闭的命名空间中引入一个新函数.(如果friend声明不依赖于模板参数,你会得到一个重新声明与每一个实例Husband.)例如,如果你实例化Husband<int>和Husband<double>,你会得到在全局命名空间两个功能:
void totalIncome(Husband<int> &);
void totalIncome(Husband<double> &);
Run Code Online (Sandbox Code Playgroud)
请注意,这些是两个不相关的函数,非常类似:
void totalIncome(int &);
void totalIncome(double &);
Run Code Online (Sandbox Code Playgroud)
您可以使用函数模板重载"普通"函数:
void totalIncome(Husband<int> &); // (A)
template<typename T>
void totalIncome(Husband<T> &); // (B)
Run Code Online (Sandbox Code Playgroud)
通过Husband<int> x; totalIncome(x);函数调用函数时,函数模板将生成一个实例:
void totalIncome<int>(Husband<int> &); // (C), instantiated from (B)
Run Code Online (Sandbox Code Playgroud)
您的重载集包含两个函数:
void totalIncome(Husband<int> &); // (A)
void totalIncome<int>(Husband<int> &); // (C)
Run Code Online (Sandbox Code Playgroud)
在所有条件相同的情况下,重载决策将优先于非模板函数(A)而不是函数模板特化(C).
这也是OP代码中发生的事情:通过实例化类模板Husband和不相关的函数模板引入了非模板函数.重载决策选择非模板函数,链接器抱怨它没有定义.
最简单的解决方案是在类定义中定义友元函数:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &){
// code here
}
};
Run Code Online (Sandbox Code Playgroud)
使用前向声明的解决方案:
template<typename T>
class Husband;
template<typename T>
void totalIncome(Husband<T> &);
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &);
};
template<typename T> void totalIncome(Husband<T> &hobj)
{}
Run Code Online (Sandbox Code Playgroud)
在这里,编译器可以找到前向声明的函数模板并与其进行特殊化,而不是声明一个新函数.显式添加模板参数可能是值得的:
template<typename T>
class Husband{
friend void totalIncome<T>(Husband<T> &);
};
Run Code Online (Sandbox Code Playgroud)
因为这强制执行功能模板的事先声明totalIncome.
与整个功能模板交朋友的解决方案:
template<typename T>
class Husband{
template<typename U>
friend void totalIncome(Husband<U> &);
};
template<typename T> void totalIncome(Husband<T> &hobj)
{}
Run Code Online (Sandbox Code Playgroud)
在这个解决方案中,friend-declaration声明了一个函数模板,后面的声明在命名空间范围后面的类' definedeclares并定义了这个函数模板.函数模板的所有特化都将由所有实例化成为友好Husband.