Click here to Skip to main content
Click here to Skip to main content

Generic C++ Properties

By , 9 Jun 2002
 

Introduction

Recently one of the new programmers (with a Borland C++ Builder background) here at The Collective (where I work) asked if there was any way to add properties to a class. I mentioned that Visual C++ uses __declspec(property) and that it is not cross compiler compatible. But it seemed to me that there should be a way to create generic properties. So I scanned various web pages including codeproject and found some references to using a struct as a replacement. But what I wanted was a way to seamlessly switch between __declspec(property) and a generic solution. I created some structs that I liked and then I converted them to a macro based implementation:

#define USE_DECLSPEC_PROPERTY 0
#if USE_DECLSPEC_PROPERTY

#define DECLARE_PROPERTY_GET( type, getfunc, base, var )      \
        __declspec( property( get=getfunc ) ) type var
#define DECLARE_PROPERTY_PUT( type, putfunc, base, var )      \
        __declspec( property( put=putfunc ) ) type var
#define DECLARE_PROPERTY( type, getfunc, putfunc, base, var ) \
        __declspec( property( get=getfunc, put=putfunc ) ) type var

#else

#define DECLARE_PROPERTY_GET( type, getfunc, base, var )                             \
struct _dp_##getfunc                                                                 \
{                                                                                    \
    inline operator type()                                                           \
    {                                                                                \
        return ((base *) ((char *) this - offsetof( base, var )))->getfunc();        \
    }                                                                                \
};                                                                                   \
friend struct _dp_##getfunc;                                                         \
_dp_##getfunc var

#define DECLARE_PROPERTY_PUT( type, putfunc, base, var )                             \
struct _dp_##putfunc                                                                 \
{                                                                                    \
    inline type operator=(const type & value)                                        \
    {                                                                                \
        return ((base *) ((char *) this - offsetof( base, var )))->putfunc( value ); \
    }                                                                                \
};                                                                                   \
friend struct _dp_##putfunc;                                                         \
_dp_##putfunc var

#define DECLARE_PROPERTY( type, getfunc, putfunc, base, var )                        \
struct _dp_##getfunc_##putfunc                                                       \
{                                                                                    \
    inline operator type()                                                           \
    {                                                                                \
        return ((base *) ((char *) this - offsetof( base, var )))->getfunc();        \
    }                                                                                \
    inline type operator=(const type & value)                                        \
    {                                                                                \
        return ((base *) ((char *) this - offsetof( base, var )))->putfunc( value ); \
    }                                                                                \
};                                                                                   \
friend struct _dp_##getfunc_##putfunc;                                               \
_dp_##getfunc_##putfunc var
#endif

At this point I figured I would create a template that any CWnd derived class could use to add a bunch of properties:

template <class T> class CWndProperties : public T, public CWndPropertiesImpl  {
public:        
    CWndProperties() : CWndPropertiesImpl( this ) 
    {       
    }
          
    DECLARE_PROPERTY( bool, Wnd_GetShow, Wnd_PutShow, 
                               CWndProperties<T>, wShow );
    DECLARE_PROPERTY( CRect, Wnd_GetRect, Wnd_PutRect, 
                               CWndProperties<T>, wRect ); 
    DECLARE_PROPERTY( DWORD, Wnd_GetStyle, Wnd_PutStyle, 
                               CWndProperties<T>, wStyle ); 
    DECLARE_PROPERTY( DWORD, Wnd_GetStyleEx, Wnd_PutStyleEx,
                               CWndProperties<T>, wStyleEx );
};

To reduce compile times and potential code bloat CWndPropertiesImpl was created:
.h file

class CWndPropertiesImpl          
{                                                 
public:                                           
    CWndPropertiesImpl(CWnd * pWnd = NULL);       
    void Wnd_SetWnd(CWnd * pWnd);                 
                                                  
    bool Wnd_GetShow();                           
    bool Wnd_PutShow(const bool & bShow);         
                                                  
    CRect Wnd_GetRect();                          
    CRect Wnd_PutRect(const CRect & rc);          
                                                  
    DWORD Wnd_GetStyle();                         
    DWORD Wnd_PutStyle(const DWORD & dwStyle);    
                                                  
    DWORD Wnd_GetStyleEx();                       
    DWORD Wnd_PutStyleEx(const DWORD & dwStyleEx);
                                                  
private:                                          
    CWnd * m_pWnd;                                
};                                                

.cpp file

CWndPropertiesImpl::CWndPropertiesImpl(CWnd * pWnd)
{
    m_pWnd = pWnd;
}

void CWndPropertiesImpl::Wnd_SetWnd(CWnd * pWnd)
{
    m_pWnd = pWnd;
}

bool CWndPropertiesImpl::Wnd_GetShow()
{
    return m_pWnd->IsWindowVisible() ? true : false;
}

bool CWndPropertiesImpl::Wnd_PutShow(const bool & bShow)
{
    bool bReturn = Wnd_GetShow();
    m_pWnd->ShowWindow( bShow ? SW_SHOW : SW_HIDE );
    return bReturn;
}

CRect CWndPropertiesImpl::Wnd_GetRect()
{
    CRect rcReturn;
    m_pWnd->GetWindowRect( &rcReturn );
    return rcReturn;
}

CRect CWndPropertiesImpl::Wnd_PutRect(const CRect & rc)
{
    CRect rcReturn = Wnd_GetRect();
    m_pWnd->SetWindowPos( NULL, rc.left, rc.top, 
                             rc.Width(), rc.Height(), 
                             SWP_NOZORDER | SWP_NOACTIVATE );
    return rcReturn;
}

DWORD CWndPropertiesImpl::Wnd_GetStyle()
{
    return m_pWnd->GetStyle();
}

DWORD CWndPropertiesImpl::Wnd_PutStyle(const DWORD & dwStyle)
{
    DWORD dwReturn = Wnd_GetStyle();
    m_pWnd->ModifyStyle( 0xFFFFFFFF, dwStyle );
    return dwReturn;
}

DWORD CWndPropertiesImpl::Wnd_GetStyleEx()
{
    return m_pWnd->GetExStyle();
}

DWORD CWndPropertiesImpl::Wnd_PutStyleEx(const DWORD & dwStyleEx)
{
    DWORD dwReturn = Wnd_GetStyleEx();
    m_pWnd->ModifyStyleEx( 0xFFFFFFFF, dwStyleEx );
    return dwReturn;
}

Now all I needed to do was derive my window class from the CWndProperties template - passing in the class that I originally derived my class from and that was it:

class CNewTreeCtrl : public CWndProperties<CTreeCtrl>
...
CNewTreeCtrl wndTree;
...
wndTree.wShow = false; // this hides the window
wndTree.wShow = true;  // this shows the window
wndTree.wShow = !wndTree.wShow // this toggles the windows visibility

Benefits

As a generic solution, this will work on different machines with different compilers. It can also reduce the API/framework that someone new to (let's say) MFC might need to learn. It is backwards compatible with classes already defined. And, in my opinion, once established the code will look cleaner and easier to read.

Problems

OK, so there are a few problems. First, the generic solution still uses at least 1 byte per property; and on Visual C++ it rounds up to the next 4 byte boundry. Second, the generic solution is not able to do property arrays.

Conclusion

The Collective is a company that makes video games that run on multiple platforms. The use of generic properties is appealing, but will likely have limited use within areas that need to be optimized. I know that I'll use them in the Tools department, but it's adoption in other departments will need to be limited to classes where every ounce of speed or memory is not an issue.

Revision history

June 1st 2002 - Initial revision.
June 4th 2002 - Removed dependency on the (4 byte) pThis variable for every property; it now only requires 1 byte per property. Thanks to Matthias Mann who made this suggestion in his comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jason.King.Work@gmail.com
Web Developer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralYou can eliminate the 1-byte problem with another trick (templates make things a bit cleaner for the optimizers too)memberMember 43482046 Dec '08 - 1:31 
Using #pragma pack(push,...) #pragma pack(pop) eliminates the natural pack size of 4, 8, etc. Leaving you stuck with 1-byte.
 
However, if you recognize that your struct/class on which you want to add properties already has some "real" field, then you can do the following trick to add an unlimited number of properties at a cost of 0-bytes.
 
#pragma pack(push,1)
typedef struct {} Empty;
template */, typename TBase=Empty>
struct TExample : TBase {
union {
int m_realField; // Assuming you need this
PROPERTY(SomeTypeA, SomeNameA); // Cost Zero for empty struct it creates
PROPERTY(SomeTypeB, SomeNameA); // Cost Zero for empty struct it creates
PROPERTY(SomeTypeC, SomeNameA); // Cost Zero for empty struct it creates
};
};
struct Example : TExample {
SomeTypeA pg_SomeNameA() ...
SomeTypeA ps_SomeNameA() ...
...
};
#pragma pack(pop)

QuestionWhat is offset? I implemented all like this, please commentmemberAxiom66622 May '08 - 5:54 
Will this work on all compilers?
 
The only thing is that now, each property is 2 in size.
 
#define Set(type, name) void _set_##name (type	Value)
 
#define Get(type, name)	type _get_##name ()
 
#define __offset(className, propertyName) (((byte*)&((className*)NULL)->propertyName - (byte*)NULL) + 1)
 
#define Property(type, name, className) \	struct {} __dummy_Property_##name;\
	struct \
	{\
		public:\
		inline void operator =(type __val)\
		{	((className*)(this - __offset(className, __dummy_Property_##name)))->_set_##name(__val); }\
	\
		inline operator type()\
		{	return (type)((className*)(this - __offset(className, __dummy_Property_##name)))->_get_##name(); }\
	} name
 
class MyClass
{
   int localVariable;
 
   Get(int, MyProperty)
   {
   }
   Set(int, MyProperty)
   {
        throw new exception();
   }
   Property(int, MyProperty, MyClass);
};
 

 
Axiom
 
One of many...

QuestionPut functions for assignment not correct ?memberDaMagic11 Jan '07 - 5:33 
Explicitly for one put function.
 
The following (as depicted in the article) returns the old (previously) set value and not the new one.
 
bool CWndPropertiesImpl::Wnd_PutShow(const bool & bShow)
{
      bool bReturn = Wnd_GetShow();
      m_pWnd->ShowWindow( bShow ? SW_SHOW : SW_HIDE );
      return bReturn;
}
 
Should be implemented as:
 
bool CWndPropertiesImpl::Wnd_PutShow(const bool & bShow)
{
      m_pWnd->ShowWindow( bShow ? SW_SHOW : SW_HIDE );
      return Wnd_GetShow();
}
 
(No reason to duplicate all other puts in a same manner.)
 
In addition it's better that the callee returns the reference to the property (the nested class) back to the caller and not a copy of field of the wrapper class. (Does not work with types which have no copy-constructor).
 
Additionally the nested class' should be implemented each as a proxy class which always behave like the appropriate type. (See design pattern for proxy class).
AnswerRe: Put functions for assignment not correct ?memberJason King11 Jan '07 - 7:27 
More appropriately:
bool CWndPropertiesImpl::Wnd_PutShow(const bool & bShow)
{
    return m_pWnd->ShowWindow( bShow ? SW_SHOW : SW_HIDE );
}
Which is actually more similar to my original function. The intent was to replicate the functionality of ShowWindow which returns whether the window was previously visible.
GeneralRe: Put functions for assignment not correct ?memberDaMagic11 Jan '07 - 8:41 
Hello again,
 
okay, I only want to note some annotations. Nevertheless very nice article and more elegant than the one I found here:
 
http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c13039/[^]
 
Bye
 

GeneralExcellent article...YET A simple way to implement properties....memberAnandChavali21 Dec '06 - 0:15 

#include <tchar.h>
#include <conio.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
 
template<typename T, char PropertyType> class Property {
private:
      T myVar;     
public:     
      Property() {
            if ( PropertyType != 'r' && PropertyType != 'w' && PropertyType != 'b' ) {                 
                  throw "Invalid PropertyType. PropertyTypes allowed are :: \n\tr ->Read , w -> Write , b -> Both Read and Write.";
            }
      }
         operator T() {
               return GetValue();
      }
      void operator = ( T theVar ) {
            SetValue(theVar);
      }
      virtual T GetValue() {
            if ( PropertyType == 'w' ) {
                  throw "Cannot read from a write-only property";
            }
            return myVar;
      }
      virtual void SetValue(T theVar) {
            if ( PropertyType == 'r' ) {
                  throw "Cannot write to a read-only property";           
            }
            myVar = theVar;
      }
};
int _tmain(int argc, _TCHAR* argv[]) {
      try
      {
            Property<string ,'e'> aStringProperty;
            aStringProperty = "Anand";
            string aStrProp = aStringProperty;        
            cout << "Previosly assigned :: " << "Anand   " << "Retrived from property :: " << aStrProp;
 
            Property<vector<int> , 'b'> aVectorProperty;
            vector<int> aVector;
            aVector.push_back(1);aVector.push_back(2);aVector.push_back(3);aVector.push_back(4);aVector.push_back(5);
            aVectorProperty = aVector;
            vector<int> aNewVector = aVectorProperty;
            cout << "\n\nInitialised vector with values 1,2,3,4,5.   Retrieving from vector property ::\n";
            for ( int i = 0 ; i < 5 ; i++ ) {
                  cout << aNewVector [ i ] << endl;
            }           
      } catch( char* theException ) {
            cout <<   "\n\nException Occured : \n--------------------\n            Message : " << theException ;
      }
      _getch();
     return 0;
}
 

 
Thanks and Regards,
Anand.
GeneralRe: Excellent article...YET A simple way to implement properties....memberAnandChavali21 Dec '06 - 0:17 
Actual class declaration is "template class Property "
 
Thanks and Regards,
Anand.

GeneralRe: Excellent article...YET A simple way to implement properties....memberAnandChavali21 Dec '06 - 0:18 
Actual class definition is like template(typename T, char PropertyType) class Property
 
I dont know why the "less than symbol and greator than symbol" are dissappearing here ...so i used "(" for them....
 
Thanks and Regards,
Anand.

GeneralRe: Excellent article...YET A simple way to implement properties....memberJason King11 Jan '07 - 7:36 
Thank you for your encouragement.
 
Regarding your implementation, it is valid if all you want to do is wrap a variable to limit read or write. Your system would require more implementation to resolve wrapping random functions which is what my article dealt with.
GeneralProperties by preprocessingmemberBoraski14 Jul '03 - 22:42 
I suggest a different way to implement properties in C++. Why not develop a simple preprocessor program that parses C++ source and substitutes property read accesses by calls to the get function, and write accesses to the set function? There would be a property declaration syntax that can be Microsoft's, Borland's or a new one.
 
It would produce a new source on the fly that can go to any compiler. And there would be no runtime overhead at all.
 
Anyway I would like that extension to be standard, but standard C++ defenders don't like the idea Frown | :( br />
GeneralRe: Properties by preprocessingmemberDragon Lord24 Jul '03 - 16:33 
The only real problem with this that I see is that which plagues any solution built on a pre-processor.
 
1. Parsing C++ is non-trivial at best, making a "simple preprocessor" something of an oxymoron.
 
2. Pre-Processing, while flexible is also often frowned upon, primarily, I think because of
      * It's impact on the build cycle (another tool which must be inserted into the compile / link chain)
      * The possibility of introducing errors in the pre-process step
      * That the resulting code is arguably no longer C++ (since adding non-standard extensions is the purpose of pre-processing), which can confuse developers not familiar with the pre-processor (the old "don't change the language syntax" rule)).
 
Of course, I don't necessarily agree that pre-processing is "bad".   Both the built in pre-processor, and templates (which can be viewed as a type of pre-processing done by the compiler) are some of C++'s more powerful assets.
 
I know that the QT library from Trolltech uses a custom pre-processor (MOC) to add features that are not normally associated with C++ (including event handling and some introspection, I believe), but it seems that often pre-processing based solutions are criticized within the development community.
 
I've run into features I'd like to add to the language that pretty much seem to require pre-processing to solve in an elegant fashion (effective Design By Contract Support for one), but the effort of making sense of C++ syntax has always stopped me from pursuing these efforts very far.
 
Ultimately, unless you are a compiler writer, parsing C++ in anything but a stupid "replace this pattern with that pattern" fashion is generally more trouble then it's worth.   It's one of the disadvantages of the C++ language that it is non-trivial to write "little helper tools".
 
There are some packages starting to crop up online now (don't feel like digging up links) which look promising for the future in this area.   By acting as parsing front-ends and allowing you to manipulate the resulting internal code structure, they could make the parsing problem moot, but I have yet to spend much actual time checking them out.
GeneralRe: Properties by preprocessingmemberBoraski27 Jul '03 - 20:35 
I agree pretty much with your reply. I was just presenting an idea to see whether it made sense. I thought this way the property mechanism would affect the build process and not the runtime behavior.
 
And yes, a C++ parser is definitely not a simple thing to make.

GeneralA small bug in DECLARE_PROPERTY()memberAnonymous19 Jun '02 - 2:59 
an expression "_dp_##getfunc_##putfunc" will always produce name "_dp_getfunc_RealPutFunc".
 
change it to "_dp_##getfunc##_##putfunc"
 

 

Questionand macros?memberAnonymous10 Jun '02 - 21:13 
what's wrong with using macros?
AnswerRe: and macros?memberJason King8 Jul '02 - 12:41 
Macros are not type safe and you cannot access private or protected members.
GeneralGet rid of the pThis pointermemberMatthias Mann2 Jun '02 - 6:53 
Hi,
 
there is a way to get ride of the pThis pointer. you can use this:
 
"CMyClass" is the name of your class where you define the properties
 
"m_Property" is the name of your proberty
 
 (CMyClass*)((char*)(this) - offsetof(CMyClass, m_Property))
 
You now have a pointer to your containing class. It's a save way. You must only change your makros so that say know the name of your class and the anme of the property. This has also the advantage that you replace a memory access to a single substraction Smile | :)
 
Ciao Matthias Mann
GeneralRe: Get rid of the pThis pointermemberJim Crafton2 Jun '02 - 7:17 
But is that in fact portable ? I.e. will this work on GCC as well as VC++ ? I am asking since I think it was the author's intent to make this work in a portable fashion.
GeneralRe: Get rid of the pThis pointermemberMatthias Mann2 Jun '02 - 8:23 
Mozilla uses it in it's crossplatform code (XPCOM) and it runs under a huge number of OSes and compiler.
 
It work on the fact the a variable must be placed somewhere in the byterange of an object. It also works for structurs that has no size. But you must ensure that the classname and variablename is right. It does only work with structs/classes because of the this pointer. You can place a assertion in it:
 
ASSERT(&pMyClass->m_Property == this);
 
PS: I've also used this method in multiple inheritens situations with templates - works great.
 
Ciao Matthias Mann
GeneralRe: Get rid of the pThis pointermemberJim Crafton3 Jun '02 - 2:50 
excellent ! I did not realize that !
GeneralRe: Get rid of the pThis pointermemberJason King2 Jun '02 - 10:20 
Thanks. I'll try to add it to the system. Once I get it working, I'll update the article.
GeneralExcellent!memberJörgen Sigvardsson2 Jun '02 - 5:37 
Very clever indeed!
 
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.
GeneralWrong rating!!! <--- the mathematics I meanmemberNish - Native CPian2 Jun '02 - 0:15 
It says 2.56 out of 3 votes. But that's impossible.
This means his total votes is 7.68.
If his total was 8 then his avg would be 2.66 and if his total was 7 he'd be 2.33.
2.56 is unusual!!!!
 
Nish Confused | :confused:
 

Regards,
Nish
Native CPian.
Born and brought up on CP.
With the CP blood in him.

GeneralRe: Wrong rating!!! <--- the mathematics I meanmemberJörgen Sigvardsson2 Jun '02 - 5:36 
Hmm, I just gave it a 5 and the avg became 2.8. 2.8 * 5 = 11.2. Still not right!
 
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.
GeneralRe: Wrong rating!!! <--- the mathematics I meanmemberC#2 Jun '02 - 5:44 
Jörgen Sigvardsson wrote:
2.8 * 5 = 11.2
 
2.8*5 = 14 Smile | :)
GeneralRe: Wrong rating!!! <--- the mathematics I meanmemberJörgen Sigvardsson2 Jun '02 - 6:06 
doh! Blush | :O
 
I meant 2.8 * 4, which is 11.2
 
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 10 Jun 2002
Article Copyright 2002 by Jason.King.Work@gmail.com
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid