我们如何将带有 class 模板的友元函数声明到 .h 文件中并将它们定义到 .cpp 文件中(不是全部在一个头文件中)?

How do we declare a friend function with a class template into .h file and define them into a .cpp file (not all in one header file)?

分离(友元函数+class模板)的declaration/definition时出现错误:
error LNK2001: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Property<int> const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABV?$Property@H@@@Z)

请注意,在没有 class 模板的情况下使用友元函数时一切正常,反之亦然 但是当同时使用这两个东西时会发生错误。

我尝试在 .cpp 文件中实例化模板,但没有成功。
另外,我在 .h 文件的末尾包含了 .cpp 文件,但它也不起作用。

class.h

template <class PropertyType>
class Property {
    PropertyType m_property;
public:
    const PropertyType& operator=(const PropertyType& value);
    friend std::ostream& operator<<(std::ostream& os, Property<PropertyType>& other);
}; 

class.cpp

template <class PropertyType>
const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
    m_property = value;
    return m_property;
}

template <class PropertyType>
std::ostream& operator<<(std::ostream& os, Property<PropertyType>& other) {
    os << other.m_property;
    return os;
}

template Property<int>;

main.cpp

int main() {
    Property<int> num;
    num = 100;
    std::cout << num << "\n";
}

把declaration/definition分成两个文件一起使用有什么问题?

您的代码片段有两个问题。

第一个问题是您将实现放在源文件而不是头文件中。所以要解决这个问题,只需将实现移到头文件中即可。

第二个问题是即使将实现移到源文件中,程序仍然无法运行(Demo)。这是因为您当前拥有的朋友声明(用于重载opearator<<)是用于普通(non-template)函数.也就是说,在您的原始代码中,operator<< for class template Property<> 不是函数模板,而是在需要时使用 class 模板实例化的“普通”函数。这就是我们所说的 模板实体 .

但是您在源文件 (.cpp) 中为重载运算符 << 提供的 定义 是针对函数模板而不是针对普通函数的。因此,对于语句 std::cout << num << "\n";,链接器无法找到对应于您具有友元声明的普通重载 operator<< 的 definition/implementation。

有两种方法可以解决这个问题:

方法一

在朋友声明中添加一个单独的参数子句。

template <class PropertyType>
class Property {
    PropertyType m_property;
public:
    const PropertyType& operator=(const PropertyType& value);
    template<typename T>  //parameter cluase added here
//---------------------------------------------------vvvvv----------------------->const added here
    friend std::ostream& operator<<(std::ostream& os,const Property<T>& other);
};
template <class PropertyType>
//----------------------------------------vvvvv---------------------------------->const added here
std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
    os << other.m_property;
    return os;
} 

Demo

方法二

这里我们为重载的 operator<<.

提供前向声明
//forward declaration for class template Property
template<typename T> class Property;

//forward declaration for overloaded operator<< 
template<typename T> std::ostream& operator<<(std::ostream&,const Property<T>&);//note the const in the second parameter
template <class PropertyType>
class Property {
    PropertyType m_property;
public:
    const PropertyType& operator=(const PropertyType& value);
//---------------------------------vvvvvvvvvvvvvv---------------------------------> angle brackets used here
    friend std::ostream& operator<<<PropertyType>(std::ostream& os,const Property<PropertyType>& other);//also note the const in the second parameter
}; 
template <class PropertyType>
const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
    m_property = value;
    return m_property;
}

template <class PropertyType>
//----------------------------------------vvvvv---------------------------------->const added here 
std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
    os << other.m_property;
    return os;
}

Demo

方法三

如果你想在源文件而不是头文件中提供实现,那么你应该添加

template std::ostream& operator<<(std::ostream& os, Property<int>& other);

在源文件中除了为友元声明添加模板参数子句外,如下所示:

class.h

#ifndef MYCLASS_H
#define MYCLASS_H
#include <iostream>
template <class PropertyType>
class Property {
    PropertyType m_property;
public:
    const PropertyType& operator=(const PropertyType& value);
    template<typename T>  //parameter clause added
//---------------------------------------------------vvvvv--------------------->const added here
    friend std::ostream& operator<<(std::ostream& os,const Property<T>& other);
}; 


#endif

class.cpp

#include "class.h"

template <class PropertyType>
const PropertyType& Property<PropertyType>::operator=(const PropertyType& value) {
    m_property = value;
    return m_property;
}

template<typename PropertyType>
//----------------------------------------vvvvv------->const added here
std::ostream& operator<<(std::ostream& os,const Property<PropertyType>& other) {
    os << other.m_property;
    return os;
}
template class Property<int>;
template std::ostream& operator<<(std::ostream& os,const Property<int>& other);


main.cpp


#include <iostream>

#include "class.h"
int main() {
    Property<int> num;
    num = 100;
    std::cout << num << "\n";
}

Demo

我所做的更改包括:

  1. 为好友声明添加了一个单独的模板参数子句。这是为了使友元声明用于函数模板。此外,我们指定了一个名为 T 而不是 PropertyType because otherwise the new PropertyTypewill shadow the outerPropertyType` 的不同类型参数。

  2. 在重载的opeartor<<.

    的第二个参数中添加了一个low-level const
  3. 在方法3中,在源文件(class.cpp)中,添加

template std::ostream& operator<<(std::ostream& os,const Property<int>& other);

对于 non-member 函数重载 operator<<