Using 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 type of problems. However, in NAppGUI think it is wrong to impose a hierarchy of classes at the SDK level, since it is too low level. The SDK is closer to the operating system and the machine than to real-world problems solved by applications, where an object-oriented strategy can be more successful.
Although NAppGUI has been designed to create "pure" C applications, it is possible to use C++ or combine both. We'll give some tips, porting our Hello, World! application to C++ (Figure 1).


1. Encapsulation
NAppGUI does not impose any class hierarchy, which leaves the programmer the freedom to perform the encapsulation through their own classes. Obviously, given C is included in C++, we can call any SDK C function within a class function member. For example, we can encapsulate the main window in this way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
As you can see, with respect to C version, i_panel
no longer need a parameter since they use the implicit this
pointer to access the class members.
2. Class Callbacks
The event handlers are C functions whose first parameter is a pointer to the object receiving the message. This works in 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 callbacks, using implicit this pointer as the receiver. To do this, we force our MainWindow
to implement the IListener
interface and use the macro listen 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 to use class members as event handlers.
It is also possible send the event to a different object (and of different class) to the control owner. We have to indicate the receiver as the first parameter of listen
, as we see below. Pressing the close button will be processed in 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 an 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 will use the C compiler and for *.cpp the C++ compiler. The same project can combine modules in both languages if we consider the following.
3.1. Use C from C++
No problem if function declarations in the C header are between the macros: __EXTERN_C
and __END_C
.
1 2 3 4 5 6 7 |
__EXTERN_C
and__END_C
are alias forextern "C" {}
. This tells to C++ compiler not to use name mangling with C Functions.
3.2. Use C++ from C
C does not understand the keyword class
and will give a compiler error when including C++ headers. It is necessary to define a C wrapper 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 object instances. We can make allocations using Heap
, the NAppGUI built-in Heap - Memory manager manager, in order to optimize C++ and control the Memory Leaks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
5. The whole Hello C++
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(ekWNSTD); 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 mathematical types and functions (Listing 2): float (real32_t)
and double (real64_t)
. We can use one or other as appropriate in each case.
bmath.h
header (partial).
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 functions and types in simple precision end with the suffix "f" and double precision in "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 by 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 must implement both versions (float
and double
), with the associated maintenance cost. C++ solves the problem thanks to the templates (template<>
). The counterpart is that, normally, we must "open" the implementation and include it in the header .h
, since the compiler doesn't know how to generate the machine code until the template is instantiated with an specific data type. This collides head-on with our Standards, especially in the information hidding. Next we will see how to use the C++ templates
to get the best of both cases: Generic programming, hidden implementations and keepping "clean" headers.
Like there is a *.h
header for each mathematical module, also exist a *.hpp
counterpart only usable from C++ modules (Listing 3).
bmath.hpp
header (partial).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
These templates contain function pointers, 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++. For this we will define the two 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
. C Header.
1 2 3 4 5 6 7 8 9 |
mymath.hpp
. C++ Header.
1 2 3 4 5 6 7 |
Now we can use our math library in C and C++, both in float
like in 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]); } |