这篇文章旨在为现有答案提供补充,因为它们没有解决一个非常普遍的问题(特别是对于初学者):如何在单独的源文件中包含带有模板的头文件。
基本上我有多个.cpp文件,其中包含了我为二叉树、BST、链表等所编写的所有函数。
由于:
- 您已经标记了您的问题
c++,
- C++ 拥有模板技术,
- 您拥有类似容器的数据结构(BST、链表等),我假设这些数据结构使用了模板,
我想指出其他答案中没有提到的一个常见问题。请考虑以下两个文件:
文件
class.hpp
:
#pragma once
template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
文件 class.cpp
:
#include "class.hpp"
#include <iostream>
template<class T> Class<T>::Class()
{
std::cout << "Class()" << '\n';
}
template<class T> T square(const T& x)
{
return x*x;
}
尝试实例化一个
Class
对象,或调用
square()
函数:
#include "class.hpp"
#include <iostream>
int main()
{
Class<int> clss;
std::cout << square<int>(3);
}
使用以下命令进行编译:
g++ class.cpp main.cpp -o cpptest.exe
./gcc/bin/ld.exe: main.cpp(.text+0x15): undefined reference to 'Class<int>::Class()'
./gcc/bin/ld.exe: main.cpp(.text+0x28): undefined reference to 'int square<int>(int const&)'
问题
1. 模板
假设你有这个函数:
template<class T> T add(const T& x, const T& y)
{
return x + y;
}
你可以这样调用:
int res = add<int>(2, 3);
在调用时刻(即编译器看到对add()
的调用时),编译器会创建函数实现的副本:
int add(const int& x, const int& y)
{
return x + y;
}
然后你的调用将变成:
int res = add(2, 3);
这显然意味着当调用被找到时,模板函数的实现必须对编译器可见,以便编译器能够复制它。而为了让编译器找到/看到它,它必须在同一个翻译单元中。
这就是模板魔法在幕后的工作原理。有关更多信息,请参阅此答案。
2. 编译器和链接器
通过编译步骤:
- 将所有的
#include
指令替换为所包含文件的内容。
- 单独编译每个
.cpp
文件(也称为“翻译单元”)。结果将是每个单元的目标文件。请注意,一些代码(如main.cpp
中的代码)引用另一个代码(class.cpp
)。这将在下一步中解决。
- 将翻译单元链接在一起形成可执行文件。(有关详细信息,请参见JMAA的答案)。
应用上述第1步,我们将有两个翻译单元(.cpp
文件):
文件class.cpp
:
template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
template<class T> Class<T>::Class()
{
std::cout << "Class()" << '\n';
}
template<class T> T square(const T& x)
{
return x*x;
}
文件 main.cpp
:
template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
int main()
{
Class<int> clss;
std::cout << square<int>(3);
}
应用以上步骤2,我们将只有地址(这不是真正的对象代码的外观,这只是一个简化):
文件 class.cpp
:
iostream_object_code
文件 main.cpp
:
iostream_object_code
int main()
{
class_int_obj_placeholder clss;
__operator_xx(cout_obj_ref, fn100017(3));
}
执行上述步骤3,您将得到undefined reference
错误:链接器不知道任何翻译单元中的fn100017()
。因为square()
是一个模板(或者模型),编译器没有生成任何具体实现。对于Class
也是同样的情况。
解决方案
- 显而易见的解决方法是将定义和实现都放在同一个头文件中。这样,在
main.cpp
中包含时,两者都将对编译器可见,因为它们在同一个翻译单元中。
- 或者,如果您想保持定义和实现分离,可以在头文件中进行实现,然后在定义头文件的末尾包含它:
文件class.hpp
:
#pragma once
template<class T> class Class {
T data;
public:
Class();
};
template<class T> T square(const T& x);
#include "class_impl.hpp"
文件 class_impl.hpp
:
#include <iostream>
template<class T> Class<T>::Class()
{
std::cout << "Class()" << '\n';
}
template<class T> T square(const T& x)
{
return x*x;
}
使用相同的命令编译并运行main.cpp
:
Class()
9
g++ A.cpp B.cpp
- user4581301g++ A.cpp ../../source/b/B.cpp
的意思是在当前文件夹中查找A.cpp,并向后返回两个文件夹,在source/b中查找B.cpp。请注意,这只是告诉编译器文件的位置,并不会改变文件本身。 - user4581301static
关键字在函数声明之前将其链接性限制为内部,也就是说,静态函数不能在其定义之外的文件中使用。 - undefined