Use of C++
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.
Web servers are written in C, and if they're not, they're written in Java or C++, which are C derivatives, or Python or Ruby, which are implemented in C. Rob Pike
Object-oriented programming (encapsulation, inheritance and polymorphism) is a very powerful tool for modeling certain kinds of problems. However, at NAppGUI we believe that it is wrong to impose a class hierarchy at the SDK level, as this is too low a level. The SDK is closer to the operating system and the machine than to the real-world problems solved by applications, where an object-oriented approach may (or may not) be more successful.
Although NAppGUI has been designed to create applications in "pure" C, it is possible to use C++ or mix both languages. We'll give some advice, porting our Hello World! application to C++ (Figure 1).
1. Encapsulation
NAppGUI does not enforce any class hierarchy, leaving the programmer the freedom to encapsulate using their own classes. Of course, since C++ includes C, we can call any SDK C function inside a member function. For example, we can encapsulate the main window like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
As you can see, relative to the C version, i_panel
no longer needs parameters, as it uses the implicit this
pointer to access class members.
2. Class callbacks
Event handlers are C functions whose first parameter is a pointer to the object that receives the message. This works the same way using static functions within a C++ class:
1 2 3 4 5 |
... static void i_OnClose(MainWindow *window, Event *e); ... window_OnClose(this->window, listener(this, i_OnClose, MainWindow)); ... |
However, we may want to use member functions as event handlers, using the this pointer as the receiver. To do this, we derive our MainWindow
from the IListener
interface and use the listen macro instead of 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
is a C++ interface that allows you to use class member methods as event handlers.
It is also possible to direct the event to a different object (and of a different class) than the control owner. To do this, we indicate the receiver as the first parameter of listen
, as we see below. The click of the close button will be processed in the App
class and not in 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(); } |
We can establish as event receiver, any object that implements the IListener
interface.
3. Combine C and C++ modules
A C/C++ project selects the compiler based on the file extension. For *.c the C compiler will be used and for *.cpp the C++ compiler. The same project can combine modules in both languages if we consider the following.
3.1. Using C from C++
There is no problem if the C header function declarations are between the macros: __EXTERN_C
and __END_C
.
1 2 3 4 5 6 7 |
__EXTERN_C
and__END_C
are aliases forextern "C" {}
. This tells the C++ compiler not to use name mangling with C functions.
3.2. Using C++ from C
C does not understand the class
keyword and will give a compile error when including C++ headers. It is necessary to define an interface in C over C++ code.
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. new and delete overload
C++ uses the new
and delete
operators to create dynamic instances of objects. We can make reservations through Heap
, the Heap manager that NAppGUI incorporates, in order to optimize C++ and control Memory Leaks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
5. Hello C++ complete
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. Math templates
In NAppGUI there are two versions for all (Listing 2) functions and math types: float (real32_t)
and double (real64_t)
. We can use one or the other as appropriate in each case.
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 |
All single-precision functions and types end with the suffix "f" and double-precision types end with "d".
When we implement more complex geometric or algebraic functions, it is not easy to be clear in advance what the correct precision is. When in doubt, we can always choose to use double
, but this will have an impact on performance, especially due to the use of memory bandwidth. Consider the case of 3D meshes with thousands of vertices. It would be great to have both versions and be able to use one or the other according to each specific case.
Unfortunately the "pure" C language does not allow programming with generic types, apart from using horrible and endless macros. We will have to implement both versions (float
and double
), with the associated maintenance cost. C++ solves the problem thanks to templates (template<>
). The downside is that, normally, we must "open" the implementation and include it in the .h
header, since the compiler does not know how to generate the machine code until the template is instantiated with a specific data type. . This is in direct conflict with our Standards, especially in the part related to information encapsulation. Next we will see how to use C++ templates
to get the best of both cases: Generic programming, hiding implementations and keeping headers "clean".
Just as there is a *.h
header for every math module, there is a counterpart *.hpp
usable only from C++ (Listing 3) modules.
bmath.hpp
(partial).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
These templates contain pointers to functions, whose implementations are hidden in bmath.cpp
. In (Listing 4) we have an example of use.
1 2 3 4 5 6 7 8 9 10 11 12 |
This algorithm is implemented within a C++ module (Listing 5), but we want to be able to call it from other modules, both C and C++. To do this we will define the two types of headers: *.h
(Listing 6) and *.hpp
(Listing 7).
mymath.cpp
. Implementation.
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 |
Now we can use our math library in C and C++, both in float
and double
precision (Listing 8).
mymaths
in generic C++ algorithms.
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]); } |