Signals and Slots in D

Bill Baxter dnewsgroup at billbaxter.com
Fri Sep 29 00:40:09 PDT 2006


Walter Bright wrote:
> Ok, I admit I don't understand S&S. But let's try starting with Qt's 
> canonical example from http://doc.trolltech.com/3.3/signalsandslots.html:
> 
>     class Foo : public QObject
>     {
>         Q_OBJECT
>     public:
>         Foo();
>         int value() const { return val; }
>     public slots:
>         void setValue( int );
>         {
>           if ( v != val ) {
>             val = v;
>             emit valueChanged(v);
>           }
>         }
>     signals:
>         void valueChanged( int );
>     private:
>         int val;
>     };
> 
>     Foo a, b;
>     connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
>     b.setValue( 11 ); // a == undefined  b == 11
>     a.setValue( 79 ); // a == 79         b == 79
>     b.value();        // returns
> 
> It seems that I can do this in D with:
> 
>     class Foo
>     {
>         this();
>         int value() { return val; }
> 
>         void setValue( int );
>         {
>           if ( v != val ) {
>             val = v;
>             valueChanged(v);
>           }
>         }
> 
>         void valueChanged( int i )
>     {
>         foreach (dg; slots)
>         dg(i);
>     }
> 
>     void connect( void delegate(int i) dg)
>     {
>         slots ~= dg;
>     }
> 
>     private:
>     void delegate(int i)[] slots;
> 
>         int val;
>     };
> 
>     Foo a = new Foo;
>     Foo b = new Foo;
>     a.connect(&b.setValue);
>     b.setValue( 11 ); // a == undefined  b == 11
>     a.setValue( 79 ); // a == 79         b == 79
>     b.value();        // returns 79
> 
> There's no casting, it's statically typesafe. Some of the boilerplate 
> can be eliminated with a mixin. Is that all there is to it, or have I 
> completely missed the boat?

It's close, but check out the signature of trolltech's connect method:

   bool connect (
      const QObject * sender, const char * signal,
      const QObject * receiver, const char * method,
      Qt::ConnectionType type = Qt::AutoCompatConnection
   );

The key difference is that the target method is specified by a *string*.

That's the main difference between what Qt has and the S&S 
implementations people generally come up with for C++ (or D).

Every QObject subclass has a QMetaObject member.
     http://doc.trolltech.com/4.1/qmetaobject.html
QMetaObject has interesting methods like
      int indexOfMethod ( const char * method ) const
      int indexOfProperty ( const char * name ) const
      int methodCount () const
      QMetaMethod method ( int index ) const
For looking up parts of the class by name and dynamic introspection.

That's the part that requires the running of their "moc" tool, the 
Meta-Object compiler.  It scans through headers and picks out that sort 
of information.

Ok, you're probably now saying, "yeh, but that's not statically 
typesafe, and my implementation is!".  You're right, sometimes you do 
want static type-safety.  But sometimes you'd rather have loose dynamic 
coupling and runtime type-safety.

Here's where I get a little hand-wavy, but this dynamic binding is very 
useful for writing GUIs (and generally any component system that needs 
loose coupling).  QtDesigner is Trolltech's GUI builder:
    http://www.trolltech.com/products/qt/features/designer
It takes advantage of all the introspection capabilities offered by the 
QMetaObject that lives in every component.  You can point it to a gui 
widget you wrote, and it immediately can show all that widget's 
properties, signals, and signalable methods (slots), and you can add 
that widget to your GUI and start hooking methods together.

Also it means that at run-time, you can safely try to connect to slots 
that may or may not be there.  If the target doesn't have that slot, no 
harm done.  And you don't need to know anything about the object at 
compile time other than it's a QObject.  Loose coupling.

I think you can get similar results in pure C++ with a lot of templates 
plus the requirement that users call some sort of method for every 
function or property they want to have dynamically callable:

    registerSlot(foo, "foo(int,int)")

I think the CEGUI library (www.cegui.org.uk) is now using something like 
that approach.  But obviously it requires a lot less maintenance if that 
is handled for you automatically, because in C++ the place you call the 
registerSlot() method always ends up being separated from the place 
where you actual declare the foo method.  Qt's "slot:" decorator keyword 
basically lets you "register" the method at the place of declaration by 
tagging it with one word.

All this is not to say that Qt S&S is the best way.  Qt's design is 
constrained ultimately by having to work with C++.  Hence the separate 
"moc" compiler.  In the end Qt's QMetaObject provides a certain, fairly 
limited amount of dynamic functionality.  But as pointed out in the 
other thread, something like Objective-C provides a much more general 
messaging mechanism.  From that you can easily build Qt-like S&S or a 
dozen other loose coupling solutions.

I think railroading Qt's S&S into a language is the wrong approach. 
What goes into the language should be a more general mechanism on top of 
which schemes like dynamic S&S can be easily built.

--bb



More information about the Digitalmars-d mailing list