为什么在模板和朋友功能的情况下出现LNK1120和LNK2019

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)

dyp*_*dyp 5

首先,让我们将其简化为一个重现问题的最小例子:

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.