Topic: Emulating Obj. Pascal Properties in C++
Author: pollardj@jba.co.uk (Jonathan de Boyne Pollard)
Date: 29 Nov 1994 17:50:14 -0000 Raw View
I too have had problems with your mail address, BTW, hence the posting.
Oh well, perhaps someone else here will be interested in this too ...
In article <MIKEY.94Nov24191134@boatman.mikey.pr.mcs.net> you wrote:
: A certain vendor's object oriented Pascal allows Property definitions
: in its class definitions. Properties encapsulate those nasty
: getFooValue and setFooValue methods in a convenient and rather elegant
: manner. Specifically, reading the value of a property calls its
: associated read method, and assigning to a property calls its write
: method. It's a programming style that I would like to promote.
If you are interested in properties, what you need to take a look at are
Metaware's and IBM's DirectToSOM C++ compilers. DirectToSOM C++ compilers
generate CORBA-style classes and instances. One of the facets of the SOM
specification is a so-called "attribute", which is accessed using get and
set functions such as you describe.
For example, here is a DirectToSOM C++ class definition.
class MyClass : public SOMObject {
public:
int i ;
} ;
The data member 'i', because it has public access control, is automatically
an attribute. When the compiler comes to generate code for
MyClass x ;
x.i = 1 ;
int z = x.i ;
it actually generates the equivalent of
MyClass x ;
x._set_i(1) ;
int z = x._get_i() ;
The implementor of the class is allowed to redefine the attribute access
functions if they so desire. Viz.
int MyClass::_get_i ()
{
return i / 2 ;
}
void MyClass::_set_i ( int j )
{
i = j * 2 ;
}
( Note that in DirectToSOM C++ member functions have direct access to
attribute data members. )
DTS C++ also solves several other bugbears of the C++ programmer. Binary
distribution of class libraries, anyone ? Or perhaps proper control of
virtual bases in user-defined assignment operators ?
JdeBP
Author: oren@silver.weizmann.ac.il (Ben-Kiki Oren)
Date: Sun, 27 Nov 1994 23:11:42 GMT Raw View
Michael H. Young (mikey@boatman.mikey.pr.mcs.net) wrote:
[snip]
> ... C++ does not appear to support this type of use efficiently. I'm
> posting here, in the standards news group, in hopes of flushing out
> the language features that would support an efficient
> implementation. I also wouldn't be chastened if someone were to point
> out how I can already do this.
It is possible to hack C++ to do this... sort of.
Who was it that said that `Every use of the pre-processor is an indication of
a lack in the language'? Anyway, here's a small hack using CPP which gives a
reasonable simulation of PASCAL properties (I called them `Pseudo members',
hence the `Pm' prefix to the macros).
Of course, this solution has its disadvantages: I'm not sure how it would
interact with inheritance; The error messages one gets for use of non-const
functions on const objects are far from intuitive; It is generally horrible,
except to people who think proxies are an elegant way of coding (I don't).
On the bright side, it does not increase the size of the class containing the
members; All operations are done via inline functions, so theoretically it
should work with no performance loss; Const correctness is preserved, via an
internal cast-const-away; And as a final bonus, you can provide any operators
you like for the pseudo member (in the example below, I implemented
operator++).
Note: I am not endorsing the following as a recomended programming style. It
has its points, especially when allowing code such as a.size() += 7 instead of
a.set_size(a.get_size() + 7); However, given the amount of machinery required,
it should probably be reserved for extreme cases only.
BTW, I tested the following on BC3.1 and it worked; can anybody tell me
whether I'm doing anything illegal/non-portable/non-standard in the code? (I
already know it is ugly ;-)
//
// C++ pseudo members
//
// Precedes read operators, if any are needed
#define PmRoStart(C, P) \
class read_##P { \
friend class C; \
protected: \
const C *THIS_PTR; \
read_##P(const C *Cp): THIS_PTR(Cp) { } \
const C *THIS() const { return(THIS_PTR); } \
void operator=(read_##P &) { }; \
public: \
// If no write operators are needed, this terminates the declaration
#define PmRoFinish(P) \
}; \
read_##P P() const { return(read_##P(this)); }
// Otherwise, this separates the read operators from the write operators
#define PmWrStart(C, P) \
PmRoFinish(P) \
class write_##P : public read_##P { \
friend class C; \
private: \
write_##P(C *Cp): read_##P(Cp) { } \
C *THIS() { return((C *)THIS_PTR); } \
public:
// If no read operators are needed, this precedes the write operators
#define PmWoStart(C, P) \
class write_##P { \
friend class C; \
private: \
C *THIS_PTR; \
write_##P(C *Cp): THIS_PTR(Cp) { } \
C *THIS() { return(THIS_PTR); } \
void operator=(write_##P &) { }; \
public:
// This terminates the declaration if there were any write operators
#define PmWoFinish(P) \
}; \
write_##P P() { return(write_##P(this)); }
// Name of the class for the read operators definitions
#define PmRo(C, P) C::read_##P
// Name of the class for the write operators definitions
#define PmWo(C, P) C::write_##P
//
// This is how they are used:
//
class B {
private:
int b_;
public:
B(int init_b): b_(init_b) { }
public:
int get_b() const { return(b_); }
int set_b(int i) { return(b_ = i); }
public:
PmRoStart(B, b)
operator int();
PmWrStart(B, b)
int operator=(int i) { return(THIS()->set_b(i); }
int operator+=(int i);
PmWoFinish(b);
};
inline
PmRo(B, b)::operator int(
){
return(THIS()->get_b());
}
inline int
PmWo(B, b)::operator+=(
int i
){
return(THIS()->set_b(THIS()->get_b() + i));
}
void
f(
){
const B b2(7);
B b1(7);
b1.b() = b2.b();
b2.b() = b1.b(); // Error
b1.b() = 7;
b2.b() = 7; // Error
b1.b() += 6;
b2.b() += 6; // Error
}
--
Life is tough and the hard ships are many.
Author: johnston@caiman.enet.dec.com (Ian Johnston)
Date: 28 Nov 1994 08:02:38 GMT Raw View
In article <MIKEY.94Nov25141427@boatman.mikey.pr.mcs.net>,
mikey@boatman.mikey.pr.mcs.net (Michael H. Young) writes:
>In article <3b4feh$f7n@vbohub.vbo.dec.com> johnston@caiman.enet.dec.com
>(Ian Johnston) writes:
>
>
> This works for me on the compilers I have right now.
>
> [I tried to mail this, but my news reader couldn't hack the return
>address]
>------
> Please send replies to mikey@mcs.com. (Sorry. My Linux system doesn't
>know about my pop account, and I seem incapable of teaching it.)
>
>Your example works fine for directly accessing the value. I'm looking
>for the more general case of calling a member function to do some
>manipulations based on context.
You could try doing these more specific things by adding a template
parameter; that parameter would be another class which would do the
specific
things (and it would probably need to be a friend of the class containing
the property).
Eg
template <class Type, class Fiddler>
class Property
{ /* ... */ };
class X
{
friend class WindowFiddler;
Property<Window *, WindowFiddler>;
// ...
};
--
Consulting for Digital Equipment Corp johnston@caiman.enet.dec.com
(33) 92.95.51.74
Author: mikey@mcs.com (Mike Young)
Date: Mon, 28 Nov 1994 02:42:38 Raw View
In article <1994Nov27.231142.22934@wisipc.weizmann.ac.il> oren@silver.weizmann.ac.il (Ben-Kiki Oren) writes:
>Subject: Re: Emulating Obj. Pascal Properties in C++
>From: oren@silver.weizmann.ac.il (Ben-Kiki Oren)
>Date: Sun, 27 Nov 1994 23:11:42 GMT
>It is possible to hack C++ to do this... sort of.
>Who was it that said that `Every use of the pre-processor is an indication of
>a lack in the language'?
------
Agreed. :) I think I'm not looking for that type of workaround at this stage.
I'm really looking for the language constructs that C++ would have to support
to allow this use, without introducing substantially new syntax.
To expand on my original post, the following might be considered "ideal"
use. There's no redundancy in information, and nothing special in the way of
reserved words. (It also is not possible using templates as they're currently
defined.)
class TPoint;
class Foo
{
private:
int getSystemTime( ) const;
TPoint setWindowSize(TPoint);
TPoint getWindowSize( ) const;
public:
ROProperty<int, getSystemTime> SysTime; // readonly property
RWProperty<TPoint,
getWindowSize, setWindowSize> WindowSize; // read-write property
};
void foobar()
{
Foo foo;
int systemTime = foo.SysTime; // resolves to call to Foo::getSystemTime()
TPoint pt = foo.WindowSize; // resolves to call to Foo::getWindowSize()
foo.WindowSize = pt + TPoint(4,4);
// resolves to call to Foo::setWindowSize(TPoint)}
}
One of the problems I had was in trying to define properties as templates.
(Again, maybe you're right in that templates ARE NOT the solution.) One problem
is template args can't be specified in terms of other template args.
template<class O, class T, T (O::*ReadMethod)()const>
class ROProperty {...};
is not legal. I can't create a template that takes as template arguments a
class type, and a pointer to member function of that type. The above example
corresponds to something like this:
class Foo
{
private:
int getSystemTime() const;
public:
ROProperty<Foo, int, &Foo::getSystemTime> SystemTime;
};
Notice that the class type was added to the template arg's. It's needed to
allow us to properly store the owning class's this pointer (second problem
already). The rest of it degenerates quickly from here. Each property carries
a pointer back to the owning class. Thus (if the above WERE legal):
template<class O, class T, T (O::*ReadMethod)()const>
class ROProperty
{
private:
O * pThis; //-- this is redundant, but necessary!
public:
ROProperty(O * pOwner) : pThis(pOwner)
{ }
operator const T & ( ) const
{ return (pThis->*ReadMethod)();
}
};
This leads to the ultimately unusable result:
template<class O, class T>
class ROProperty
{
private:
typedef T (O::*ReadMethod)( ) const;
O * pThis;
ReadMethod readMethod;
public:
ROProperty(O * pOwner, ReadMethod pfn) : pThis(pOwner), readMethod(pfn)
{ }
operator T ( ) const
{ return (pThis->*readMethod)();
}
};
class Foo
{
public:
const int & getSystemTime() const; // wants to be private
public:
ROProperty<Foo, int> SystemTime;
Foo( ) : SystemTime(this, &Foo::getSystemTime)
{ }
};
The other problems have to do with public accessibility to the member
function. The property isn't a friend of the class, so the members it
references must be publicly accessible.
And const-ness of the object. The property template carries its own
pointer to the owning object. A read-write property template would need a
non-const pointer to do anything useful, and it automatically casts away
const-ness to do the assignment. Thus, the following is legal:
void foobar()
{
const foo;
foo.WindowSize = TPoint(4,4);
}
Templates brings us close to a solution, but it is still frustratingly far
away.
Mike.
Author: mikey@boatman.mikey.pr.mcs.net (Michael H. Young)
Date: 25 Nov 1994 01:11:34 GMT Raw View
A certain vendor's object oriented Pascal allows Property definitions
in its class definitions. Properties encapsulate those nasty
getFooValue and setFooValue methods in a convenient and rather elegant
manner. Specifically, reading the value of a property calls its
associated read method, and assigning to a property calls its write
method. It's a programming style that I would like to promote.
I made a half-hearted attempt to provide the same capability in C++
using templates. While the user code looks the way I want, the
declaration syntax is far from ideal. This is unfortunate, because
it's the class designers that need to be "won over."
The user code looks like this:
Class.property1 = "foo";
string envString = Class.property2;
instead of the very ugly and decidedly unencapsulated equivalents:
Class.setStringVal("foo");
string envString = Class.getNameString();
The class that supports this wants to look like this:
//----------------------------------------------------------------
class Class {
public:
ROProperty<string, getStringVal> property1;
RWProperty<string, getNameString, setNameString> property2;
private:
const string & getStringVal() const; // reading property1
const string & getNameString() const; // reading property2.
const string & setNameString(); // setting property2.
};
//----------------------------------------------------------------
But the closest I can come using a couple of simple templates is this:
//----------------------------------------------------------------
class Class {
private:
typedef Class THIS;
public:
ROProperty<THIS, string> property1;
RWProperty<THIS, string> property2;
Class( )
: property1(this, &THIS::getStringVal),
property2(this, &THIS::getNameString, &THIS::setNameString)
{ }
public: //**** These really want to be private! *****
const string & getStringVal() const;
const string & getNameString() const;
const string & setNameString();
};
//----------------------------------------------------------------
The differences are profound, and bad enough to make this first
iteration not useful.
1) The declaration of the read/write methods are disjoint from the
property declaration. (Does anyone know how to write the templates to
take the function names?) In this short example, having the
constructor inlined makes the problem not so apparent. Normally,
however, the constructor will be in a separate file altogether, and
makes this a real mess.
2) The real methods need to be public to allow the template class to
call them. Their names clutter up the public space unnecessarily, but
aren't otherwise harmful.
3) Each property carries the owner's this pointer. This shouldn't be
necessary, and there appears no other portable way to connect the
property to the owner.
4) The template requires the type (class name) of the owner, since it
uses pointers to member functions. The simplest way to work around
this is to declare a typedef so the current class can be referred to
as THIS. This information is redundant given the context at the point
where the property is defined. It's extra work, and can lead to errors.
5) The templatized property won't enforce const-ness on an
object. That is, the user code can assign to the property, and the
property will obligingly call the object's "set" method, regardless of
the object's const-ness at that point. (The property carries around
its own copy of the object's this pointer. It needs to be non-const
for the assignment operator to work.)
I'm open to suggestions. The classes are simple, almost trivial to
write. I'll attach them hopes that I'll get more meaningful feedback.
Mike.
=================== property.h =================================
#ifndef __PROPERTY_H__
#define __PROPERTY_H__
//*************************************************************
template <class O, class T>
class ROProperty
{
protected:
typedef const T & (O::*GetMethod)( ) const;
GetMethod pfnGet;
O * pThis;
public:
ROProperty(const O * pOwner, GetMethod pfnRead);
operator const T & ( ) const;
};
//*************************************************************
template <class O, class T>
class RWProperty : public ROProperty<O, T>
{
protected:
typedef const T & (O::*PutMethod)(const T &);
PutMethod pfnPut;
public:
RWProperty(O * pOwner, GetMethod pfnRead, PutMethod pfnWrite);
const T & operator = (const T & rVal);
};
//*************************************************************
template <class O, class T>
inline RWProperty<O,T>::RWProperty(O * pOwner, GetMethod pfnRead, PutMethod pfnWrite)
: ROProperty<O, T>(pOwner, pfnRead), pfnPut(pfnWrite)
{ }
//-------------------------------------------------------------
template <class O, class T>
inline const T & RWProperty<O,T>::operator = (const T & rVal)
{ return (pThis->*pfnPut)(rVal);
}
//-------------------------------------------------------------
template <class O, class T>
inline ROProperty<O,T>::ROProperty(const O * pOwner, GetMethod pfnRead)
: pThis((O *)pOwner), pfnGet(pfnRead)
{
}
//-------------------------------------------------------------
template <class O, class T>
inline ROProperty<O,T>::operator const T & ( ) const
{ return (pThis->*pfnGet)();
}
//*************************************************************
#endif // #ifndef __PROPERTY_H__
======================== testme.cpp ========================
#include <cstring.h>
#include <iostream.h>
#include "property.h"
//*************************************************************
//*************************************************************
class testme
{
private:
typedef testme THIS;
string iName;
int bInit;
static string strUnknown;
//------- Construction
public:
testme();
//------- Attributes
public:
ROProperty<THIS, int> state;
RWProperty<THIS, string> name;
//------- Property Get/Put methods
public:
const int & getState() const;
const string & getName() const;
const string & putName(const string & iVal);
};
//-------------------------------------------------------------
string testme::strUnknown("<Unknown>");
//-------------------------------------------------------------
ostream & operator << (ostream & os, const testme & r)
{
os << "state = " << r.state << ", name = \"" << r.name << '"';
return os;
}
//*************************************************************
//*************************************************************
inline testme::testme()
: state(this, &testme::getState),
name(this, &testme::getName, &testme::putName),
bInit(0)
{ }
//-------------------------------------------------------------
inline const int & testme::getState() const
{ return bInit;
}
//-------------------------------------------------------------
inline const string & testme::getName() const
{ return bInit ? iName : strUnknown;
}
//-------------------------------------------------------------
inline const string & testme::putName(const string & iVal)
{ iName = iVal;
bInit = 1;
return iName;
}
//*************************************************************
int main(int, char **)
{
testme foo;
cout << "foo constains: " << foo << endl;
foo.name = "Eat me!";
cout << "foo constains: " << foo << endl;
return 0;
}
Author: johnston@caiman.enet.dec.com (Ian Johnston)
Date: 25 Nov 1994 10:48:49 GMT Raw View
Re: emulating properties
This works for me on the compilers I have right now.
[I tried to mail this, but my news reader couldn't hack the return address]
// --------------------------------------------------------------------
template <class T>
class Property
{
public:
Property(T const &val) : prop(val)
{
}
~Property()
{
}
operator T const () const
{
return prop;
}
T & operator = (T const &val)
{
prop = val;
return prop;
}
private:
T prop;
};
class X
{
public:
Property<int> integer;
X(int i) : integer(i)
{
}
};
int main()
{
int i;
X x(1);
X const cx(2);
x.integer = 2;
cx.integer = 3; // Gives compile error
i = x.integer;
int const ci = cx.integer;
return 0;
}
--
Consulting for Digital Equipment Corp johnston@caiman.enet.dec.com
(33) 92.95.51.74
Author: mikey@boatman.mikey.pr.mcs.net (Michael H. Young)
Date: 25 Nov 1994 20:14:27 GMT Raw View
In article <3b4feh$f7n@vbohub.vbo.dec.com> johnston@caiman.enet.dec.com (Ian Johnston) writes:
This works for me on the compilers I have right now.
[I tried to mail this, but my news reader couldn't hack the return address]
------
Please send replies to mikey@mcs.com. (Sorry. My Linux system doesn't
know about my pop account, and I seem incapable of teaching it.)
Your example works fine for directly accessing the value. I'm looking
for the more general case of calling a member function to do some
manipulations based on context. I hesitate to give specific examples
for fear of sidetracking the discussion, but examples of intended use
will clarify the design intent. So, here goes...
As examples of possible use, assigning to an object's window size
property changes its window size. Another example: reading the
logged-on time property queries the current system time and calculates
and returns the elapsed time since logon.
Presumably these operations only make sense for an object as member
functions. This gives rise to the problems I mentioned in my original
post. C++ does not appear to support this type of use efficiently. I'm
posting here, in the standards news group, in hopes of flushing out
the language features that would support an efficient
implementation. I also wouldn't be chastened if someone were to point
out how I can already do this.
Thanks!
Mike.