有关从C++中的不同源文件引用类的问题



我最近遇到了一些"未定义的引用"错误,但我不明白为什么该解决方案有效。我有以下主源文件:

主.cpp:

#include <iostream>
#include "Log.h"
int main()
{
std::cout << "Hello World!" << std::endl;
Log log;
log.SetLevel(Log::LevelWarning);
log.Error("Hello!");
log.Warning("Hello!");
log.Info("Hello!");
std::cin.get();
}

它引用在单独的源文件中声明的类:

日志.cpp:

#include <iostream>
class Log
{
public:
enum Level
{
LevelError, LevelWarning, LevelInfo 
};
private:
Level m_LogLevel = LevelInfo;
public:
void SetLevel (Level level)
{
m_LogLevel = level;
}
void Error (const char* message)
{
if (m_LogLevel >= LevelError)
std::cout << "[ERROR]: " << message << std::endl;
}
void Warning (const char* message)
{
if (m_LogLevel >= LevelWarning)
std::cout << "[WARNING]: " << message << std::endl;
}
void Info (const char* message)
{
if (m_LogLevel >= LevelInfo)
std::cout << "[INFO]: " << message << std::endl;
}
};

日志:

#pragma once
class Log
{
public:
enum Level { LevelError, LevelWarning, LevelInfo };
private:
Level m_LogLevel;
public:
void SetLevel (Level);
void Error (const char*);
void Warning (const char*);
void Info (const char*);
};

上面的代码给了我链接器错误"对 Log::..."对于在 Main.cpp 中调用的类日志的所有成员。搜索四周,我最终发现评论说"静态成员和函数应该初始化",这给了我添加以下内容的想法:

void Init()
{
Log log;
log.SetLevel(Log::LevelInfo);
log.Error("NULL");
log.Warning("NULL");
log.Info("NULL");
}

到我的日志.cpp文件。这惊人地解决了问题并且项目成功构建,但是这些成员没有声明为静态,所以我不明白为什么这有效,或者即使这是正确的解决方案。

我在 linux 中使用 gcc 并使用"g++ Main.cpp Log.cpp -o main"进行编译。源文件位于同一文件夹中。

c++不是java也不是c#。此构造根本不会生成任何代码:

class X
{
public:
void foo()
{
std::cout << "Hello, world"<< std::endl;
}
};

是的,在java中编译后,您将获得可以使用的X.class。但是,在 c++ 中,这不会产生任何东西。

证明:

#include <stdio.h>
class X
{
void foo()
{
printf("X");
}
};
$ gcc -S main.cpp
$ cat main.s
.file   "main.cpp"
.ident  "GCC: (GNU) 4.9.3"
.section        .note.GNU-stack,"",@progbits

在 c++ 中,你需要除"定义"之外的东西来编译任何内容。

如果要模拟类似 Java 的编译器行为,请执行以下操作:

class X
{
public:
void foo();
};
void X::foo()
{
std::cout << "Hello, world"<< std::endl;
}

这将生成包含void X::foo()的对象文件。

证明:

$ gcc -c test.cpp
$ nm --demangle test.o
0000000000000000 T X::foo()

另一种选择当然是像你一样使用内联方法,但在这种情况下,你需要将整个"Log.cpp"#include 到你的"Main.cpp"中。

在 c++ 中,编译是由"翻译单元"而不是类完成的。一个单元(比如.cpp(产生一个目标文件(.o(。此类目标文件包含机器指令和数据。

编译器现在看不到正在编译的翻译单元之外的任何内容。

因此,与Java不同,当编译main.cpp编译器时,编译器只能看到 #included 到main.cpp和main.cpp本身的内容。因此,编译器此时看不到 Log 的内容.cpp。

只有在链接时,从翻译单元生成的对象文件才会合并在一起。但此时编译任何东西都为时已晚。

具有内联函数的类(如第一个示例(不定义任何机器指令或数据。

对于类的内联成员,机器指令仅在您使用它们时生成。

由于在编译 Log 期间,您在翻译单元Log.cpp之外的main.cpp中使用类成员.cpp因此编译器不会为它们生成任何机器指令。

一个定义规则的问题是一个不同的问题。

您的代码组织不正确。同一类不应有两个不同的class Log { ... };内容。

main.cpp 需要知道class Log的内容,因此class Log的(单个(定义需要在您的头文件中。 这就留下了类成员函数的定义问题。 有三种方法可以定义类成员函数:

  1. 在类定义中(位于标头中(。

这是您在 Log.cpp 文件中尝试的内容。 如果在 Log.h 中定义类定义中的所有成员,则根本不需要 Log.cpp 文件。

  1. 类定义之外,在头文件中使用inline关键字。

这看起来像:

// Log.h
class Log
{
// ...
public:
void SetLevel(Level level);
// ...
};
inline void Log::SetLevel(Level level)
{
m_LogLevel = level;
}
  1. 类定义之外,在源文件中没有inline关键字。

这看起来像:

// Log.h
class Log
{
// ...
public:
void SetLevel(Level level);
// ...
};
// Log.cpp
#include "Log.h"
void Log::SetLevel(Level level)
{
m_LogLevel = level;
}

注意 Log.cpp 包括 Log.h,以便编译器在您开始尝试定义其成员之前看到类定义。

您可以混合搭配这些。 尽管没有关于什么是最好的严格规定,但一般准则是小而简单的函数可以放在头文件中,而大而复杂的函数在源文件中可能会做得更好。 一些程序员建议不要在类定义中放置任何函数定义,或者将此选项限制在定义非常短的情况下,并且有助于明确函数的用途,因为那时(公共部分(类定义是类做什么的摘要,而不是关于它如何做的细节。

在某些情况下,在源 *.cpp 文件中定义一个类可能是合适的 - 但这意味着它只能从该文件使用。

最新更新