dirk/C++/glibmm/sigc++-disconnect

Weingarten, 10-02-11

sigc++-disconnect – Avoid Signals to Lost Receivers

Today, I thought about the unhandled exception occurring when I quit the application (I'm currently developing). The reason for this exception is a signal handler called from a data object for widgets which seems already be destroyed at this time. (Stack trace went across a bunch of destructors.) So, I saw it is necessary to disconnect these signals correctly to have a properly exit behaviour.

Once, I used my own concept of callback functions, the callback handlers were removed if the receiving instance was gone. This was possible due to the magic of smart pointers. It seems that the slot concept of sigc++ is too flexible for this approach – a little disadvantage of its great design.

Thus, I need a way to conviniently incorporate the connections in the class design. Thereby, I have the following intentions:

  1. Concentrate the code for disconnect to lower the danger of forgetting a signal handler.
  2. Automate disconnect. (e.g. disconnect in destructor)

My application development follows usually the MVC approach. Thus, the sample contains two classes Data (the model) and View (the view). (The surrounding application provides the controller?)

Source Code

Text Filesigc++-disconnect.cc
/** @file sample for sigc++ disconnect
 *
 * Last CVS checkin:
 * $Date: $
 * $Author: scheff $
 */

/**************************************************************************/

// standard C/C++ header:
#include <iostream>
#include <string>

// Gnome header:
#include <sigc++/sigc++.h>
#include <glibmm/init.h>

using namespace std;

/**************************************************************************/

/// provides a class representing data.
class Data {

  // signal:
  public:
    sigc::signal<void> sig;

};

/**************************************************************************/

/// provides a class representing a data view.
class View {

  // variables:
  private:
    /// pointer to data
    Data *_pData;
    /// connection
    struct Connection {
      sigc::connection sig;
      void disconnect(void)
      {
        sig.disconnect();
      }
      ~Connection(void) { disconnect(); }
    } _connection;

  // methods:
  public:
    /// @name Construction & Destruction
    //@{

    /** constructs a view for a certain data.
     *
     * @param data the data
     */
    View(Data &data);

    /// destroys view.
    virtual ~View(void);

    //@}
  protected:
    /// @name Signal Handling
    //@{

    /// called for data.
    void onData(void);

    //@}
};

/**************************************************************************/

// Construction & Destruction

View::View(Data &data): _pData(&data)
{
  _connection.sig
    = data.sig.connect(sigc::mem_fun(*this, &View::onData));
}

View::~View(void) { cout << "View::~View()" << endl; }

// Signal Handling

void View::onData(void)
{
  cout << "View::onData(): _pData = " << _pData << endl;
}

/**************************************************************************/

/** waits for user confirm.
 *
 * @param action text with next action
 */
static void pause(const char *action)
{
  cout << "Press [ENTER] to " << action << ": " << flush;
  string str; getline(cin, str);
}

/** provides the main function of application.
 *
 * @param argc number of command line arguments
 * @param argv pointer to array of pointers to strings with command line
 *        arguments
 * @return 0 ... application exited regularly\n
 *         else ... execution of application aborted
 */
int main(int argc, char *argv[])
{
  Glib::init();
  // signals
  pause("start");
  {
    Data data; // a Data instance
    { View view(data); // a view
      data.sig(); // emit a signal
    } // life time of view ends here
    data.sig(); // emit another signal
  } // life time of Data instance ends here
  // done
  pause("finish");
  return 0;
}

Output

Console
                                                                                
Press [ENTER] to start: ↵
View::onModel(): _pModel = 0012FF50
View::~View()
Press [ENTER] to finish: ↵

Conclusion

I will check and improve this design in my daily work.

The usage of destructor ~Connections is not possible if the structure shall be used in assign/copy operations. In this case, premature disconnects may occur. This is also true if the structure is part of something that might be assigned/copied. Copy and assignment may be prevented declaring the copy constructor/assignment operator private and without implementation (what I mostly do if copy/assignment is not intended). However, this shouldn't cause crashs but may lead to weird application behavior. (That means: How difficult it will be to detect and fix related errors?)

Files

Text Filesigc++-disconnect.ccC++ source code
MS Visual C++ 2008 Project Filesigc++-disconnect.vcprojMS Visual C++ 2008 project file

Last modified: Sun Feb 21 14:38:07 2010