为什么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.

的所有实例化都会成为函数模板的所有特化