Easy C++ - Delegates / Generic Properties / Closures / Thunks

The purpose of this document is not to start yet another discussion, but rather it is to show how to take an implementation of delegates in C++ —which normally most of C++ compilers don't support—to one more step ahead. And, this will make it really easy for developers to add such feature to their existing C++ program. These features should compile on any C++ compiler.

What Am I Talking About?

I am talking about a member (treated as variable) of a class that has two types of functions associated with; one function that returns the value of a certain type when the data member is asked to return a value, and another function to set the value when the data member is assigned a value.

Using A Simple Example

Take the example of a button on a dialog. Button is basically a class and has a member called "width". When width is assigned a value the button's width is physically changed on the dialog's surface. When this member is assigned to an "int" type variable, the "width" property returns the width of the button.

Button b;

b.width = 100;	   //setting / assigning the value, change button width to 100 pixels
int bw = b.width;  //getting the value, get button's current width in pixels

Now, if the data member "width" was a normal integer "int" it won't affect the width of the button physically, so basically you have to create "width" in a way that it will affect the button width when assigned a value, and it must retun button's physical width when required. For this two functions need to be attached to this member—one function for changing the button's width and the other for getting button's width.

How Do You Accomplish This?

To accomplish this, you will not define data member 'width' as int, but rather you will define member 'width' as a property; In other words, a class that has "operator =" and has two functions that are bound to this tiny Button class for getting and setting data value as well as performing extra functionality (e.g. affecting button's width). So you need something like:

class Button {
   public:
      <SOMETHING ELSE BUT NOT int> width;
			
      void set_value(int newValue) 
      {
         //change button width
      }
			
      int get_value() 
      {
         return ??; //button's width;
      }			
};

The question is how to attach the two little member functions 'set_value' and 'get_value' to the data member 'width', so that they get called automatically when 'width' is used in either way.

Let's dive a little into the depth. This is what keywords, property in Microsoft Visual C++, delegates in C#, and closure in Borland C++ Builder are used. But, they are not cross platform and not supported by all C++ compilers. For example (keyword):

  • property not supported by GCC
  • property not supported by Borland C++
  • closure not supported by GCC
  • closure not supported by Microsoft VC++
  • property and closure both not supported by Watcom C++
  • property and closure both not supported by LCC
  • property and closure both not supported by Intel C++ (am not sure about this)

To overcome this problem, we have many implementations for generic properties

  • some use templates
  • some use hardcoded class name to use with scope resolution operator
  • some utilize #define keyword
  • etc.

SO WHY YET ANOTHER IMPLEMENTATION?

Keyword "property" is available in MSVC++ only.

Keyword "closure" is available in Borland C++ only.

What about GCC, and compilers on other platforms like Linux and Mac OSX?

Using other than (built-in implementations) "property" and "closure", mostly a new class is created for each data member, which becomes bulky. The result might not affect program speed (depends), but definitely increases final executable size very much. And after all, it is not a good idea to have new class for thousands of data members.

In all implementations to member functions are bound to such properties. But we know that ISO forbids assigning address of member function in a class. In the result we have to point to a member function including class scope, such as &Button::*get_value or &Button::*set_value. But wait, this is specific to class Button, and of course we will end up with templates to resolve this problem.

Now question arrives?

WHY BIND MEMBER FUNCTIONS?

Because we like our member functions to access all elements of the class (they are in), either public, private, and/or protected.

I had an idea last night. Why not use "friend" functions? (If it has already been implemented, forgive me I neve saw it!)

A "friend" function has access to all of private, protected, as well as public data, which will result in more generic class for properties imnplementation. Create a type once and use it multiple times. So, I wrote two macros bacially. One creates the abstract class not "abstract of c++" I would say a new type). The other resolves the class pointer from long, using casting "reinterpret_cast".

Usage:
#include "delegate.h"

CREATE_PROPERTY_TYPE(int, intProp); 

class Button {
   public:
      intProp width; //generic property of type "int"
};

Now it is time to bind some "friend" functions

class Button {
   public:
      intProp width;
		
      Button() {
         // bind property to 2 new friend functions 
         // rather than member functions
			
         width.bind( get_width , set_width , (long)this );
      }
		
		
      // bound "friend" functions
		
      friend int get_width(long);
      friend int set_width(long, int);
};
What is this?
width.bind( get_width , set_width , (long)this );

We called the function "bind" that is a member of the new property class that was created. It is then passed arguments:

  1. getter - get_width
  2. setter - set_width
  3. pointer to current class to reuse in friend functions

Compulsions

  1. The first argument of each get and set functions must be "long", which will be used as a class pointer.
  2. In the set function's second argument will be the type of variable "new class type" was created for.
  3. The set function must also have a return type of property type, even if it is not returning any value.

Now implement the bind functions. They can be implemented outside, but I am implementing them within the class to make it clearer.

class Button {
   public:
      intProp width;
		
      HWND hwnd;  //assumed handle to Button window
		
		
      Button(HWND han=NULL) {
         this->hwnd = han;
         width.bind( get_width , set_width , (long)this );
      }
		
		
      friend int get_width(long __CP) {
         Button *cls = reinterpret_cast<Button *> (__CP); //get access to class pointer
		
         RECT r;
         GetWindowRect(cls->hwnd,&r);
         return r.right-r.left;			
      }
		
      friend int set_width(long __CP, int v) {
         Button *cls = reinterpret_cast<Button *> (__CP); //get access to class pointer
			
         RECT r;
         GetWindowRect(cls->hwnd,&r);            
         MoveWindow(cls->hwnd,r.left,r.top,value,r.bottom-r.top,true);            
         return value;		
      }
};

Further summarizing the interpretation portion of the code:

CREATE_CLASS_POINTER(Button, cls); 

instead of using

//Button *cls = reinterpret_cast<Button *> (__CP);
Computations
  1. First argument variable name of both get and set functions must be __CP, if you wishto use said "CREATE_CLASS_POINTER" macro, or create your own.

USING THIS "Button" CLASS:

Button b( GetDlgItem(myDlg, 101) );
btn.width = 100;
int bw = btn.width;

Summary of All Talk

  1. Cross platform code
  2. All C++ compilers supopport
  3. Small foot-prints in final executable
  4. Not bulky for execution
  5. Can be used even if you have no idea what are function pointers and generic properties

IMPLEMENTATION OF "CREATE_PROPERTY_TYPE" in delegate.h

#define CREATE_PROPERTY_TYPE(__USERTYPE, __TYPENAME) \
class __TYPENAME {\
    typedef __USERTYPE (*__TYPENAME##_GP)(long);\
    typedef __USERTYPE (*__TYPENAME##_SP)(long, __USERTYPE);\
    \
    private:\
        __TYPENAME##_GP gf;\
        __TYPENAME##_SP sf;\
        long    cls;\
    public:\
        __TYPENAME() {\
            gf = NULL;\
            sf = NULL;\
        }\
        void bind(__TYPENAME##_GP vgf,__TYPENAME##_SP vsf, long vcls=0) {\
            gf=vgf;\
            sf=vsf;\
            cls=vcls;\
        }\
        __TYPENAME(__TYPENAME##_GP vgf,__TYPENAME##_SP vsf, long vcls=0) {\
            this->bind(vgf,vsf,vcls);\
        }\
        __USERTYPE operator = (__USERTYPE v) {\
            (*sf)(cls, v);\
            return (__USERTYPE)v;\
        }\
        __USERTYPE operator = (__TYPENAME & v) {\
            (*sf)(cls, v);\
            return (__USERTYPE)v;\
        }\
        operator __USERTYPE () const {\
            return (__USERTYPE)(*gf)(cls);\
        }\
        bool operator == (__USERTYPE v) {\
            return ( (__USERTYPE)(*gf)(cls) == v );\
        }\
        bool operator != (__USERTYPE v) {\
            return ( (__USERTYPE)(*gf)(cls) != v );\
        }\
};\

IMPLEMENTATION OF "CREATE_CLASS_POINTER" in delegate.h

#define CREATE_CLASS_POINTER(__CLASS, __VAR) __CLASS * __VAR = reinterpret_cast <__CLASS *> (__CP);

SAMPLE WINDOWS PROGRAM (changes and retrieves width of a window):

#include <windows.h>
#include "delegate.h"

/*
C++ generic properties demostration program
Scenario:
    
    Create a class window, which has a generic property
    "width". When property width is assigned a value, 
    it will resize the window to assigned value, and equally
    returns the width of the window when property itself is
    assign to some other variable of same type.
    
*/


CREATE_PROPERTY_TYPE(int, intProp); //Create new property type

class Window {
   public:
      intProp width;
      HWND hwnd;
		
      Window(HWND=NULL);
		
      //friend bound functions
      friend int get_width(long);
      friend int set_width(long, int);
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
   HWND dlg = CreateWindowEx(
      0,
      WC_DIALOG,
      TEXT("My Window"),
      WS_OVERLAPPEDWINDOW | WS_VISIBLE,
      100,100,200,200,NULL,NULL,NULL,NULL
   );
	
   Window w(dlg);
	
   MessageBox(dlg, TEXT("Now we will assign property value which will affect the width of the window"),TEXT(""),0);
	
   w.width = 500;     //notice this
	
   TCHAR temp[100];
	
   int wid = w.width;  // notice this
	
   wsprintf(temp,TEXT("New width is %d"),wid);

   MessageBox(dlg,temp,TEXT(""),0);
	
   return 0;
}

//implementation of constructor and bound functions
Window::Window(HWND han) {
   this->hwnd = han;
   width.bind( get_width , set_width , (long)this );
}
int get_width(long __CP) {
   CREATE_CLASS_POINTER(Window, cls);

   RECT r;
   GetWindowRect(cls->hwnd,&r);
   return r.right-r.left;			
}

int set_width(long __CP, int value) {
   CREATE_CLASS_POINTER(Window, cls);

   RECT r;
   GetWindowRect(cls->hwnd,&r);            
   MoveWindow(cls->hwnd,r.left,r.top,value,r.bottom-r.top,true);            
   return value;		
}

That's all for now, and let me know if I missed anything or if this article was of some help to you.



Downloads

Comments

  • My head goes ... SPIN!

    Posted by foxmuldr on 02/24/2010 12:59am

    A lot of code for not much gain?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

Most Popular Programming Stories

More for Developers

RSS Feeds