Ejemplo sencillo de un Makefile

English version of this post.
Éste artículo intenta explicar como crear un archivo makefile sencillo para compilar una class que imprimirá el texto “hello world” a la consola.

Primero, empezamos definiendo un archivo principal que contendrá la función main y la lógica para empezar nuestro programa, a continuación se muestra Main.cpp

Main.cpp

#include "Hello.hpp"
 
int main() {
    phoxonics::samples::Hello hello;
    hello.say_hello_world();
    return 0;
}

La primera línea incluye el header Hello.hpp que será usado para crear nuestra instancia de objeto, ésta instancia será usada para acceder a nuestra lógica encapsulada de objeto, luego se tiene la función main la cual se encarga de la creación del objeto y después de eso se tiene la llamada a la función que despliega nuestro mensaje “hello world”, vamos a pasar a la definición del objeto, a continuación se muestra código para “Hello.hpp”.

Hello.hpp

#ifndef HELLO_HPP_
#define HELLO_HPP_
 
#include <string>
#include <iostream>
 
namespace phoxonics {
namespace samples {
 
class Hello {
public:
    Hello(); // constructor
    virtual ~Hello(); // destructor
 
    void say_hello_world();
     
    std::string hello();
    std::string world();
 
    void hello(std::string message);
    void world(std::string message);
 
private:
    std::string hello_ { "" };
    std::string world_ { "" };
};
 
}
}
 
#endif

Las primeras 2 líneas de código son code guards, que previenen que haya múltiples inclusiones de los header files, después tenemos las directivas include que definen las librerías que deseamos incluir para usarlas en nuestro programa, usualmente estos includes pueden referirse a clases en la librería STL , A continuación tenemos la definición del namespace que es un contenedor que previene colisiones de nombres entre funciones de diferentes librerías, después de eso se tiene el constructor y el destructor de nuestra clase que son los responsables de la inicialización de nuestro objeto y la destrucción del mismo,
recuerda que estas funciones son solamente definiciones de clase, eso quiere decir que no hay código en la definición y el código estará incluido en la implementación de la clase que es el archivo con extensión “.cpp” que corresponde a este header, las siguientes líneas de código se refieren a las funciones públicas que son accesibles desde cualquier lugar, también se tienen funciones miembro que son responsables de accesar los datos internos de un objeto, recuerda que los datos están encapsulados en el mismo objeto por lo que éstas funciones son necesarias para el acceso a éstos datos, y al final de la clase tenemos la sección privada de la clase que contiene todos los datos privados que la clase usará internamente para ejecutar una acción, a ésto se le llama miembros de clase o propiedades y tienen un tipo relacionado a cada una, vamos a pasar a la implementación, aquí se muestra el código para “Hello.cpp”:

Hello.cpp

#include "Hello.hpp"
 
namespace phoxonics {
namespace samples {
 
// constructor
Hello::Hello() {
    this->hello("hello");
    this->world("world");
}
 
// destructor
Hello::~Hello() { }
 
void Hello::say_hello_world() {
    std::cout << this->hello() << " " << this->world() << std::endl; 
}
 
std::string Hello::hello() {
    return this->hello_; 
}
 
std::string Hello::world() {
    return this->world_;
}
 
void Hello::hello(std::string message) {
    this->hello_ = message; 
}
 
void Hello::world(std::string message) {
    this->world_ = message; 
}
 
}
}

La primera línea define el header para esta clase, donde todas las funciones y miembros están definidos, en caso de que necesitemos agregar una nueva función, ésta función necesita estar definida primeramente en el archivo de header para luego ser implementada en este archivo, a continuación se tiene el namespace que es necesario para encapsular los nombres de las funciones y clases para evitar colisiones con otras librerías, a continuación se tienen las funciones de implementación de nuestro constructor y destructor, en el constructor estamos usando nuestras funciones miembro para inicializar el estado de nuestro objeto, eso es, establecemos nuestra variable miembro hello_ y world_ a los valores strings “hello” y “world” respectivamente mediante nuestra función pública hello(str) y world(str), nótese que también se debe hacer uso de la palabra reservada this que nos permite acceder a la propia instancia del objeto y a todas las funciones y propiedades del mismo, a continuación tenemos la implementación para la función say_hello_world(), el único propósito de ésta función es el de desplegar los datos contenidos en nuestras variables privadas mediante las funciones públicas de nuestro objeto, y al final de la implementación tenemos las funciones miembro, éstas funciones sirven como compuerta para acceder a los datos de las variables privadas. Ahora procedemos con nuestro makefile y la compilación de éste código, a continuación se muestra el código del makefile:

makefile

all: hello
 
hello: main.o Hello.o
    g++ -Wall main.o Hello.o -o hello -std=c++0x
 
main.o: main.cpp
    g++ -Wall -c main.cpp -std=c++0x
 
hello.o: Hello.cpp Hello.hpp
    g++ -Wall -c Hello.cpp -std=c++0x
 
clean:
    rm -f *.o hello
     
run:
    ./hello

El estándar makefile es una forma limpia de especificar como el programa se compilará y todos los pasos para pasar nuestro código fuente a binarios ejecutables, hay un sin fin de posibilidades que pueden ser usadas para definir éstos pasos y empaquetar nuestro código en éstos estándares, éste es un ejemplo mínimo solo para mostrar como funciona, al inicio del archivo tenemos “all:” a ésto se le llama target y es el target default que se llama cuando ejecutamos el comando $ make en un directorio que contenga un makefile, si queremos ejecutar la lógica para otros targets como por ejemplo “clean:” entonces lo que se necesita hacer es ejecutar el comando $ make clean, y esa es la manera en que los targets funcionan. El target “all:” define que necesitamos 2 objetos que nuestro programa utilizará para correr, el primero es “main.o” y es el archivo principal que contiene la lógica inicial, y el segundo que es “Hello.o” que es la definición de la clase que es utilizada por el programa principal para desplegar el mensaje a la consola, estos objetos son también targets que definen como construir el objeto a partir del código, el primer target “main.o” define que estamos compilando Main.cpp, define el compilador a utilizar, los archivos de código fuente y las banderas a utilizar en la compilación, luego se tiene el otro target llamado “Hello.o” este target define que necesita 2 archivos de código para producir el objeto “Hello.o” que son el header de clase y la clase propia, también define el compilador a utilizar, los archivos de código y las banderas de compilación, al final tenemos el target clean y run que son utilizados para remover antiguos binarios generados y para correr el programa respectivamente, para compilar el programa necesitamos ejecutar esto en la consola:

$ make

No olvides que el target default para el comando antes mencionado es “all:”, una vez compilado procedemos a correr el target run con el siguiente comando

$ make run

Esto correrá el programa generado y veremos un mensaje “hello world” en la consola, Finalmente corremos el target clean

$ make clean

Puedes descargar el código fuente aquí..

¡Saludos! 🙂
-Yohan

Leave a Reply

Your email address will not be published. Required fields are marked *