Rodrigo Oliveira
Rodrigo Oliveira

Reputation: 1582

Making and compiling C++ projects with multiple files

I am new to C/C++, I already know the basics and I am starting to learn more advanced concepts now.

Currently I am developing a project using C++ and it is quite a big one, so structuring my project will be better.

From what I have already seen, a good structure relies on, at least, to folders: /src and /include. All .cpp files should go in /src folder and .hpp ones in /include. Here the first doubt arose: how to include a header which is not in the same directory nor in a standard one? After searching about it I concluded that the best way is to pass the option -I../include to the compiler.

I had three source files: main.cpp, GraphData.cpp and RandomGraphGenerator.cpp. I have read that it is best practice to have a header for each of these files, to separate declaration from definition. Now I also have two headers, but I am not getting how to compile all of these.

I am focusing making GraphData.cpp to be linked correctly to main.cpp, I commented everything that needs RandomGraphGenerator.cpp. This is my main file:

// 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;
}

My GraphData 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

And my GraphData file:

#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;
}

I am trying to compile, under Ubuntu 14.10 with GCC 4.9.2, using:

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

But it gives me an error of undefined referece:

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

I am a little bit lost with it, what am I missing?

Upvotes: 3

Views: 15233

Answers (2)

John Drouhard
John Drouhard

Reputation: 1259

g++ by default invokes the linker (ld) as soon as it has finished compiling all the input files. There are many ways to produce the binary executable, but the two most common are:

  1. Include all source files in a single g++ command. You would need to add GraphData.cpp and main.cpp to the same call to g++. This method ensures the linker has access to all the symbols to create the executable.
  2. Compile each file into an object file separately and link them in a separate step (this is more common for large software projects). To do this, pass the -c switch to g++ for each cpp file, then call g++ again and pass the .o files instead of the .cpp files.

Either way, at some point, you have to give all the symbols to the linker at once so it can create the executable.

As Joachim explained, anything beyond a couple files becomes a huge pain, at which point learning a build system can be quite profitable. He mentions make, but I would also consider looking into cmake which has simpler syntax and is cross platform. It can even generate Makefiles for you automatically.

Upvotes: 5

Some programmer dude
Some programmer dude

Reputation: 409472

[Note: This isn't an answer to the question, it's just an explanation on how to compile multiple files one by one]

You can compile the source files separately, but then you have to tell g++ to create object files, which you then link together.

Something like this:

$ 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

The option -Wall tells g++ to enable more warnings, this is good because warnings are signs of something that you might be doing wrong (like causing undefined behavior). The -g options tells g++ to generate debug information, always good to have while developing in case you need to run the program in a debugger. Finally the -c option is what tells g++ to generate an object file instead of an executable program.

Then you have a separate command that links the object files together to form the final executable.

If you get more than a couple of files this all becomes kind of a pain in the backside, and then you should learn about make which is a program which can automate much of this for you. It will, for example, check time-stamps on the files to make sure you don't compile unmodified files.

Upvotes: 1

Related Questions