signals and slots vs. virtual_hook (was [PATCH] KFileDialog overwrite confirmation)

Rafael Fernández López ereslibre at kde.org
Tue Jul 15 02:00:31 BST 2008


Hi there,

First of all, I don't want this thread to become an open war. I also wouldn't 
like this thread to become the typical endless thread from which we will get 0 
advantage from it.

From the original thread you can see the discussion was moving towards on how 
to preserve the binary compatibility. And what methods we have for that. We 
actually have some of them, but since our libraries are becoming bigger and 
bigger each day, I thought we could talk on how to stablish a canonical way of 
making them extendable without breaking binary compatibility.

Imagine we are writing a library, and we need a new method "myVirtualMethod" 
be added without breaking binary compatibility.

I will revisit 3 techniques, but only 2 are really doable in all cases, so 
let's go on:

1)
Share a protected d pointer for all subclasses.
Approach: Define a protected d pointer on the base class. All subclasses that 
need it, will make them ::Private class inherit this one, and rewrite the 
needed methods.
Example:

File: a.h

> class A
> {
> public:
> 	void myVirtualMethod() {
> 		d->myVirtualMethod();
> 	}
> protected:
> 	class Private;
> 	Private *d;
> };

File: a_p.h

> class A::Private
> {
> public:
> 	virtual void myVirtualMethod() {
> 		cout << "foo" << endl;
> 	}
> }

File: b.h

> class B
>       : public A
> {
> public:
> 	B()
>
> 		: d(new B::Private()) {}
>
> };

File: b_p.h

> #include "a_p.h"
>
> class B::Private
>
> 	: public A::Private
>
> {
> public:
> 	virtual void myVirtualMethod() {
> 		cout << "bar" << endl;
> 	}
> };


2)
Signals and slots.
Approach: Create a new signal on the base class, that will work as a 
"trigger", for the correct implementation to be called.
Example:

File: a.h

> class A
> {
> public:
> 	A() {
> 		connect(this, SIGNAL(myVirtualMethodCalled()), this,
> SLOT(myVirtualMethodCalledSlot()));
>       }
>
> 	void myVirtualMethod() {
> 		emit myVirtualMethodCalled();
> 	}
> Q_SIGNALS:
> 	void myVirtualMethodCalled();
> public Q_SLOTS:
> 	void myVirtualMethodCalledSlot() {
> 		cout << "foo" << endl;
> 	}
> };

File: b.h

> class B
> 	: public A
>
> {
> public:
> 	B() {
> 		disconnect(this, SIGNAL(myVirtualMethodCalled()), this,
>                          SLOT(myVirtualMethodCalledSlot())));
> 		connect(this, SIGNAL(myVirtualMethodCalled()), this, 			
>                       SLOT(myVirtualMethodCalledSlot())));
> }
>
> public Q_SLOTS:
> 	void myVirtualMethodCalledSlot() {
> 		cout << "bar" << endl;
> 	}
> }


3)
virtual_hook.
Add a virtual method, called typically virtual_hook, with an identifier, which 
will identify the new method that wants to be called.
Note: while 1) and 2) are _post_, this one is _pre_. That means, the library 
needs to contain the virtual_hook _before_ the virtual method wants to be 
added. If not, you would be breaking the binary compatibility the same way you 
would be doing with a new virtual method of any kind.
Example:

File: a.h

> class A
> {
> public:
> 	enum NewVirtualMethods {
> 		MyVirtualMethod = 0
> 	};
>
>       void myVirtualMethod() {
>		virtual_hook(MyVirtualMethod, 0);
>       }
>
> 	virtual void *virtual_hook(NewVirtualMethods id, void *params) = 0;
> };

File: b.h

> class B
> 	: public A
> {
> public:
> 	void *virtual_hook(NewVirtualMethods id, void *params) {
> 		switch(id) {
> 			case MyVirtualMethod:
> 				myVirtualMethod_();
> 				return 0;
> 			default:
> 				return 0;
> 		}
> 	}
> };


Now, why all this ? In order to make humongous changes less painful, I think 
we could focus at some time at all this details. Such a huge project would 
benefit a lot if we took some design decisions to take at this points.

Now, my 2 cents, why I do prefer 3) over the rest of the solutions.

- Over 1): sharing d pointers can be dangerous from my point of view. A 
subclass shouldn't have access to the very private members of a base class. 
Also, sharing d pointers is not always possible.

- Over 2): we do export on our libraries classes that aren't QObjects, so 
directly the signals/slots connections are not possible.

- Over 2): we already have tons of signals/slots on our code in general. The 
code seems to mix with the rest, even if we add a comment "### KDE 5: make 
this virtual".

In general, if we used the virtual_hook approach we would be having tons of 
benefits from my very point of view. Why ? All classes with virtual methods 
(at least, those which are very possible to grow when we need to assure binary 
compatibility) would count with a virtual hook and an enum. Is just a matter 
of checking the enum (that could be even called the same on all classes) when 
changing between BIG versions of KDE and see what we need to make virtual.

What do you think ? Am I just crazy ?


Regards,
Rafael Fernández López.

-- 
Regards,
Rafael Fernández López.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part.
URL: <http://mail.kde.org/pipermail/kde-core-devel/attachments/20080715/e57809f7/attachment.sig>


More information about the kde-core-devel mailing list