Search code examples
c++segmentation-faultglobal-variablesgtkgtkmm

Why can I not declare a top-level GTKMM objects?


I am old to GTK+ (C) but new to GTKMM and adapted a very simple example app from some example code.

#include <gtkmm/button.h>
#include <gtkmm/window.h>
#include <gtkmm/application.h>
#include <iostream>
int main (int argc, char *argv[])
{
    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    Gtk::Window win;
    Gtk::Button m_button("Hello World");
    m_button.signal_clicked().connect([](){
        std::cout << "Hello World" << std::endl;
    });
    win.set_border_width(10);
    win.add(m_button);
    m_button.show();
    return app->run(win);
}

This works as expected, creating a window with a Hello World button that prints Hello World when clicked.

Hello World
Hello World
Hello World

I am not entirely sure how this memory is allocated in C++, but I thought using static memory as opposed to heap memory would be better, and this could potentially be achieved by moving some of the declarations to the top level. Or if I just want to access these later on from separate functions:

#include <gtkmm/button.h>
#include <gtkmm/window.h>
#include <gtkmm/application.h>
#include <iostream>
Gtk::Window win;
Gtk::Button m_button("Hello World");
int main (int argc, char *argv[])
{
    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    m_button.signal_clicked().connect([](){
        std::cout << "Hello World" << std::endl;
    });
    win.set_border_width(10);
    win.add(m_button);
    m_button.show();
    return app->run(win);
}

That does not work as expected.

Segmentation fault (core dumped)

Why does GTKMM and/or C++ not like the 'global'/top-level variables? Can someone explain what is going on behind the scenes, or how I could have a top-level object without this error?


Solution

  • The reason for the segfault is indeed what was mentioned by @Retired Ninja in the comments. You should not try to instantiate widgets before the toolkit has been initialized, through a call to Gtk::Application::create. That being said, you could use "top level" variables.

    Solution 1 (heap)

    You can use std::unique_ptr (specifically designed to own a single resource, such as a widget) to store your "top level" variables.

    For example:

    #include <gtkmm/button.h>
    #include <gtkmm/window.h>
    #include <gtkmm/application.h>
    #include <iostream>
    std::unique_ptr<Gtk::Window> win;
    std::unique_ptr<Gtk::Button> m_button;
    int main (int argc, char *argv[])
    {
        auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    
        win = std::make_unique<Gtk::Window>();
        m_button = std::make_unique<Gtk::Button>("Hello World");
    
        m_button->signal_clicked().connect([](){
            std::cout << "Hello World" << std::endl;
        });
        win->set_border_width(10);
        win->add(*m_button);
        m_button->show();
        return app->run(*win);
    }
    

    These lines:

    std::unique_ptr<Gtk::Window> win;
    std::unique_ptr<Gtk::Button> m_button;
    

    instantiate unique pointers to a window and a button, but do not instantiate the widgets themselves (the pointer value is nullptr here), which is fine because at this time, the application has not been created. These lines:

    win = std::make_unique<Gtk::Window>();
    m_button = std::make_unique<Gtk::Button>("Hello World");
    

    instantiate the widgets (on the heap) and assign them to our unique pointers. This must happen after the application has been created otherwise you will get a segfault as before.

    With this solution, notice how all syntax has turned to pointer syntax (using * to access the widgets and -> to call methods on it).

    Solution 2 (stack)

    You could use free functions that return static instances of your widgets. Wrapping the creation of the static variables in functions allows you to control the time of creation of these variables (on the stack). The use of static will make sure the widgets live for the whole lifespan of the program (if this is what you want).

    For example:

    #include <gtkmm/button.h>
    #include <gtkmm/window.h>
    #include <gtkmm/application.h>
    #include <iostream>
    
    Gtk::Window& TopLevelWindowGet()
    {
        static Gtk::Window window;
        return window;
    }
    
    Gtk::Button& TopLevelButtonGet()
    {
        static Gtk::Button button("Hello World");
        return button;
    }
    
    int main (int argc, char *argv[])
    {
        auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");
    
        Gtk::Window& win = TopLevelWindowGet();
        Gtk::Button& m_button = TopLevelButtonGet();
    
        m_button.signal_clicked().connect([](){
            std::cout << "Hello World" << std::endl;
        });
        win.set_border_width(10);
        win.add(m_button);
        m_button.show();
        return app->run(win);
    }
    

    These lines:

    Gtk::Window& TopLevelWindowGet()
    {
        static Gtk::Window window;
        return window;
    }
    
    Gtk::Button& TopLevelButtonGet()
    {
        static Gtk::Button button("Hello World");
        return button;
    }
    

    do not create the widgets. This will only happen if the functions are called. This is why these lines are added after the application has been created:

    Gtk::Window& win = TopLevelWindowGet();
    Gtk::Button& m_button = TopLevelButtonGet();
    

    Conclusion

    If I had to pick a solution, I would choose none of them. In fact, I would avoid "top level" variables (or globals as they are usually called) altogether. They can create nasty bugs, especially if you write concurrent code. With a gun to my head, however, I have a preference for the std::unique_ptr solution. Why? Because the std::unique_ptr container is designed to hold resources such as widgets in a way that heap is not a problem. Memory is handled by the smart pointer, not by you. It also gives you more control over the lifespan of the widgets. By calling std::unique_ptr::reset(), for example, you can destroy you widgets (if they are no longer needed, for example).