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

  • Specialization and efficiency are always in need. Whether it's replacing an aging roof, getting a haircut, or tuning up a car, most seek the assistance of trusted experts. The same is true in the business world, where an increasing number of companies are seeking the help of others to administer their IT systems and services. This special edition of Unleashing IT highlights a new breed of IT caretaker -- Cisco Powered service providers -- and the business advantages and operational efficiencies they …

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds