Uso de C++
Los servidores web están escritos en C y, si no lo están, están escritos en Java o C++, que son derivados de C, o en Python o Ruby, que se implementan en C. Rob Pike
La programación orientada a objetos (encapsulación, herencia y polimorfismo) es una herramienta muy poderosa para modelar cierto tipo de problemas. Sin embargo, en NAppGUI creemos que es incorrecto imponer una jerarquía de clases a nivel de SDK, ya que este es un nivel demasiado bajo. El SDK está más cerca del sistema operativo y de la máquina que de los problemas del mundo real resueltos por las aplicaciones, donde una estrategia orientada a objetos puede ser más acertada (o puede que no).
Aunque NAppGUI ha sido diseñado para crear aplicaciones en C "puro", es posible utilizar C++ o combinar ambos lenguajes. Daremos algunos consejos, portando nuestra aplicación ¡Hola Mundo! a C++ (Figura 1).
1. Encapsulación
NAppGUI no impone ninguna jerarquía de clases, lo que deja al programador la libertad de realizar la encapsulación mediante sus propias clases. Evidentemente, dado que C++ incluye a C, podremos llamar a cualquier función C del SDK dentro de una función miembro. Por ejemplo, podemos encapsular la ventana principal de esta manera.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Como puedes ver, con respecto a la versión C, i_panel
ya no necesita parámetros, ya que usa el puntero implícito this
para acceder a los miembros de la clase.
2. Callbacks de clase
Los manejadores de evento son funciones C cuyo primer parámetro es un puntero al objeto que recibe el mensaje. Esto funciona de la misma manera usando funciones estáticas dentro de una clase de C++:
1 2 3 4 5 |
... static void i_OnClose(MainWindow *window, Event *e); ... window_OnClose(this->window, listener(this, i_OnClose, MainWindow)); ... |
Sin embargo, es posible que queramos utilizar funciones miembro como manejadores del evento, usando el puntero this como receptor. Para hacer esto, derivamos nuestra MainWindow
de la interfaz IListener
y usamos la macro listen en lugar de listener()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MainWindow : public IListener { ... void i_OnClose(Event *e); void i_OnButton(Event *e); ... }; void MainWindow::i_OnButton(Event *e) { String *msg = str_printf("Button click (%d)\n", this->clicks); ... } ... button_OnClick(button, listen(this, MainWindow, i_OnButton)); ... |
IListener
es una interfaz C++ que permite utilizar métodos miembro de una clase como manejadores de evento.
También es posible dirigir el evento a un objeto diferente (y de diferente clase) a la dueña control. Para ello indicamos el receptor como primer parámetro de listen
, como vemos a continuación. La pulsación del botón de cierre será procesada en la clase App
y no en MainWindow
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class App : public IListener { public: App(); ~App(); void i_OnClose(Event *e); private: MainWindow *main_window; }; class MainWindow : public IListener { public: MainWindow(App *app); } MainWindow::MainWindow(App *app) { ... window_OnClose(this->window, listen(app, App, i_OnClose)); ... } void App::i_OnClose(Event *e) { osapp_finish(); } |
Podemos establecer como receptor de eventos, cualquier objeto que implemente la interfaz IListener
.
3. Combinar módulos C y C++
Un proyecto C/C++ selecciona el compilador en función de la extensión del archivo. Para *.c se utilizará el compilador C y para *.cpp el compilador C++. El mismo proyecto puede combinar módulos en ambos lenguajes si consideramos lo siguiente.
3.1. Uso de C desde C++
No hay problema si las declaraciones de funciones la cabecera de C están entre las macros: __EXTERN_C
y __END_C
.
1 2 3 4 5 6 7 |
__EXTERN_C
y__END_C
son alias paraextern "C" {}
. Esto le dice al compilador de C++ que no use name mangling con funciones en C.
3.2. Uso de C++ desde C
C no entiende la palabra clave class
y dará un error de compilación al incluir cabeceras de C++. Es necesario definir una interfaz en C sobre código C++.
1 2 3 4 5 6 7 8 9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
4. Sobrecarga de new y delete
C++ utiliza los operadores new
y delete
para crear instancias dinámicas de objetos. Podemos hacer que las reservas se realicen a través de Heap
, el gestor de Heap - Gestor de memoria que incorpora NAppGUI, con el fin de optimizar C++ y controlar los Memory Leaks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
5. Hola C++ completo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
/* NAppGUI C++ Hello World */ #include <nappgui.h> class App; class MainWindow : public IListener { public: MainWindow(App *app); ~MainWindow(); void *operator new(size_t size) { return (void*)heap_malloc((uint32_t)size, "MainWindow"); } void operator delete(void *ptr, size_t size) { heap_free((byte_t**)&ptr, (uint32_t)size, "MainWindow"); } private: void i_OnButton(Event *e); Panel *i_panel(void); Window *window; TextView *text; uint32_t clicks; }; /*---------------------------------------------------------------------------*/ class App : public IListener { public: App(); ~App(); void i_OnClose(Event *e); void *operator new(size_t size) { return (void*)heap_malloc((uint32_t)size, "App"); } void operator delete(void *ptr, size_t size) { heap_free((byte_t**)&ptr, (uint32_t)size, "App"); } private: MainWindow *main_window; }; /*---------------------------------------------------------------------------*/ void MainWindow::i_OnButton(Event *e) { String *msg = str_printf("Button click (%d)\n", this->clicks); textview_writef(this->text, tc(msg)); str_destroy(&msg); this->clicks += 1; unref(e); } /*---------------------------------------------------------------------------*/ Panel *MainWindow::i_panel(void) { Panel *panel = panel_create(); Layout *layout = layout_create(1, 3); Label *label = label_create(); Button *button = button_push(); TextView *textv = textview_create(); this->text = textv; label_text(label, "Hello!, I'm a label"); button_text(button, "Click Me!"); button_OnClick(button, IListen(this, MainWindow, i_OnButton)); layout_label(layout, label, 0, 0); layout_button(layout, button, 0, 1); layout_textview(layout, textv, 0, 2); layout_hsize(layout, 0, 250); layout_vsize(layout, 2, 100); layout_margin(layout, 5); layout_vmargin(layout, 0, 5); layout_vmargin(layout, 1, 5); panel_layout(panel, layout); return panel; } /*---------------------------------------------------------------------------*/ void App::i_OnClose(Event *e) { osapp_finish(); unref(e); } /*---------------------------------------------------------------------------*/ MainWindow::MainWindow(App *app) { Panel *panel = i_panel(); this->window = window_create(ekWINDOW_STD); this->clicks = 0; window_panel(this->window, panel); window_title(this->window, "Hello, C++!"); window_origin(this->window, v2df(500, 200)); window_OnClose(this->window, IListen(app, App, i_OnClose)); window_show(this->window); } /*---------------------------------------------------------------------------*/ MainWindow::~MainWindow() { window_destroy(&this->window); } /*---------------------------------------------------------------------------*/ App::App(void) { this->main_window = new MainWindow(this); } /*---------------------------------------------------------------------------*/ App::~App() { delete this->main_window; } /*---------------------------------------------------------------------------*/ static App *i_create(void) { return new App(); } /*---------------------------------------------------------------------------*/ static void i_destroy(App **app) { delete *app; *app = NULL; } /*---------------------------------------------------------------------------*/ #include "osmain.h" osmain(i_create, i_destroy, "", App) |
6. Plantillas matemáticas
En NAppGUI existen dos versiones para todas las funciones y tipos matemáticos (Listado 2): float (real32_t)
y double (real64_t)
. Podemos utilizar unos u otros según convenga en cada caso.
bmath.h
(parcial).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* Math funcions */ #include "osbs.hxx" __EXTERN_C real32_t bmath_cosf(const real32_t angle); real64_t bmath_cosd(const real64_t angle); real32_t bmath_sinf(const real32_t angle); real64_t bmath_sind(const real64_t angle); extern const real32_t kBMATH_PIf; extern const real64_t kBMATH_PId; extern const real32_t kBMATH_SQRT2f; extern const real64_t kBMATH_SQRT2d; __END_C |
Todas las funciones y tipos en simple precisión acaban con el sufijo "f" y las de doble precisión en "d".
Cuando implementamos funciones geométricas o algebraicas más complejas, no es fácil tener claro de antemano cual es la precisión correcta. Ante la duda siempre podemos optar por utilizar double
, pero esto tendrá un impacto en el rendimiento, sobre todo por el uso del ancho de banda de memoria. Pensemos en el caso de mallas 3D con miles de vértices. Sería estupendo disponer de ambas versiones y poder utilizar una u otra según cada caso concreto.
Por desgracia el lenguaje C "puro" no permite programar con tipos genéricos, al margen de utilizar horribles e interminables macros. Deberemos implementar ambas versiones (float
y double
), con el coste de mantenimiento asociado. C++ resuelve el problema gracias a las plantillas (template<>
). La contrapartida es que, normalmente, debemos "abrir" la implementación e incluirla en la cabecera .h
, ya que el compilador no sabe generar el código máquina hasta que la plantilla se instancia con un tipo de dato concreto. Esto choca de frente con nuestros Estándares, sobre todo en la parte relativa a la encapsulación de información. A continuación veremos como utilizar las templates
de C++ para obtener lo mejor de ambos casos: Programación genérica, ocultar implementaciones y mantener las cabeceras "limpias".
Al igual que existe una cabecera *.h
para cada módulo matemático, existe una contrapartida *.hpp
utilizable únicamente desde módulos C++ (Listado 3).
bmath.hpp
(parcial).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Estas plantillas contienen punteros a función, cuyas implementaciónes están ocultas en bmath.cpp
. En (Listado 4) tenemos un ejemplo de uso.
1 2 3 4 5 6 7 8 9 10 11 12 |
Este algoritmo está implementado dentro de un módulo C++ (Listado 5), pero queremos poder llamarlo desde otros módulos, tanto C como C++. Para ello definiremos los dos tipos de cabecera: *.h
(Listado 6) y *.hpp
(Listado 7).
mymath.cpp
. Implementación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "mymath.h" #include "mymath.hpp" #include "bmath.hpp" template<typename real> static void i_circle(const real r, const uint32_t n, V2D<real> *v) { real a = 0, s = (2 * BMath<real>::kPI) / (real)n; for (uint32_t i = 0; i < n; ++i, a += s) { v[i].x = r * BMath<real>::cos(a); v[i].y = r * BMath<real>::sin(a); } } void mymath_circlef(const real32_t r, const uint32_t n, V2Df *v) { i_circle<real32_t>(r, n, (V2D<real32_t>*)v); } void mymath_circled(const real64_t r, const uint64_t n, V2Dd *v) { i_circle<real64_t>(r, n, (V2D<real64_t>*)v); } template<> void(*MyMath<real32_t>::circle)(const real32_t, const uint32_t, V2D<real32_t>*) = i_circle<real32_t>; template<> void(*MyMath<real64_t>::circle)(const real64_t, const uint32_t, V2D<real64_t>*) = i_circle<real64_t>; |
mymath.h
. Cabecera C.
1 2 3 4 5 6 7 8 9 |
mymath.hpp
. Cabecera C++.
1 2 3 4 5 6 7 |
Ahora podemos utilizar nuestra librería matemática en C y C++, tanto en float
como en double
precisión (Listado 8).
mymaths
en algoritmos genéricos C++.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "mymath.hpp" #include "t2d.hpp" template<typename real> static void i_ellipse(const real r1, const real r2, const uint32_t n, V2D<real> *v) { T2D<real> transform; T2D<real>::scale(&transform, r1, r2); MyMath<real>::circle(1, n, v); for (uint32_t i = 0; i < n; ++i) T2D<real>::vmult(&transform, &v[i]); } |