为什么LNK1120 & LNK2019在模板和友元函数的情况下出现
Why LNK1120 & LNK2019 appears in case of template and friend function
我已经用 Turbo-C 编译了第一个版本的代码,并且编译没有任何错误。但是,当我在 Visual Studio 中或从 CommandLine 中使用纯 g++ 编译它时,我得到了 post 中提到的错误。
我确实在互联网上搜索并阅读了一些 Whosebug 问题和 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
注意:请注意程序中的箭头。我已经明确地把他们。所以,可能不会完成完整的程序。
容易出错的代码:
#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;
}
但在下面的例子中,只需将 class 中的定义上移,它就可以完美运行。为什么是什么原因?
固定代码:
#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;
}
首先,让我们将其缩减为重现该问题的最小示例:
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);
}
这会导致类似的链接器错误。例如,使用 clang++:
/tmp/main-fb41c4.o: In function `main':
main.cpp:(.text+0xd): undefined reference to `totalIncome(Husband&)'
根本问题与 Overloaded arithmetic operators on a template causing an unresolved external error and in Strange behavior of templated operator<<
中的问题相同。因此,我将根据我对第二个问题的回答来回答。
友元函数声明
在class模板Husband
中,有一个函数totalIncome
:
的友元声明
friend void totalIncome(Husband<T> &);
友元函数声明在周围范围内查找已声明函数的名称(此处:totalIncome
),直至并包括最内层的封闭命名空间。如果找到该名称的声明,则声明的实体成为朋友:
class Husband;
void totalIncome(Husband&); // (A)
class Husband{
friend void totalIncome(Husband&); // befriends (A)
};
如果没有找到名称的声明,在最里面的封闭命名空间中声明一个新函数:
class Husband{
friend void totalIncome(Husband&); // declares and befriends ::totalIncome
};
void totalIncome(Husband&); // friend of class Husband
如果函数仅通过友元函数声明声明(并且在封闭命名空间中没有后面的声明),则只能通过参数相关查找找到该函数。
在 class 模板中添加函数
OP 代码中的问题是涉及 class 模板:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &);
};
规则保持不变:未找到 totalIncome
的声明。由于友元声明的签名取决于 class 模板的模板参数, Husband
的每个实例化都会在封闭的命名空间 中引入一个新函数。 (如果 friend 声明不依赖于模板参数,您将在每个 Husband
的实例化中得到一个 重新声明 。)例如,如果您实例化 Husband<int>
和 Husband<double>
,您将在全局命名空间中获得两个函数:
void totalIncome(Husband<int> &);
void totalIncome(Husband<double> &);
请注意,这是两个不相关的函数,很像:
void totalIncome(int &);
void totalIncome(double &);
使用函数模板重载函数
您可以使用函数模板重载 "ordinary" 函数:
void totalIncome(Husband<int> &); // (A)
template<typename T>
void totalIncome(Husband<T> &); // (B)
当通过Husband<int> x; totalIncome(x);
调用函数时,函数模板会产生一个实例:
void totalIncome<int>(Husband<int> &); // (C), instantiated from (B)
并且您的重载集包含两个函数:
void totalIncome(Husband<int> &); // (A)
void totalIncome<int>(Husband<int> &); // (C)
在所有条件相同的情况下,重载解析将更喜欢非模板函数 (A) 而不是函数模板特化 (C)。
这也是 OP 代码中发生的情况:通过实例化 class 模板 Husband
引入了一个非模板函数,以及一个不相关的函数模板。重载解析选择非模板函数,链接器报错没有定义
各种解决方案
最简单的解决方案是在 class 定义中定义 friend 函数:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &){
// code here
}
};
使用前向声明的解决方案:
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)
{}
在这里,编译器可以找到前向声明的函数模板并对其进行特化,而不是声明一个新函数。显式添加模板参数可能是值得的:
template<typename T>
class Husband{
friend void totalIncome<T>(Husband<T> &);
};
因为这强制函数模板有一个预先声明totalIncome
。
对整个函数模板友好的解决方案:
template<typename T>
class Husband{
template<typename U>
friend void totalIncome(Husband<U> &);
};
template<typename T> void totalIncome(Husband<T> &hobj)
{}
在此解决方案中,友元声明声明了一个 函数模板 ,随后在 class 定义之后的命名空间范围内的声明 重新声明 并定义了这个函数模板。 Husband
.
的所有实例化都会成为函数模板的所有特化
我已经用 Turbo-C 编译了第一个版本的代码,并且编译没有任何错误。但是,当我在 Visual Studio 中或从 CommandLine 中使用纯 g++ 编译它时,我得到了 post 中提到的错误。
我确实在互联网上搜索并阅读了一些 Whosebug 问题和 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
注意:请注意程序中的箭头。我已经明确地把他们。所以,可能不会完成完整的程序。
容易出错的代码:
#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;
}
但在下面的例子中,只需将 class 中的定义上移,它就可以完美运行。为什么是什么原因?
固定代码:
#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;
}
首先,让我们将其缩减为重现该问题的最小示例:
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);
}
这会导致类似的链接器错误。例如,使用 clang++:
/tmp/main-fb41c4.o: In function `main': main.cpp:(.text+0xd): undefined reference to `totalIncome(Husband&)'
根本问题与 Overloaded arithmetic operators on a template causing an unresolved external error and in Strange behavior of templated operator<<
中的问题相同。因此,我将根据我对第二个问题的回答来回答。
友元函数声明
在class模板Husband
中,有一个函数totalIncome
:
friend void totalIncome(Husband<T> &);
友元函数声明在周围范围内查找已声明函数的名称(此处:totalIncome
),直至并包括最内层的封闭命名空间。如果找到该名称的声明,则声明的实体成为朋友:
class Husband;
void totalIncome(Husband&); // (A)
class Husband{
friend void totalIncome(Husband&); // befriends (A)
};
如果没有找到名称的声明,在最里面的封闭命名空间中声明一个新函数:
class Husband{
friend void totalIncome(Husband&); // declares and befriends ::totalIncome
};
void totalIncome(Husband&); // friend of class Husband
如果函数仅通过友元函数声明声明(并且在封闭命名空间中没有后面的声明),则只能通过参数相关查找找到该函数。
在 class 模板中添加函数
OP 代码中的问题是涉及 class 模板:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &);
};
规则保持不变:未找到 totalIncome
的声明。由于友元声明的签名取决于 class 模板的模板参数, Husband
的每个实例化都会在封闭的命名空间 中引入一个新函数。 (如果 friend 声明不依赖于模板参数,您将在每个 Husband
的实例化中得到一个 重新声明 。)例如,如果您实例化 Husband<int>
和 Husband<double>
,您将在全局命名空间中获得两个函数:
void totalIncome(Husband<int> &);
void totalIncome(Husband<double> &);
请注意,这是两个不相关的函数,很像:
void totalIncome(int &);
void totalIncome(double &);
使用函数模板重载函数
您可以使用函数模板重载 "ordinary" 函数:
void totalIncome(Husband<int> &); // (A)
template<typename T>
void totalIncome(Husband<T> &); // (B)
当通过Husband<int> x; totalIncome(x);
调用函数时,函数模板会产生一个实例:
void totalIncome<int>(Husband<int> &); // (C), instantiated from (B)
并且您的重载集包含两个函数:
void totalIncome(Husband<int> &); // (A)
void totalIncome<int>(Husband<int> &); // (C)
在所有条件相同的情况下,重载解析将更喜欢非模板函数 (A) 而不是函数模板特化 (C)。
这也是 OP 代码中发生的情况:通过实例化 class 模板 Husband
引入了一个非模板函数,以及一个不相关的函数模板。重载解析选择非模板函数,链接器报错没有定义
各种解决方案
最简单的解决方案是在 class 定义中定义 friend 函数:
template<typename T>
class Husband{
friend void totalIncome(Husband<T> &){
// code here
}
};
使用前向声明的解决方案:
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)
{}
在这里,编译器可以找到前向声明的函数模板并对其进行特化,而不是声明一个新函数。显式添加模板参数可能是值得的:
template<typename T>
class Husband{
friend void totalIncome<T>(Husband<T> &);
};
因为这强制函数模板有一个预先声明totalIncome
。
对整个函数模板友好的解决方案:
template<typename T>
class Husband{
template<typename U>
friend void totalIncome(Husband<U> &);
};
template<typename T> void totalIncome(Husband<T> &hobj)
{}
在此解决方案中,友元声明声明了一个 函数模板 ,随后在 class 定义之后的命名空间范围内的声明 重新声明 并定义了这个函数模板。 Husband
.