I love C ++, but ...
I’ll just make a reservation that C ++ is my favorite language, I’m writing it practically “from childhood” and I will not deny its importance as the
best one of the best languages for writing programs for any purpose. Moreover, I see no reason to start a regular holivar or to be measured by “pointers”. This article is just a description of a bad experience with a language, explaining some of its aspects, the knowledge of which will help other programmers in the future.
Once I ran into a developing class GUI library. From the point of view of C ++, or rather of its classes, instances, and hierarchies, this language seems incredibly close to the GUI management concept, in particular, such elements as widgets, class windows and a sub-window. OO C ++ models and window systems are nevertheless different. C ++ was conceived as a “static” language with lexeme scope, static type checking and hierarchies defined at compile time. Windows and their objects, on the other hand, are inherently dynamic, they usually live outside the framework of a separate procedure or block through which they were created; widget hierarchies are largely determined by location, visibility and event streams. The fundamentals of a graphical user interface, such as dynamic and geometric hierarchies of windows and controls, the flow of events, are not directly supported by C ++ syntax or its semantics. Thus, these functions must be reproduced in C ++ GUI code. This leads to duplication of the graphical toolkit, or the functionality of the window manager, the code “swells up”, we have to abandon many of the “strong” features of C ++ (for example, type checking during compilation). The article provides a few simple examples of C ++ / GUI "not docking".
')
Do not create constructors (or at least do not use them)
When the resulting class overrides the virtual method of the parent class, it must be borne in mind that the override does not take effect as long as the base class constructor is executed. This is especially annoying when objects request widgets that respond to GUI events. Suppose that the
Basic_Window class was designed to create a vanilla black and white window on the screen:
class Basic_Window {
public:
Basic_Window(Rect rect) { gt_create_window(rect,visible,this); }
virtual void handle_create_event() { set_background(WHITE); }
};
Here,
gt_create_window () is responsible for the low-level invocation of the main graphical toolkit (for example,
xvt_win_create () ). This function allocates space for the toolkit data, notifies the window manager, registers this object as the event receiver, and in the example above, initializes the graphical output to the window on the screen.
Suppose we want to create an instance of
Basic_Window , but with a red background. Usually, to change the behavior of a class, you need to extract from it and override the corresponding virtual methods. We are writing:
class RedWindow : public Basic_Window {
virtual void handle_create_event() { set_background(RED); }
public:
RedWindow(Rect rect) : Basic_Window(Rect rect) {}
};
RedWindow red_window(default_rect);
But
red_window will appear white, not red! To create a
RedWindow , the parent must be created first. After completing
Basic_Window :: Basic_Window () , the
RedWindow virtual tables take effect, the
handle_create_event () method becomes inapplicable, and the
RedWindow () constructor is executed. The
Basic_Window () constructor registers a graphical tool object that instantly starts sending events to the object (for example, a CREATE event). The
Basic_Window () constructor is not yet complete (this is not guaranteed), so the overridden virtual method is not yet in place. Thus, the CREATE event will be handled by
Basic_Window :: handle_create_event () . Virtual tables of the
RedWindow class will be created only when the base class is fully built, that is, when the window is already on the screen. Changing the color of the window at this stage will lead to an annoying error.
There is a simple workaround: prevent each constructor from registering a graphical tool object. Event handling will be constructed in such a way as to keep the end of initialization to derived classes. It is very tempting to view widgets on the screen as the “face” of an application's GUI object in memory. As the example above shows, this relationship between the screen and the C ++ object is not so simple to implement: they are born separately.
No syntax switching event
Suppose that the class library includes a
PictWindow class
GUI that displays a photo in a window:
class PictWindow {
Picture picture;
public:
virtual void repaint() { gt_draw_pict(picture); }
...
};
We would like to impose a small rectangle in a certain area of the image. To this end, we can try to subclass
PictWindow :
class OvWindow : public PictWindow {
Rect rect;
virtual void repaint() { gt_draw_rect(rect); }
};
Unfortunately, when we create an instance of
OvWindow , we will only see a rectangle in an empty window, and no image. From the moment
OvWindow :: repaint () overrides
PictWindow :: repaint () , the last function will not be called when the window is to be drawn. We had to implement an
OvWindow like this:
class OvWindow : public PictWindow {
Rect rect;
virtual void repaint() { PictWindow::repaint(); gt_draw_rect(rect); }
public:
OvWindow(void) : PictWindow() {}
};
The
OvWindow constructor
is stated to emphasize that the
OvWindow :: repaint () method must be deferred to the superclass, as the constructors do. In fact, the constructor of the derived object from the beginning calls the constructor of the corresponding object. repaint () should be postponed to its parent: method in the base class that overrides it.
In summary: Poor C ++ / GUI compatibility
C ++ was developed as a “static” language:
- with tracking tokens
- static type checking
- with static class hierarchies
- without garbage collection
- with a message system without specific compile-time hierarchies
GUI objects:
- characterized by dynamic objects, and often the only of its kind
- usually live far beyond the framework in which they were created
- hierarchies are determined largely by event streams and their location, rather than class inheritance
- hierarchies are built and destroyed at run time, often in response to unpredictable user actions
C ++ is not designed to support dynamic messaging and messaging protection (except for exceptions). All this leads to duplication of graphical tools and functionality of the window manager, sprawl of code, the use of insecure functions and the rejection of many of the strengths of C ++.
findings
Of course, all these "snags" are not fatal. C ++ is a universal and powerful language and, therefore, is able to express all possible calculation algorithms. Therefore, if an application requires dynamic functions, such as those found in
Tcl / Tk ,
Scheme / Tk ,
Postscript, and the like; using C ++, you can always do something by their example. On the other hand, why not use a language where all these traits are present?