【问题标题】:C++ Error message redefinition of functions函数的 C++ 错误消息重新定义
【发布时间】:2023-03-10 06:19:01
【问题描述】:

我正在使用两个堆栈来实现一个队列类。我的头文件看起来像:

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

谢谢

【问题讨论】:

  • 你永远不应该 include 源文件(即以 .cpp.cc.C 结尾的文件)。此外,请确保 include 定义了 stack 的头文件,除非您使用的是 STL 堆栈。

标签: c++ class stack queue redefinition


【解决方案1】:

你在尝试一些我真的不喜欢的东西。这是一种伪装。

去掉#include "MyQueue.cpp",替换成MyQueue.cpp的内容,删除文件MyQueue.cpp。现在一切正常。

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

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

【讨论】:

  • 我最近看到有人使用.tpp 文件来存储实现并将其包含在.hpp 文件的末尾。你会同意吗?当然,这对编译器来说毫无意义,但对某些人来说可能看起来更“整洁”。
  • 我也看到过,对我来说似乎毫无意义。当一个文件包含在另一个文件中时,只有在一个地方引用的文件有什么意义。同样,为什么不只有一个文件?但这些都是样式问题,只要你不尝试编译 tpp 文件,它就可以这样工作。
  • 谢谢。这只是一种做法:)我在cpp文件中删除了“#include“MyQueue.h”,但现在它仍然无法编译。Xcode抱怨说(在它遇到的第一个函数,构造函数,MyQueue:: MyQueue()):1)“未知类型名称‘MyQueue’”和 2),“预期的 unqualified_id。”有什么想法吗?
  • @HaydenCoyle 再次阅读我的回答,您所做的事情与我的建议完全不同。基本上你应该删除cpp文件并将所有代码放在头文件中。
【解决方案2】:

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

【讨论】:

  • 谢谢。我在cpp文件中删除了“#include“MyQueue.h”,但现在它仍然没有编译。Xcode抱怨(在它遇到的第一个函数,构造函数,MyQueue::MyQueue()):1) “未知类型名称'MyQueue'”和2),“预期的unqualified_id。”有什么想法吗?
  • @HaydenCoyle,这个答案不太清楚要删除哪一行。您可以从头文件中删除# include "MyQueue.cpp"如果你决定将这两个文件分开保存,那么你应该在cpp文件中重新插入#include "MyQueue.h
【解决方案3】:

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

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

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

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

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

【讨论】:

    【解决方案4】:

    在 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 has pointed out 一样,您甚至可以强制为特定类型或功能生成模板,以便所有单元都可以访问它。

    通过这种方式,函数的定义可以嵌入到任意模块中,其余模块不会抱怨没有定义函数。

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

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

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

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

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

    这样的额外步骤
    template class mynamespace::queue<int> ;
    

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

    【讨论】:

    • Stack Overflow 是一个问答网站。答案框仅用于问题的完整答案。也许我只是没有看到它,但是您在帖子中的哪个位置回答了重新定义函数的位置?如果不是,我建议您回答顶部的问题,然后将帖子的其余部分添加为侧边栏(在每一行前加上 &gt;)。这将有助于防止在审核期间因未回答所提问题而删除答案。
    • 我的印象是,在 SO 上,解决提问者潜在问题的答案算作答案。我添加了我的答案,因为我害怕人们会来到这个页面,唯一的补救办法是将所有代码包含在一个文件中,我看到没有人介绍这种确切的方法。但由于我仍在建立我对 SO 的理解,我会相信你的判断。我不介意删除答案,除非您认为将其保留为侧边栏会更有用。
    • 没错,解决根本问题的答案很好。但是,如果是这种情况,您并没有说得很清楚(不清楚的事实是我评论而不是标记您的答案的原因)。最好的方法是将被问到的问题放在答案的顶部(前缀为&gt;),然后立即解决。无需将帖子设为侧边栏。在这种情况下,您可以说“您不一定必须这样做......”这样的话,让审阅者很容易理解您是在直接解决 OP(即使您还指的是其他答案)。
    • 我能做到,我现在会尝试编辑它,如果你愿意的话,请给我反馈看看它是否更好看。
    • 顺便说一句 - 在 SO 上的帖子由 upvotes 排序,这可能会随着时间而变化,因此使用“以上”一词来指代另一个答案是不合适的。相反,您应该使用下面的 share 链接链接到另一个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-21
    相关资源
    最近更新 更多