错误 LNK2005: 已定义 - C++

16

背景

我有一个名为 PersonLibrary 的项目,它包含两个文件。

  1. Person.h
  2. Person.cpp

该库生成一个静态库文件。另一个项目是 TestProject,它使用了 PersonLibrary(通过 VS008 中的项目依赖项添加)。一切都很正常,直到我在 Person.h 中添加了一个非成员函数。 Person.h 的代码如下:

class Person
{
public:
    void SetName(const std::string name);

private:
    std::string personName_;
};

void SetPersonName(Person& person,const std::string name)
{
    person.SetName(name);
}

Person.cpp 定义了 SetName 函数。当我尝试从 TestProject 中使用 SetPersonName 时,出现了 error LNK2005: already defined 错误。以下是我的使用方式:

#include "../PersonLibrary/Person.h"
int main(int argc, char* argv[])
{
    Person person;
    SetPersonName(person, "Bill");
    return 0;
}

已尝试解决方法

1 - 我移除了 Person.cpp 文件,并将整个类定义在 Person.h 中。错误消失,一切正常。

2 - 将 SetPersonName 修改器改为 static。修改后如下:

static void SetPersonName(Person& person,const std::string name)
{
    person.SetName(name);
}

问题

  1. 为什么第一段代码不像我预期的那样运行?
  2. static 在这里有何不同?
  3. 这个问题的适当解决方案是什么?

谢谢

7个回答

23

您需要执行以下操作之一:

  • SetPersonName 的定义移到一个 .cpp 文件中,编译并链接到生成的目标文件
  • SetPersonName 定义为内联函数

这是一个已知的违反“单一定义规则”的情况。

静态关键字使该函数的链接仅限于所在的翻译单元可用。然而,这掩盖了真正的问题。我建议将函数的定义移动到其自己的实现文件中,但保留头文件中的声明。


谢谢回答。我已经解决了。 - Navaneeth K N
@dirkgently... 但是我记得boost是仅有头文件的,也许其他库也是。他们是如何避免这样的问题的? - Mr. Boy
1
可能会为他人节省时间的内容:如果您在VS上编译C程序,应使用 __inline 将其设置为内联(否则将在编译时失败)。 - Illidan
我也遇到了全局变量的问题,情况与此处完全相同。只需将 "SetPersonName()" 替换为全局变量即可。我该如何解决这个问题? - codeLover

3
当你编译库时,它的lib文件包含SetPersonName的定义。当你编译使用该库的程序时,由于它包含头文件,并且你已经在头文件中内联编写了代码,因此它也会编译出一个SetPersonName的定义。两个相同函数的定义通常是不允许的。静态关键字告诉编译器该函数不应被暴露在当前翻译单元(正在编译的离散代码片段)之外,因此库中的定义对链接器不可见。
解决这个问题的适当方法取决于你的目标。头文件中带有静态函数声明几乎永远不是你想要的。从设计角度来看,我建议彻底摒弃SetPersonName,只使用Person::SetName。
然而,如果无法避免,我会像你为其余功能所做的那样,在头文件中进行声明,在.cpp文件中进行实现。与库相关联的内联函数往往会削弱使用库的许多优势。

1
  1. 函数SetPersonName将被编译到包含Person.h文件的每个目标文件中,因此使链接器看到多个函数并给出错误。

  2. 通过写入static,您声明该函数仅在单个目标文件中可见。您仍将在二进制文件中获得多个函数,但现在不会出现错误。

  3. 尝试在函数前面写入inline,如下所示:

    inline void SetPersonName(Person& person,const std::string name)
    {
        person.SetName(name);
    }
    

    ...因为这个函数非常简单,我认为将其作为内联函数是可以的。内联函数将在使用函数的地方放置必要的代码,而不会创建要调用的函数。


1
通过声明函数为静态,您将其范围限定在当前翻译单元中,因此实际上您已经在主文件中添加了一个新的SetPersonName函数,并且会调用该函数而不是库中定义的函数。
正确的解决方案是在person.h中声明SetPersonName为extern,并在person.cpp中实现它。

Person.h

extern void SetPersonName(Person& person,const std::string name);

Person.cpp

void SetPersonName(Person& person,const std::string name)
{
    person.SetName(name);
}

0

我曾经遇到过与@logan-capaldo之前描述的类似情况。

一个CPP源文件(myfile.cpp)包含了一个名为MyFunction的函数。在编译时,它被编译成了myfile.obj。但是主CPP文件(main.cpp)也包含了myfile.cpp,因此函数MyFunction被包含/编译/链接了两次,导致了“LNK2005已定义”错误。

这很混乱,但我没有时间正确地修复它。最快的解决方法(在VS Express 2012中)是右键单击Solution Explorer中的myfile.cpp,转到属性并将“从构建中排除”更改为“Yes”。我想这可以防止创建和/或链接其中一个OBJ文件,从而消除了错误。


0
一个解决办法是将该函数声明为静态方法。这将阻止“已定义”错误的发生。

0
对于在Qt项目中遇到此错误的任何人,请确保您的头文件中没有在signals:下定义任何非信号函数。
不正确的是,在Foo::promiseData()上抛出LNK2005
class Foo : public QObject {
        Q_OBJECT

    public:
        explicit Foo(QObject* parent = nullptr);

    signals:
        void dataReady(QList<QObject*> data) const;

        void promiseData() const; // <-- This function is not supposed to be a signal.

正确:

class Foo : public QObject {
        Q_OBJECT

    public:
        explicit Foo(QObject* parent = nullptr);

        void promiseData() const;

    signals:
        void dataReady(QList<QObject*> data) const;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接