关于在 C++ 中从不同的源文件引用 class 的问题
Question about referencing a class from a different source file in C++
我最近遇到了一些 "undefined reference" 错误,我设法解决了这些错误,但我不明白该解决方案为何有效。我有以下主要源文件:
Main.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();
}
它引用了在单独的源文件中声明的 class:
Log.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;
}
};
Log.h:
#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*);
};
上面的代码为 Main.cpp 中调用的 class 日志的所有成员提供了链接器错误 "undefined reference to Log::..."。四处搜索,我最终发现评论说的是 "static members and functions should be initialized",这让我想到添加以下内容:
void Init()
{
Log log;
log.SetLevel(Log::LevelInfo);
log.Error("NULL");
log.Warning("NULL");
log.Info("NULL");
}
到我的 Log.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++ 中你需要 "definitions" 以外的东西来编译任何东西。
如果你想模仿 java-like 编译器行为,请执行以下操作:
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 整个 "Log.cpp" 到您的 "Main.cpp".
在 c++ 中,编译由 "translation units" 而不是 classes 完成。一个单元(比如.cpp
)产生一个目标文件(.o
)。这样的目标文件包含机器指令和数据。
编译器现在看不到正在编译的翻译单元之外的任何内容。
因此,与 Java 不同,当 main.cpp
被编译时,编译器只会看到 #included 到 main.cpp 和 main.cpp 本身的内容。因此编译器此时看不到 Log.cpp 的内容。
只有在 link 时,翻译单元生成的目标文件才会合并在一起。但是这个时候编译什么都来不及了
具有内联函数的class(如第一个示例)未定义任何机器指令或数据。
对于 class 的内联成员,只有在您使用它们时才会生成机器指令。
由于您在编译 Log.cpp 期间在翻译单元 Log.cpp
之外的 main.cpp
中使用了 class 成员,因此编译器不会为它们生成任何机器指令。
One Definition Rule 的问题是不同的。
您的代码组织不正确。同一个 class.
不应有两个不同的 class Log { ... };
内容
Main.cpp 需要知道 class Log
的内容,因此 class Log
的(单个)定义需要在您的 header 文件中。这就留下了 class 成员函数定义的问题。定义class成员函数的三种方式:
- 在 class 定义中(在 header 中)。
这就是您在 Log.cpp 文件中尝试的内容。如果您在 Log.h 中定义 class 定义中的所有成员,那么您根本不需要 Log.cpp 文件。
- 在 class 定义之外,使用
inline
关键字,在 header 文件中。
这看起来像:
// Log.h
class Log
{
// ...
public:
void SetLevel(Level level);
// ...
};
inline void Log::SetLevel(Level level)
{
m_LogLevel = level;
}
- 在 class 定义之外,在源文件中没有
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,以便编译器在您开始尝试定义其成员之前看到 class 定义。
您可以混合搭配这些。虽然对于什么是最好的没有严格的规定,但一般准则是小而简单的函数可以放在 header 文件中,而大而复杂的函数可能在源文件中做得更好。一些程序员建议根本不要在 class 定义中放置任何函数定义,或者将此选项限制在定义非常短且有助于明确函数目的的情况下,从那时起 (public 的一部分) class 定义是对 class 所做的事情的总结,而不是关于它如何做的细节。
在某些情况下,在源 *.cpp 文件中定义 class 可能是合适的 - 但这意味着它只能在该文件中使用。
我最近遇到了一些 "undefined reference" 错误,我设法解决了这些错误,但我不明白该解决方案为何有效。我有以下主要源文件:
Main.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();
}
它引用了在单独的源文件中声明的 class:
Log.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;
}
};
Log.h:
#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*);
};
上面的代码为 Main.cpp 中调用的 class 日志的所有成员提供了链接器错误 "undefined reference to Log::..."。四处搜索,我最终发现评论说的是 "static members and functions should be initialized",这让我想到添加以下内容:
void Init()
{
Log log;
log.SetLevel(Log::LevelInfo);
log.Error("NULL");
log.Warning("NULL");
log.Info("NULL");
}
到我的 Log.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++ 中你需要 "definitions" 以外的东西来编译任何东西。
如果你想模仿 java-like 编译器行为,请执行以下操作:
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 整个 "Log.cpp" 到您的 "Main.cpp".
在 c++ 中,编译由 "translation units" 而不是 classes 完成。一个单元(比如.cpp
)产生一个目标文件(.o
)。这样的目标文件包含机器指令和数据。
编译器现在看不到正在编译的翻译单元之外的任何内容。
因此,与 Java 不同,当 main.cpp
被编译时,编译器只会看到 #included 到 main.cpp 和 main.cpp 本身的内容。因此编译器此时看不到 Log.cpp 的内容。
只有在 link 时,翻译单元生成的目标文件才会合并在一起。但是这个时候编译什么都来不及了
具有内联函数的class(如第一个示例)未定义任何机器指令或数据。
对于 class 的内联成员,只有在您使用它们时才会生成机器指令。
由于您在编译 Log.cpp 期间在翻译单元 Log.cpp
之外的 main.cpp
中使用了 class 成员,因此编译器不会为它们生成任何机器指令。
One Definition Rule 的问题是不同的。
您的代码组织不正确。同一个 class.
不应有两个不同的class Log { ... };
内容
Main.cpp 需要知道 class Log
的内容,因此 class Log
的(单个)定义需要在您的 header 文件中。这就留下了 class 成员函数定义的问题。定义class成员函数的三种方式:
- 在 class 定义中(在 header 中)。
这就是您在 Log.cpp 文件中尝试的内容。如果您在 Log.h 中定义 class 定义中的所有成员,那么您根本不需要 Log.cpp 文件。
- 在 class 定义之外,使用
inline
关键字,在 header 文件中。
这看起来像:
// Log.h
class Log
{
// ...
public:
void SetLevel(Level level);
// ...
};
inline void Log::SetLevel(Level level)
{
m_LogLevel = level;
}
- 在 class 定义之外,在源文件中没有
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,以便编译器在您开始尝试定义其成员之前看到 class 定义。
您可以混合搭配这些。虽然对于什么是最好的没有严格的规定,但一般准则是小而简单的函数可以放在 header 文件中,而大而复杂的函数可能在源文件中做得更好。一些程序员建议根本不要在 class 定义中放置任何函数定义,或者将此选项限制在定义非常短且有助于明确函数目的的情况下,从那时起 (public 的一部分) class 定义是对 class 所做的事情的总结,而不是关于它如何做的细节。
在某些情况下,在源 *.cpp 文件中定义 class 可能是合适的 - 但这意味着它只能在该文件中使用。