我使用两个堆栈来实现队列类。我的头文件如下:
#ifndef _MyQueue_h
#define _MyQueue_h
using namespace std;
template <typename T>
class MyQueue {
public:
MyQueue();
~MyQueue();
void enqueue(T element);
T peek();
void dequeue();
int size();
bool empty();
private:
int count;
stack<T> stk1;
stack<T> stk2;
};
# include "MyQueue.cpp"
# endif
我的cpp(实现)文件如下所示:
#include <stack>
#include "MyQueue.h"
using namespace std;
template <typename T>
MyQueue<T>::MyQueue()
{
count = 0;
}
template <typename T>
MyQueue<T>::~ MyQueue()
{
}
template <typename T>
void MyQueue<T>::enqueue(T element)
{
stk1.push(element);
count ++;
}
(省略其他功能)。
然而,使用Xcode 4.5,它一直在说我的函数(MyQueue,~MyQueue,enqueue,peek等)被重新定义。任何人都可以帮我澄清我在哪里重新定义它们?
谢谢
答案 0 :(得分:5)
你正在尝试一些我真的不喜欢的东西。这是假装。
删除#include "MyQueue.cpp"
,将其替换为MyQueue.cpp的内容,删除文件MyQueue.cpp。现在一切都会奏效。
您正在尝试假装模板代码可以拆分为头文件和实现文件。但是因为它不能通过在头文件中包含实现文件而作弊。如果你不作弊或假装,只有一个文件,头文件,其中包含所有内容,那就不那么令人困惑了。
您获得重新定义的确切原因是您正在编译cpp文件,其中包括您的头文件,其中再次包含您的cpp文件。因此,cpp文件的内容会被编译两次。
答案 1 :(得分:1)
问题在于,在编译cpp文件时,cpp文件包含.h
文件,然后.h
文件包含.cpp
文件。然后,您在同一个“翻译单元”中同时拥有两个的cpp代码副本。
但是有一些不同的解决方案,这取决于你的最终目标。
最简单,最灵活的解决方案就是从.cpp
文件中删除所有模板内容,然后将其放入.h
文件中。您可能认为这是一个糟糕的设计,您可能已经被教会将声明和定义保存在单独的文件中,但这通常是模板的实现方式。 (欢迎来到奇怪而精彩的C ++模板世界!)
但是,也许这些是'私有'模板,只能在一个.cpp
文件中使用。在这种情况下,最好的办法就是将.h
文件中的所有内容移动到.cpp
文件中。
还有第三种方法,在我看来并没有得到足够的重视。首先,从#include "MyQueue.cpp"
文件中删除.h
,然后重新编译。它很可能会对你有用。但是,如果您的项目有多个.cpp
文件,则可能会收到有关undefined reference to MyQueue<string> :: MyQueue()
的链接器错误。 (其中string
将替换为您放入队列中的任何内容。可以通过将template MyQueue<string>;
放在具有定义的文件的 end 来修复这些链接器错误。模板(您的MyQueue.cpp
)。这意味着您必须为计划存储在队列中的每种类型执行此操作一次,但您可能会将此视为优势,因为它可以帮助您记住队列支持的类型
答案 2 :(得分:1)
在C和C ++中,#include的行为类似于复制和粘贴。 每次看到
#inclue "file"
它应该被视为你在一个地方重新输入整个文件。 因此,如果您编译MyQueue.cpp,预处理器将预先添加MyQueue.h的内容, 它本身适用于由
证明的MyQueue.cpp副本# include "MyQueue.cpp"
然后遵循MyQueue.cpp的原生内容。
所以
的结果# include "MyQueue.cpp"
MyQueue.h中的就像你写了一个包含内容的大文件一样 再次调用MyQueue.h,MyQueue.cpp和MyQueue.cpp。 (当然还有堆栈的包含) 这就是编译器抱怨重新定义函数的原因。
从
插入复制品# include "MyQueue.cpp"
也可能包含
行#include "MyQueue.h"
但是我认为包含警卫(ifndef,endif)可以防止递归扩展 似乎不是问题。
我想指出,将所有实现代码和声明代码放在模板的同一文件中并不是唯一的解决方案,正如其他人所说的那样。
您必须记住,模板是在编译时生成的,并将它们包含在需要的任何地方。与Aaron has pointed out类似,您甚至可以强制为特定类型或函数生成模板,以便所有单元都可以访问它。
通过这种方式,函数的定义可以嵌入到任意模块中,其余模块不会抱怨没有定义函数。
我喜欢在头文件中声明小模板和模板接口 并将大型实现放在特殊文件中,这些文件只是美化标题。你可以放一些特殊的扩展名,如.tpp .cppt或其他什么来提醒自己这是你必须包含在某处的代码(这就是我所做的)。
它是在头文件中存储大型实现的合适替代方法,必须粘贴它们才能引用类型(或函数签名)。多年来它一直很好用。
所以例如,当我准备好编译我的大程序时,我可能有一个名为structures.cpp的文件,我指定它来实现我使用的许多小结构,以及为我的项目实例化所有模板。 / p>
项目中的所有其他.cpp文件都需要包含“mylib / template_structs.h”,以便创建模板实例和调用函数。而construct.cpp只需要包含“mylib / template_structs.cppt”,而mibib / template_structs.cppt又可以包含template_structs.h 或者construct.cpp也必须首先包括它。
如果structures.cpp调用任何其他.cpp文件将调用该模板的所有函数,那么我们就完成了,如果没有,那么你需要额外的步骤,比如
template class mynamespace::queue<int> ;
生成项目其余模块所需的所有其他定义。
答案 3 :(得分:0)
当您包含某些内容时,它会将代码替换为包含的文件 #include“MyQueue.cpp” 它用cpp文件替换它,然后你的cpp文件重新定义它。 摆脱这条线将解决它。