我正在尝试编译一个使用模板化 Bag 类的小程序。其组织如下:
// main.cpp
#include <iostream>
#include <string>
#include "bag.h"
int main() {
Bag<std::string> bag;
bag.add("cheese");
bag.add("milk");
bag.add("eggs");
bag.add("butter");
bag.add("orange juice");
std::cout << bag.getCount() << std::endl;
return 0;
};
// bag.h
#ifndef _BAG
#define _BAG
#include <stdexcept>
const size_t _MAX_SIZE = 5;
template<typename T>
class Bag {
public:
Bag();
int getCount() const;
bool isEmpty() const;
bool add(const T& item);
private:
size_t count;
T contents[_MAX_SIZE];
};
#include "bag.cpp"
#endif
和
// bag.cpp
#include "bag.h"
template<typename T>
Bag<T>::Bag() {
count = 0;
};
template<typename T>
int Bag<T>::getCount() const {
return count;
};
template<typename T>
bool Bag<T>::isEmpty() const {
return !getCount();
};
template<typename T>
bool Bag<T>::add(const T& item) {
bool flag = count < _MAX_SIZE;
if (flag) {
contents[count] = item;
count++;
}
return flag;
};
我使用g++版本11.4.0来编译。当我输入命令
g++ main.cpp bag.cpp
时,我会看到错误
bag.cpp:4:1: error: redefinition of ‘Bag<T>::Bag()’
4 | Bag<T>::Bag() {
| ^~~~~~
我正在使用 VS Code。
当我从
#include "bag.cpp"
中删除 bag.h
时,出现错误
/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: /tmp/ccZmwYEn.o:main.cpp:(.text+0x1e): undefined reference to `Bag<std::string>::Bag()'
当我从
#include "bag.h"
中删除 bag.cpp
时,我收到错误
bag.cpp:4:1: error: ‘Bag’ does not name a type
4 | Bag<T>::Bag() {
| ^~~
我已在标头中包含了实现代码,但我不喜欢这种廉价的解决方法。我有什么遗漏的吗?不确定如何继续。我感谢我能得到的所有帮助。谢谢你。
两个主要问题:
你有一个圆形
#include
。 #include
表示简单复制并粘贴文件的全部内容来代替该指令。如果存在循环包含,则意味着编译器必须无限地复制和粘贴它们。从 #include "bag.h"
中删除 bag.cpp
。
由于
bag.cpp
不是一个可以单独编译的完整翻译单元(由于删除了Bag
而缺少#include
类的定义),因此不应将其编译为一个单元。从编译命令中删除 bag.cpp
。
最终会困扰你的第三个问题:
_MAX_SIZE
那样在头文件中声明全局变量(另外,该名称是 保留供编译器使用,请选择其他名称)。对于这个程序来说这很好,因为您只需 #include
该标头一次,但对于任何更复杂的事情,您都会违反“单一定义规则”。使用 C++17,您可以通过 inline
来解决这一问题 - 它只会为整个程序创建一个全局变量。在此之前,您可以做到 static
- 每个翻译单元都会有自己的副本,但假设您只使用该值,那不会有太大问题。推荐:
.cpp
对于大多数人来说意味着“翻译单元”。您的文件不是翻译单元,它是标题的一部分。 .tpp
是一个常见的扩展,用于告诉其他人“这是一个模板实现,它是标头的一部分”。