-
Notifications
You must be signed in to change notification settings - Fork 1
C y CPP
Estos veteranos lenguajes son los empleados en prácticamente todas las piezas clave de Linux aunque entrañan muchas dificultades. Complejos a la par de necesarios, hemos decidido crear una guía de buenas prácticas para que los proyectos que acometamos con estos lenguajes lleguen a buen puerto.
Es importante tener claro que se trata de dos lenguajes distintos, con compiladores distintos y distintas librerías. Es común creer que C++ es C con objetos, la realidad es que C++ implementa un subconjunto de C y es en gran medida compatible con este, pero nunca al 100%. Además si se trata de un entorno donde solo se emplea C, estaríamos arrastrando dependencias de C++ de forma innecesaria.
Por ejemplo, si estamos haciendo un plugin de PAM, estamos hablando de C, con lo que compilamos con GCC y usamos sus librerías.
#include <stdio.h>
int main(int argc,char* argv[])
{
printf("Hello world\n");
return 0;
}
En C++ tendríamos algo como esto:
#include <iostream>
int main(int argc,char* argv[])
{
std::cout<<"Hello world"<<std::endl;
return 0;
}
Como se ha comentado antes, desde C++ se puede hacer uso de la librería ANSI C, pero intentaremos hacer las cosas usando las funciones de la librería estándar de C++ en la medida de lo posible. Generalmente son mas fáciles de usar y manejan mejor los errores, algo especialmente cierto en el tratamiento de cadenas.
Tomamos como base de que estamos haciendo una distribución de Linux y salvo requisitos puntuales, las aplicaciones no necesitan ser portables a otros sistemas operativos, con lo cual, siempre vamos a contar con estas librerías:
- ANSI C (glibc)
- Estándar C++ (libstdc++)
- POSIX
- Linux
Gran parte de la funcionalidad específica de Linux se ofrece mediante la interfaz de /dev, /proc o /sysfs. Como se ha mencionado antes asumamos sin miedo que va a estar ahí siempre y hagamos uso del potencial que Linux ofrece.
En la medida de lo posible evitaremos arrastrar dependencias. Las dependencias de terceros pueden traer problemas, por ejemplo: Licencias incompatibles, cambios de API, paradigmas diferentes, complejidad en la construcción. Usaremos una librería de terceros cuando exista un buen motivo, como puede ser el caso de xmlrpc o Qt. Para una simple función es mucho mas práctico hacerla nuestra que arrastrar toda la librería, incluso un copia-pega respetando la autoría. De esta forma se reducen las dependencias, se agiliza la compilación y la depuración. No obstante es una linea gris.
Empleamos Qt para las aplicaciones gráficas ya que hemos adoptado Plasma como escritorio, no usamos Gtk mas que para casos concretos.
Qt está separada en diversos módulos, uno de ellos es QtCore. Proporciona la clase QObject a partir de la cual se cimienta todo Qt y prácticamente todo un framework completo con contenedores, señales, hilos... Antaño tenía su razón de ser, pero hoy en día con C++17 se puede prescindir en gran medida. Es una librería muy grande, con un estilo desfasado que requiere de mecanismos de pre-procesado de cabeceras que a veces dan problemas, además de complicar la construcción del proyecto.
En aplicaciones de consola y servicios desaconsejamos su uso en la medida de lo posible.
Aunque no está muy bien documentada, provee algunas buenas utilidades por encima de Qt. Por ejemplo, ayuda a crear aplicaciones que encajan con las lineas visuales de Plasma o a usar las traducciones de gettext.
Esta es una pequeña librería que estamos desarrollando que pretender cubrir algunas de las necesidades que no cubren las librerías estándar de C++ a la vez que facilita el acceso a algunas interfaces de Linux como puede ser el caso de /proc.
Este base toolkit no depende de terceras librerías con el fin de tener una huella pequeña, tal y como se ha mencionado con anterioridad. Es especialmente útil en aplicaciones no gráficas.
Hagamos un uso correcto de los file-descriptors estándar. La entrada (si la hay) la hacemos por stdin, por stdout sólo aquellas datos de salida que tengan algún valor, y los mensajes de depuración los restringimos a stderr. Esto afecta especialmente al print de python y al printf de C. En C++ podemos usar cerr o clog para mostrar mensajes en stderr y no usar cout a discreción.
Sería interesante ceñirnos a la convención de sintaxis de argumentos propuesta por GNU, que no es más que una extensión a la propuesta de POSIX.
Si bien, no es fácil hacerla cumplir en su totalidad si que es interesante que la leamos y la tengamos en mente cuando diseñemos la lectura de argumentos. Para C tenemos disponible las funciones getopt y argp_parse con el fin de facilitar la correcta implementación. En el toolkit de Edupals hay disponible también una clase para facilitar una correcta implementación de argumentos de entrada.
A efectos prácticos el mas conveniente es GCC, aunque CLang progresa positivamente, a día de hoy no es posible compilar el 100% de los paquetes con CLang, especialmente el kernel. Aunque es un compilador rápido y muy elegante, el código que genera rivaliza en rendimiento con GCC.
Existen distintas revisiones de ambos lenguajes, C89, C99 y C11 para el caso de C, y C++98, C++03, C++11, C++14, C++17 para C++. Salvo requerimientos concretos de algún proyecto por cuestiones de retrocompatibilidad con librerías de terceros, recomiendo basarse en C11 y C++11 como mínimo. Actualmente KDE ya está basándose en C++17.
Usemos siempre una herramienta de construcción evitando caer en la tentación de hacer scripts que ejecutan gcc. Como siempre, puede depender de la situación pero el sistema de construcción que deberemos emplear es CMake, por lo motivos a continuación:
- Es el empleado por Qt/KDE en detrimento de QMake.
- Es un sistema veterano y con generosa documentación.
- A pequeña escala es fácil.
Según el caso, se puede usar un Makefile, advierto a los aventureros de que no son fáciles. Meson es interesante, usa un lenguaje mas sofisticado que CMake y crea compilaciones mas rápidas, muchos proyectos importantes están migrando de Autoconf de Meson, no obstante no lo recomiendo en nuestro proyecto por los siguientes motivos:
- No es actualmente muy Qt-friendly
- La API no es estable
- La documentación es mejorable
En definitiva, necesita madurar.
Evitamos usar Autoconf, pero seguro que vuestro propio instinto ya os hará huir de ellas.
Que cada cual use lo que quiera, pero todo proyecto debe de ser posible su construcción desde la consola, generalmente con CMake esto no será un problema. Lo importante es que el IDE nunca sea un requerimiento.
La herramienta de depuración es esencialmente GDB, existen diversos frontends para hacer su uso algo mas fácil. Las aplicaciones se despliegan sin símbolos de depuración, en el caso de que se quiera depurar una aplicación desplegada, es posible indicarle a GDB un directorio con los símbolos de depuración.
GDB es duro, pero se aprende, evitemos caer en la tentación de los printf masivos que luego se olvidan en producción.
Aunque en C si que existe un estilo de facto bastante extendido, en C++ nos encontramos con todas las vertientes: PascalCase, camelCase y snake_case. Y a esto le sumamos sangría y metodologías.
De forma resumida, la principal recomendación es consistencia, que al menos dentro de una misma clase todos los métodos y variables usen la misma convención de nombres. La segunda recomendación, como extensión de la primera, es que nos adaptemos al contexto, si por ejemplo vamos a hacer un plugin de Qt, usemos su convención de estilo.
Ante la duda, estas son a grandes rasgos las reglas en las que se basa Edupals:
- Usamos Inglés, siempre, nombres, variables y comentarios
- Sangrado con 4 espacios
- Incluimos en cada fichero la cabecera de la licencia: GPL, LGPL, ...
- Si es una API, usamos comentarios compatibles con Doxygen
- No pongamos dos o mas lineas en blanco, con una sobra
- Los espacios de nombres en minúsculas
- Las nombres de clases en PascalCase
- Los métodos y variables en snake_case