Why C ++ is not suitable for writing graphical user interfaces

    I love C ++, but ...



    I must say right away that C ++ is my favorite language, I write on 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 another holivar or to be measured by “pointers”. This article is just a description of a bad experience with the language, explaining some of its aspects, the knowledge of which will help other programmers in the future.

    One day I came across a developing class GUI library. From the point of view of C ++, or rather its classes, instances, and hierarchies, this language seems incredibly close to the concept of GUI control, in particular, elements such as widgets, class windows, and subwindows. The OO models of C ++ and the window system are nonetheless different. C ++ was conceived as a “static” language with token coverage, static checking of types and hierarchies defined at compile time. Windows and their objects, on the other hand, are dynamic in nature, they usually live outside the framework of a separate procedure or block with which they were created; widget hierarchies are largely determined by location, visibility, and event flows. The basics of the graphical user interface, such as dynamic and geometric hierarchies of windows and controls, the flow of events, not supported directly by C ++ syntax or its semantics. Therefore, these functions must be reproduced in the C ++ GUI code. This leads to duplication of graphical tools, or window manager functionality, the code is "bloated", we are forced to abandon many of the "strong" features of C ++ (for example, type checking at compile time). This article provides some simple examples of C ++ / GUI "no 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 while the constructor of the base class 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: Here gt_create_window () is responsible for the low-level call of the main graphical tools (for example, xvt_win_create () ). This function allocates space for toolkit data, notifies the window manager, registers this object as an event recipient, and in the example above, initializes graphical output to a 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); }
    };



    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 write: But red_window will appear white, not red! To create a RedWindow , the parent must be created first. After the completion of Basic_Window :: Basic_Window () , the RedWindow virtual tables take effect, the handle_create_event () method becomes inapplicable, and the RedWindow () constructor is executed. Constructor Basic_Window ()

    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);


    registers an object of graphic instrumentation, which instantly starts sending events to the object (for example, a CREATE event). The constructor of Basic_Window () is not finished yet (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: to prohibit each designer from registering a graphic toolkit object. Event handling will be built in such a way as to restrain the end of initialization to derived classes. It is very tempting to consider widgets on the screen as the “face” of an application’s GUI object in memory. As the example above shows, this connection between the screen and the C ++ object is not so simple to implement: they are born separately.

    No event syntax



    Suppose a class library includes a pictwindow class GUI that displays a photo in a window: We would like to overlay a small rectangle in a specific area of ​​the image. To this end, we can try to subclass PictWindow : 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 should have implemented OvWindow like this: OvWindow Constructor

    class PictWindow {
    Picture picture;
    public:
    virtual void repaint() { gt_draw_pict(picture); }
    ...
    };




    class OvWindow : public PictWindow {
    Rect rect;
    virtual void repaint() { gt_draw_rect(rect); }
    };




    class OvWindow : public PictWindow {
    Rect rect;
    virtual void repaint() { PictWindow::repaint(); gt_draw_rect(rect); }
    public:
    OvWindow(void) : PictWindow() {}
    };


    set out to emphasize that the OvWindow :: repaint () method should be deferred to the superclass, as the constructors do. In fact, the constructor of the derived object calls the constructor of the corresponding object from the beginning. repaint () should defer its parent: a method in the base class that overrides it.

    Bottom line: Poor C ++ / GUI compatibility



    C ++ was developed as a “static” language:
    • token tracking
    • static type checking
    • with static class hierarchies
    • without garbage collection
    • with a message system without specific compile time hierarchies


    GUI objects:
    • dynamic objects are characteristic, and often one of a kind
    • tend to live far beyond the framework in which they were created
    • hierarchies are largely determined by the flow of events and their location, rather than class inheritance
    • hierarchies are built and destroyed at runtime, often in response to unpredictable user actions


    C ++ is not intended to support the dynamic protection of messaging and transmissions (except for exceptions). All this leads to duplication of graphical tools and window manager functionality, code overgrowth, the use of unsafe functions and the rejection of many of the strengths of C ++.

    conclusions



    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 the application requires dynamic functions, such as those in Tcl / Tk , Scheme / Tk , Postscript and the like; using C ++, you can always do something according to their example. On the other hand, why not use a language where all these traits are present?

    Also popular now: