使用多个文件制作和编译 C++ 项目

Making and compiling C++ projects with multiple files

我是 C/C++ 的新手,我已经了解基础知识,现在我开始学习更高级的概念。

目前我正在用C++开发一个项目,比较大,所以结构化我的项目会更好。

据我所知,一个好的结构至少依赖于文件夹:/src/include。所有 .cpp 文件都应该放在 /src 文件夹中,.hpp 文件应该放在 /include 文件夹中。这里出现了第一个疑问:如何包含一个不在同一目录或标准目录中的 header ?在搜索之后我得出结论,最好的方法是将选项 -I../include 传递给编译器。

我有三个源文件:main.cpp、GraphData.cpp 和 RandomGraphGenerator.cpp。我读过,最好为每个文件设置一个 header,以将声明与定义分开。现在我也有两个 header,但我不知道如何编译所有这些。

我专注于使 GraphData.cpp 正确链接到 main.cpp,我评论了所有需要 RandomGraphGenerator.cpp 的内容。这是我的主文件:

// main.cpp
#include <iostream>
#include <string>
#include <Snap.h>
#include <GraphData.hpp>

int main() {
    std::string file_path;
    char path[1024];
    DataPoint **data_set;
    int motif_size, num_null_models;

    std::cin >> file_path;
    std::cin >> motif_size;
    std::cin >> num_null_models;

    data_set = new DataPoint*[num_null_models];

    strncpy(path, file_path.c_str(), sizeof(path));
    path[sizeof(path) - 1] = 0;

    //read input
    PNGraph G = TSnap::LoadEdgeList<PNGraph>(path, 0, 1);

    //enumerate and classify k-subgraphs in G
    GD::extractData(&G, motif_size, data_set[0]);

    //generate random graphs and enumerate and calssify k-subgraphs on them
    for(int i=1; i<=num_null_models; i++) {
        //GM::randomize(&G);
        GD::extractData(&G, motif_size, data_set[i]);
    }

    //detect motifs
    GD::discoverMotifs(data_set);

    return 0;
}

我的图表数据header:

#ifndef GRAPHDATA_HPP_INCLUDED
#define GRAPHDATA_HPP_INCLUDED

#include <Snap.h>

struct DataPoint;

namespace GD {
    void extractData(PNGraph*, int, DataPoint*);
    DataPoint* discoverMotifs(DataPoint**);
}

#endif // GRAPHDATA_HPP_INCLUDED

还有我的 GraphData 文件:

#include <GraphData.hpp>

struct DataPoint {
    int label;
    int frequency = 0;
};

void GD::extractData(PNGraph* G, int k, DataPoint* data_array) {
    //stuff
}

DataPoint* GD::discoverMotifs(DataPoint** data_set) {
    //dummy code
    DataPoint* dp;
    dp = new DataPoint[2];
    dp[0].label = 10;
    dp[1].label = 99;
    return dp;
}

我正在尝试在 Ubuntu 14.10 下使用 GCC 4.9.2 进行编译,使用:

g++ -o main main.cpp /usr/include/Snap-2.3/snap-core/Snap.o -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core

但是它给我一个未定义引用的错误:

main.cpp:(.text+0x179): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1b9): undefined reference to `GD::extractData(TPt<TNGraph>*, int, DataPoint*)'
main.cpp:(.text+0x1dd): undefined reference to `GD::discoverMotifs(DataPoint**)'
collect2: error: ld returned 1 exit status

我有点迷茫,我错过了什么?

[注意:这不是问题的答案,只是解释如何一个一个地编译多个文件]

可以单独编译源文件,但是你必须告诉g++创建目标文件,这然后你们link在一起。

像这样:

$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core main.cpp
$ g++ -Wall -g -c -I../include -I/usr/include/Snap-2.3/snap-core -I/usr/include/Snap-2.3/glib-core GraphData.cpp
$ g++ -g  main.o GraphData.o /usr/include/Snap-2.3/snap-core/Snap.o -o main

选项 -Wall 告诉 g++ 启用更多警告,这很好,因为警告是您可能做错事情的迹象(例如导致 undefined behavior)。 -g 选项告诉 g++ 生成调试信息,在开发时总是很好,以防您需要 运行 调试器中的程序。最后,-c 选项告诉 g++ 生成一个 目标文件 而不是一个可执行程序。

然后你有一个单独的命令link将目标文件组合在一起形成最终的可执行文件。

如果您获得的文件不止几个,这一切都将成为一种痛苦,然后您应该了解 make,这是一个可以为您自动完成大部分工作的程序。例如,它将检查文件上的时间戳以确保您不会编译未修改的文件。

g++ 默认情况下会在编译完所有输入文件后立即调用 linker (ld)。生成二进制可执行文件的方法有很多种,但最常见的两种是:

  1. 在单个 g++ 命令中包含所有源文件。您需要将 GraphData.cppmain.cpp 添加到对 g++ 的同一调用中。 此方法确保 linker 可以访问所有符号以创建可执行文件。
  2. 将每个文件分别编译成一个目标文件,然后 link 在一个单独的步骤中将它们编译(这对于大型软件项目来说更为常见)。 为此,将每个 cpp 文件的 -c 开关传递到 g++,然后再次调用 g++ 并传递 .o 文件而不是 .cpp 文件。

无论哪种方式,在某些时候,您必须立即将所有符号提供给 linker,以便它可以创建可执行文件。

正如 Joachim 所解释的那样,除了几个文件之外的任何事情都会变得非常痛苦,此时学习构建系统可能会非常有利可图。他提到了 make,但我也会考虑研究 cmake,它具有更简单的语法并且是跨平台的。它甚至可以自动为您生成 Makefiles