未定义的成员函数引用错误

5

我将尝试解决有关外部定义构造函数的编译问题。

以下是两个存在此问题的类:

//Username.h

#ifndef USERNAME_H
#define USERNAME_H

#include <string>
using namespace std;

class Username{
private:
    string Name;
public:
    Username(string = "");
    string getName() const;
    void setName(string);
};

#endif

...

//Username.cpp

#include "Username.h"

Username::Username(string n):Name(n){}
string Username::getName() const{return Name;}
void Username::setName(string n){Name = n;}

...

//User.h

#ifndef USER_H
#define USER_H

#include <string>
#include "Username.h"
using namespace std;

class User{
protected:
        Username* UserUsername;
public:
    User(string s);
    virtual ~User();
    Username* getUsername() const;
    void setUsername(Username*);
};

#endif

...

//User.cpp

#include "User.h"

User::User(string s):UserUsername(new Username(s)){}

User::~User(){}

Username* User::getUsername() const{return UserUsername;}

void User::setUsername(Username* u){UserUsername=u;}

int main(){return 0;}

如果我使用"g++ User.cpp"编译,就会出现以下错误:
/tmp/ccEmWmfl.o: In function `User::User(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)':

User.cpp:(.text+0x3e): undefined reference to `Username::Username(std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'

collect2: ld returned 1 exit status

如果我使用“g++ User.cpp Username.cpp -o main”或者使用内联构造函数/析构函数,就不会出现错误。
这些类非常简单,但我还有很多需要编译的文件,其中包含不止一个类。
我在Ubuntu shell中使用g++编译是新手,请问有人可以帮助我理解吗?

2
请注意,在头文件中使用 using namespace XXX 被认为是非常不好的做法。 - Oliver Charlesworth
1
为什么不使用Makefile或者更加方便的构建系统(http://www.cmake.org/)? - André Caron
7个回答

4

使用 g++ User.cpp 命令可以编译 User.cpp 并尝试生成可执行文件(即调用链接器)。由于 User.cpp 中存在一个未在其中完全定义的符号(即这里的 Username 构造函数:

User::User(string s):UserUsername(new Username(s)){}

该符号应在链接阶段被定义。链接是通过组合所有生成的对象文件输出(即所有已生成的cpp文件)并拼接缺失的符号来完成的。在这里,您没有告诉g ++在除了带有main的cpp之外的其他位置找到Username构造函数的完整定义,因此它失败了。

然而,下面这条命令:

g ++ User.cpp Username.cpp -o main

告诉链接器在编译Username.cpp时找到完整的Username定义(在生成的目标文件中)。因此,通过使用在Username.cpp中定义的内容来匹配未在User.cpp中定义的标识符,链接可以成功填充缺失的部分。

您可能会想:“我已经通过包含头文件告诉编译器了,它应该知道!”您所做的是声明该符号。您承诺某些内容最终将被定义,无论是在编译一个cpp期间还是通过将其与另一个由编译另一个cpp生成的目标文件链接起来。g ++需要知道您打算从哪里提取所有定义,以便能够正确地构建最终的可执行文件。


3

你已经回答了自己的问题,如果在编译过程中不添加Username.cpp,用户将无法知道它。


我知道如何编译它们,但我想了解为什么要这样做,因为我曾经在Windows上使用其他工具。;) - DkSw
这就是为什么存在 makefile 的原因。 - Whiskas

2
您可以使用部分编译,使用-c标志:
g++ -c User.cpp

这会生成一个名为User.o的文件。
你需要对每个文件进行部分编译。
g++ -c Username.cpp

然后将所有目标文件(*.o 文件)链接在一起:
g++ User.o Username.o -o main

通常情况下,你会使用一些构建系统来自动化这个过程。这样你也会得到其他的好处,比如不会跳过自上次编译以来没有更改的文件的重新编译。

1
这种情况也可能发生,如果您试图从模板类的实现文件之外的源文件调用模板类的成员函数。例如:您有一个名为ABC的类,具有ABC.h和ABC.cpp(实现文件)。
只要您对泛型对象的调用位于ABC.cpp中,一切都正常。但是,如果您尝试在另一个类BCD中包含ABC.h并调用任何ABC对象的成员函数,则会收到未定义的成员函数引用错误。
解决方案:为您的模板类创建一个单独的头文件,包括实现,以避免这个误导性的错误。

0

User.cpp 添加:

#include "Username.h" 

0

这不是一个 C++ 的问题,而是一个 GCC 的问题。通常情况下,你会使用 Make 来构建一个更大的程序。然后你会有一个 makefile 规则来编译目录中的所有 .cpp 文件并将它们链接在一起。另一种选择是让你的 makefile 明确指定应该一起编译哪些 .cpp 文件。


0

你必须将所有的实现文件传递给 g++ 可执行文件。第一次编译尝试失败,因为你只将 User.cpp 编译成一个对象文件,并将其链接到一个名为 main 的可执行对象中。

在第二次尝试中,你正确地将两个必要的实现文件传递给了 g++ 可执行文件。在这种情况下,User.cppUsername.cpp 被编译成对象文件,并链接在一起形成 main 可执行文件。在这种情况下,Username::Username() 构造函数的实现是存在的。


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