Compilation and code organization

APM 50179 EP

When we design a software

We observe that

  • The software is designed to solve a family of problems.
  • The problems solved involve several topics.
  • The same algorithm can be used in several places.
  • Some algorithms or implementations can be used in other software.

Consequences

  • Each topic must be clearly separated using directories or files.
  • The readability of a code is improved when each important part of an algorithm is divided into dedicated functions.
  • An implementation is never copied twice in a code.
  • If several algorithms can be used by another software, can they be grouped in a dedicated software?

Different user profiles

  • Beginners who just want to run some demos.
  • Intermediates who want to change some parameters and write their own experience.
  • Experts who want to add new features in your code base.

How to satisfy everyone

  • The source code must be documented.
  • Several types of documentation must be created
    • How to use with several levels of expertise.
    • How to contribute and add new features.
  • Tests are important and facilitate contributions.
  • Demos provide concrete idea of how to solve a real problem.

Just an example


std::vector<double> x(101), y(201);
double a = 0, b = 1;
double c = 0, d = 1;

for(std::size_t i = 0; i<101; ++i)
{
  double dx = (b - a)/100;
  x[i] = i*dx + a;
}

for(std::size_t i = 0; i<201; ++i)
{
  double dx = (d - c)/200;
  y[i] = i*dx + c;
}

std::vector<std::vector<double>> v(101, std::vector<double>(201));

for(std::size_t i=0; i<101; i++)
{
  for(std::size_t j=0; j<201; j++)
  {
    v[i][j] = std::exp(-100*(x[i]*x[i] + y[j]*y[j]));
  }
}
              

Improve readability and reusability


auto x = linspace(0, 1, 101);
auto y = linspace(0, 1, 201);
auto v = init_on_meshgrid(x, y, [](auto x, auto y){return std::exp(-100*(x*x + y*y));});
              

or


auto v = init_on_meshgrid(linspace(0, 1, 101), linspace(0, 1, 201),
                          [](auto x, auto y){return std::exp(-100*(x*x + y*y));});
              

linspace with loop


auto linspace(double start, double stop, std::size_t num)
{
  std::vector<double> x(num);
  double step = (stop - start)/(num-1);

  for(std::size_t i=0; i<num; ++i)
  {
    x[i] = start + step*i;
  }

  return x;
}
              

linspace with std::generate


#include <algorithm>

auto linspace(double start, double stop, std::size_t num)
{
  std::vector<double> x(num);
  double step = (stop - start)/(num-1);

  std::generate(x.begin(), x.end(), [step, i = 0]() mutable {return start + step*(i++);});

  return x;
}
              

Init on meshgrid


template<class Func>
auto init_on_meshgrid(const std::vector<double>& x, const std::vector<double>& y, Func&& f)
{
  std::vector<std::vector<double>> output(x.size(), std::vector<double>(y.size()));

  for(std::size_t i = 0; i < x.size(); ++i)
  {
    for(std::size_t j = 0; j < y.size(); ++j)
    {
      output[i][j] = f(x[i], y[j]);
    }
  }

  return output;
}
              

These two functions should be in separate files.

Then

  • What type of files do we have in C++?
  • How to combine them?
  • How to make them understandable by the computer?

Declaration

A declaration introduces a name which denotes an entity.

An entity can be

  • Function definition
  • Template declaration (including Partial template specialization)
  • Explicit template instantiation
  • Explicit template specialization
  • Namespace definition
  • Linkage specification
  • ...

Definition

A definition is a declaration that fully define the entity introduced by the declaration.

All definitions are declarations.

Not all declarations are definitions.

Examples

declarations

int f();

using array_t = std::vector<double>;
typedef integer int;
                  

extern int i;
                  

struct poly
{
  double apply(double x);
}
                  
definitions

int f()
{
  return 42;
}
                  

int i;
extern int i = 42;
                  

double poly::apply(double x)
{
  return x*x + 2*x + 1;
}
                  

Other possibilities will be seen throughout this course.

Two kinds of C++ files

  • Source files

    text files with the extensions .cpp, .cxx, .C

  • Header files

    text files with the extensions .hpp, .h

By convention, source files contain the definitions and header files contain the declarations.

Available compilers

gcc

clang

Intel compiler

gcc

clang

Xcode

Intel compiler

Visual Studio

MinGW

Intel compiler

Compile your first program using gcc

following the given instructions in the notebook

How it works ?

How it works ?

  • Create the object files

g++ -c source_1.cpp source_2.cpp
              
  • Create the object files by adding include directory

g++ -c source_1.cpp source_2.cpp -I/PATH/TO/INCLUDE/FILES
              

How it works ?

  • Link without external libraries

g++ source_1.o source_2.o -o my_app.exe
              
  • Link with external libraries

g++ source_1.o source_2.o -o my_app.exe -L/PATH/TO/LIBRARY/FILES -lmylib.a
              

One Definition Rule (ODR)

A translation unit can contain only one definition of certain entities (variable, function, class, ...).

But multiple declarations are allowed.

One Definition Rule (ODR)

A given program must contain exactly one definition for every non-inline variable or function that is used in the program.

For an inline variable or an inline function, a definition is required in every translation unit that uses it.

Exactly one definition of a class must appear in any translation unit that uses it in such a way that the class must be complete.

One Definition Rule (ODR)


int i;
                  

int i;
                  

extern int i;
                  

int i;
                  

int func(int i);
                  

int func(int i)
{
  return i*i;
}
                  

int func(int i)
{
  return i*i;
}
                  

int func(int i)
{
  return i*i;
}
                  
TU-1
TU-2

Header guard


double square(double x)
{
  return x*x;
}
                  
square.hpp

#include <cmath>
#include "square.hpp"

double gaussian(double x, double y)
{
  return std::exp(-100*(square(x) + square(y)));
}
                  
gaussian.hpp

#include "square.hpp"
#include "gaussian.hpp"

int main()
{
  double x = gaussian(1, 2);
}
                  
main.cpp

Multiple definition of square !!!

Header guard


#ifndef SQUARE_HPP
#define SQUARE_HPP

double square(double x)
{
  return x*x;
}

#endif
                  
square.hpp

#ifndef GAUSSIAN_HPP
#define GAUSSIAN_HPP

#include <cmath>
#include "square.hpp"

double gaussian(double x, double y)
{
  return std::exp(-100*(square(x) + square(y)));
}

#endif
                  
gaussian.hpp

namespace

namespace provide a method for preventing name conflicts in large project.


namespace algorithm
{
  namespace insertion
  {
    void sort(std::vector<double>& x)
    {
      ...
    }
  }
  namespace merge
  {
    void sort(std::vector<double>& x)
    {
      ...
    }
  }
}

algorithm::merge::sort(x);
              

using


namespace mg = algorithm::merge; // namespace alias
mg.sort(x);
              

using algorithm::merge::sort; // using directive
sort(x);
              

using namespace algorithm::merge; // evil !
sort(x);
              

using namespace std; // ultimate evil !!
              

static keyword


int my_var = 6;
                  
file_1.cpp

int my_var = 8;
                  
file_2.cpp

Link error: multiple definitions of my_var


static int my_var = 6;
                    
file_1.cpp

static int my_var = 8;
                    
file_2.cpp

OK: A static variable is local to a translation unit

Anonymous namespace


static int my_var = 6;
                  
file_1.cpp

static int my_var = 8;
                  
file_2.cpp

Equivalent to


namespace
{
  int my_var = 6;
}
                    
file_1.cpp

namespace
{
  int my_var = 8;
}
                    
file_2.cpp

Anonymous namespace encapsulates definitions

Static keyword


int func()
{
  int res = 0;
  return ++res;
}
              

int func()
{
  static int res = 0;
  return ++res;
}
                

Go back to the Jupyter notebook and follow the instructions

Code organization

How to compile large project

CMake example 1


project(myapp)

cmake_minimum_required(VERSION 3.15)

# create an executable
add_executable(my_app src/source.cpp include/header.hpp)
              

CMakeLists.txt

CMake build process


cmake -B build . -DCMAKE_BUILD_TYPE=Release

cmake --build build

./build/my_app
              

CMAKE_BUILD_TYPE flag can be Debug, Release or RelWithDebInfo

CMake example 2


project(myapp)

cmake_minimum_required(VERSION 3.15)

find_package(other_lib REQUIRED)

add_subdirectory(src)

# create an executable
add_executable(my_app src/my_app.cpp)
target_link_library(my_app PRIVATE my_lib other_lib)
              

CMakeLists.txt


# create a library
add_library(my_lib STATIC src/source_1.cpp)
target_include_directory(my_lib PUBLIC include)
target_compile_features(my_lib PUBLIC cxx_std_17)
              

src/CMakeLists.txt