8

我正在使用两个堆栈来实现一个队列类。我的头文件如下所示:

#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 等)被重新定义了。谁能帮我澄清我在哪里重新定义了它们?

谢谢

4

4 回答 4

4

你在尝试一些我真的不喜欢的东西。这是一个幌子。

删除#include "MyQueue.cpp",将其替换为MyQueue.cpp的内容,删除文件MyQueue.cpp。现在一切都会好起来的。

您试图假装模板代码可以拆分为头文件和实现文件。但是因为它不能通过在头文件中包含实现文件来作弊。如果您不作弊或假装,并且只有一个文件,即头文件,其中包含所有内容,则不会那么混乱。

您获得重新定义的确切原因是您正在编译您的 cpp 文件,其中包含您的头文件,该文件再次包含您的 cpp 文件。所以 cpp 文件的内容被编译了两次。

于 2013-11-13T22:07:18.930 回答
3

在 C 和 C++ 中,#include 的行为类似于复制和粘贴。每次看到

#include "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 指出的那样,您甚至可以强制为特定类型或功能生成模板,以便所有单元都可以访问它。

这样,函数的定义可以嵌入到任意模块中,其余模块不会抱怨未定义函数。

我喜欢在头文件中声明小模板和模板接口,并将大型实现放在只是美化头文件的特殊文件中。您可以放置​​一些特殊的扩展名,例如 .tpp .cppt 或其他任何东西来提醒自己这是您必须在某处包含的代码(这就是我所做的)。

它是一种合适的替代方法,可以将大型实现存储在必须粘贴的头文件中,以引用类型(或函数签名)。多年来,它工作得非常好。

例如,当我准备编译我的大程序时,我可能有一个名为structures.cpp 的文件,我指定它来实现我使用的许多小结构,并为我的项目实例化所有模板。

项目中的所有其他 .cpp 文件都需要包含“mylib/template_structs.h”,以便创建模板实例并使用它们调用函数。而structures.cpp 只需要包含“mylib/template_structs.cppt”,它又可能包含template_structs.h,否则structures.cpp 也必须首先包含它。

如果结构.cpp 调用任何其他 .cpp 文件将为该模板调用的所有函数,那么我们就完成了,如果没有,那么你需要额外的步骤,比如

template class mynamespace::queue<int> ;

生成项目其余模块所需的所有其他定义。

于 2018-04-24T01:04:36.753 回答
1

问题是,编译cpp文件时,cpp文件包含.h文件,然后.h文件包含.cpp文件。然后,您同时在同一个“翻译单元”中有两个cpp 代码副本。

但是对此有几种不同的解决方案,这取决于您的最终目标是什么。

  1. 最简单、最灵活的解决方案就是从.cpp文件中删除所有模板内容并将其放入.h文件中。您可能认为这是糟糕的设计,您可能被教导将声明和定义保存在单独的文件中,但模板通常是这样实现的。(欢迎来到奇异而奇妙的 C++ 模板世界!)

  2. 但是,也许这些是“私人”模板,只能从一个.cpp文件中使用。在这种情况下,最好的办法就是将文件中的所有内容移动.h.cpp文件中。

  3. 还有第三种方法,我认为它没有得到足够的关注。首先,#include "MyQueue.cpp".h文件中删除,然后重新编译。很有可能这对你有用。但是,如果您的项目有多个.cpp文件,您可能会收到有关undefined reference to MyQueue<string> :: MyQueue(). (其中string替换为您放入队列中的任何内容。这些链接器错误可以通过放置template MyQueue<string>;在具有模板(您的)定义的文件末尾MyQueue.cpp来修复。这意味着您必须为每种类型执行一次您计划存储在队列中,但您可能会认为这是一个优势,因为它可以帮助您记住队列支持哪些类型。

于 2013-11-13T22:44:41.977 回答
0

当您包含某些内容时,它会将包含的文件替换为其中的代码,因此当您调用 #include "MyQueue.cpp" 时,它会将其替换为 cpp 文件,然后您的 cpp 文件会重新定义它。摆脱这条线将解决它。

于 2013-11-13T22:12:50.853 回答