【问题标题】:Splitting a C++ class into files now won't compile将 C++ 类拆分为文件现在不会编译
【发布时间】:2011-02-12 17:33:52
【问题描述】:

我正在自学用 C++ 编写类,但似乎无法通过编译。如果您能帮助我弄清楚不仅是如何,而且是为什么,将不胜感激。先感谢您!这是我的三个文件:

ma​​ke_pmt.C

#include <iostream>
#include "pmt.h"

using namespace std;


int main() {
    CPMT *pmt = new CPMT;
    pmt->SetVoltage(900);
    pmt->SetGain(2e6);

    double voltage = pmt->GetVoltage();
    double gain= pmt->GetGain();

    cout << "The voltage is " << voltage
         << " and the gain is " << gain << "." <<endl;

    return 0;
}

pmt.C

#include "pmt.h"

using namespace std;

class CPMT {
    double gain, voltage;
    public:
        double GetGain() {return gain;}
        double GetVoltage() {return voltage;}

        void SetGain(double g) {gain=g;}
        void SetVoltage(double v) {voltage=v;}
};

pmt.h

#ifndef PMT_H
#define PMT_H 1

using namespace std;

class CPMT {
    double gain, voltage;
    public:
        double GetGain();
        double GetVoltage();

        void SetGain(double g);
        void SetVoltage(double v);
};

#endif

作为参考,我得到一个链接器错误(对吗?):

Undefined symbols:
  "CPMT::GetVoltage()", referenced from:
      _main in ccoYuMbH.o
  "CPMT::GetGain()", referenced from:
      _main in ccoYuMbH.o
  "CPMT::SetVoltage(double)", referenced from:
      _main in ccoYuMbH.o
  "CPMT::SetGain(double)", referenced from:
      _main in ccoYuMbH.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

【问题讨论】:

  • 在标题中放置“使用”指令通常是一个坏主意,因为该指令会应用于包含标题的任何文件。在您的情况下,任何包含 pmt.h 的文件 - 甚至是间接的 - 都会将所有命名空间 std 拉入全局命名空间。
  • 我同意加雷斯的观点。请参阅此处:stackoverflow.com/questions/2712076,了解为什么成为 using namespace std;(或任何其他人,就此而言,根本不是)是一个坏主意。

标签: c++ class split compilation


【解决方案1】:

首先是一些分类。
这个

class CPMT {
    public:
        double GetGain();
        // ...
};

定义一个类而不定义成员函数。这个

class CPMT {
    public:
        double GetGain() {return gain;}
        // ...
};

定义了同一个类,还定义了它的成员函数(隐式)内联。这个

double CPMT::GetGain() {return gain;}
// ...

正在定义成员函数(不是内联)。

现在,如果你想将实现与接口分开,你的头文件需要定义类,而你的实现文件需要定义它的成员函数。所以纯类定义

class CPMT {
    public:
        double GetGain();
        //...
};

进入头文件和实现

double CPMT::GetGain() {return gain;}
// ...

进入实现文件 - 除了你想要实现的那些成员函数内联。由于inline 要求编译器在每次调用时都替换为函数的实现,因此该实现必须存在于调用函数的位置。这就是为什么内联函数的实现必须在头文件中的原因。

有两种方法可以内联成员函数。一种是在其类的定义中定义它

class CPMT {
    public:
        double GetGain() {return gain;}
        // ...
};

这隐含地使它成为inline。另一种是显式内联

class CPMT {
    public:
        double GetGain();
        //...
};

inline double CPMT::GetGain() {return gain;}
// ...

在这两种情况下,实现都必须在头文件中。

【讨论】:

  • 内联函数的一个很好的链接是:parashift.com/c++-faq-lite/inline-functions.html
  • +1:我同意。如今,许多现代 C++ 库都只有标头。我尝试在标头中编写尽可能多的代码,并且仅在必要时才放置实现。
  • @Eddy:我不同意。有理由制作inline,也有反对这样做的理由。 (对于初学者来说,现在很多现代 C++ 库都是只有标头的,因为它们的大部分东西都是模板,必须在标头中。此外,库中的内联代码的情况 i> 比 应用程序代码 要强大得多。)
  • @sbi:这方面没有好的指导方针,但我想说的是它可以加快开发速度。它可以节省时间,而不必保持标头和 cpp 文件同步。如果有具体的理由要改变它,你可以改变它,这很便宜。见stackoverflow.com/questions/2174657/…
  • @Eddy:您是否曾经在多个 MLoC 项目中处理过全模板标题?在整个代码库中都使用了一些基本的东西,比如错误处理或字符串操作?您为仅由其他模板的私有实现使用的辅助类更改了一些辅助函数的实现,并等待 20 分钟等待编译器。然后您意识到,您的一些重要同事忽略了您的文档,只是查看了代码,并依赖于您将要更改的实现工件。内联会非常严重减慢开发速度。
【解决方案2】:

pmt.C 看起来像这样:

#include "pmt.h"

using namespace std;

double CPMT::GetGain() {return gain;}
double CPMT::GetVoltage() {return voltage;}

void CPMT::SetGain(double g) {gain=g;}
void CPMT::SetVoltage(double v) {voltage=v;}

我是这样编译的:

g++ make_pmt.C pmt.C

您还需要添加一个构造函数并初始化增益和电压。

【讨论】:

  • 我已经读过,如果你省略了一个构造函数,它会自动使用没有提供参数的默认构造函数。是的?我只是没有初始化变量,因为它只是一个有启发性的例子。还是绝对有比double gain, voltage更多的东西?
  • 它会生成一个默认的构造函数,但是增益和电压不会被初始化并且包含垃圾。
【解决方案3】:

您的 pmt.C 文件的语法错误。它应该是

double CPMT::GetGain() {return gain;}
double CPMT::GetVoltage() {return voltage;}
void CPMT::SetGain(double g) {gain=g;}
void CPMT::SetVoltage(double v) {voltage=v;}

【讨论】:

    【解决方案4】:

    在 pmt.c 中,您正在重新定义类。相反,您应该只定义它的功能:

    double CPMT::GetGain() { return gain; }
    double CPMT::GetVoltage() {return voltage;}
    
    void CPMT::SetGain(double g) {gain=g;}
    void CPMT::SetVoltage(double v) {voltage=v;}
    

    此外,您需要确保将两个目标文件都提供给链接器。

    【讨论】:

    • CPMT:: 应该在每个函数名之前
    【解决方案5】:

    尝试将文件重命名为 .cpp 或 .cxx,您的编译器可能会假设 .C 表示它是 C 而不是 C++,这似乎是因为它不会破坏名称。

    【讨论】:

    • 感谢您的建议,但我使用 .C 扩展名已经有一段时间了,似乎还不错。然而,这不是我的选择!
    【解决方案6】:

    看起来您正在编译和链接单独的 .C 文件,这些文件本身并不完整。您需要先编译它们以获得.o 文件,然后链接.o 文件以获得最终的可执行文件。这一切都可以使用:

    g++ make_pmt.C pmt.C
    

    pmt.C 也应该只定义在头文件中声明的函数。

    【讨论】:

      猜你喜欢
      • 2016-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-04
      相关资源
      最近更新 更多