Me said:
Hi, I must admit that I don't understand the "proxy class" that was
mentioned. Would you mind explaining it? It seems that using it would be
inefficient compared to the other methods, but since I don't know anything
of proxy classes, perhaps it doesn't.
Thanks for the alternatives, both of you.
Who invented the name "proxy class" ?
So effienciency need not necessarily be bad. If your proxy class
contructor and accessors are simple enough, the compiler may optimize
them away making it just as fast as anything else.
Just say you had a class with an extreme number of attributes and they
all did basically the same thing but if any of them was changed, you
needed to perform some post processing )e.g. recompute a checksum or
inform a GUI object of the change, write the change to disk, or perhaps
make a "transaction" where you change a number of attributes at once but
you wanted to make the change to be made atomically yet you wanted to
hide all of this and make it look just like getters and setters.
I created a quick chunk of test code that implements the
lock/transaction scenario. In this case we're using the property of how
C++ temporary objects are created and destroyed to our advantage. Hence
any SINGLE C++ expression is transaction safe because temporary objects
are the last thing to be destroyed when executing an expression.
Actually, if you decide to pollute your code with nasty Proxy objects
you could extend the transaction safety beyond a single expression.
Note that the example below DOES NOT TAKE INTO ACCOUNT issues when you
have 2 or more lockable objects. The order in which you take a mutex is
extremely important in avoiding deadlock.
I've never used this paradigm for managing tranactions and I'm not sure
that it makes sense to do it like this because writing expressions and
transaction semantics are not genrally combinable concepts. Having said
that, I'm sure there are some cases where this paradigm may work
perfectly (like reading/writing to shared memory). Also, with a little
more work, you could actually make rollback on exception functional
which IS truly cool.
As for efficiency, as I alluded earlier, the compiler should be able to
inline and remove most of the code that does nothing. Hence from an
efficiency standpoint, this is about as efficient as anything you could
write by hand.
This is just an example of how you can use a proxy. There are so many
more uses. I've used variations on this for factory accessors and
shared memory accessors but they didn't seem to be appropriate examples
given the OP question.
One last point, there is nothing stopping you from using methods GetData
and SetData with proxy classes. My original response to John was just a
clarification on the theme he described.
#include <iostream>
// demo lock - does nothing like locking just yells
struct MutexType
{
int m_lock_count;
MutexType()
: m_lock_count( 0 )
{}
void TakeLock()
{
if ( m_lock_count ++ ) return;
std::cout << "lock is taken\n";
}
void ReleaseLock()
{
if ( -- m_lock_count ) return;
std::cout << "lock is released\n";
}
};
struct Lock
{
Lock( MutexType & i_mutex )
: m_mutex( & i_mutex )
{
m_mutex->TakeLock();
}
~Lock()
{
m_mutex->ReleaseLock();
}
MutexType * m_mutex;
};
template<typename Type> class Proxy
{
public:
Proxy( Type & value, MutexType & i_mutex )
: m_value( value ),
m_lock( i_mutex )
{
}
operator const Type & () const
{
std::cout << "converted to Type\n";
return m_value;
}
// operator =
Proxy & operator= ( const Type & i_val )
{
std::cout << "= Type\n";
m_value = i_val;
return * this;
}
template<typename RHSType> Proxy & operator= ( const Proxy<RHSType>
& i_val )
{
std::cout << "= Proxy<RHSType>\n";
m_value = i_val.get();
return * this;
}
Proxy & operator= ( const Proxy & i_val )
{
std::cout << "= Proxy\n";
m_value = i_val.get();
return * this;
}
// operator +=
Proxy & operator+= ( const Type & i_val )
{
std::cout << "+= Type\n";
m_value += i_val;
return * this;
}
template<typename RHSType> Proxy & operator+= ( const
Proxy<RHSType> & i_val )
{
std::cout << "+= Proxy<RHSType>\n";
m_value += i_val.get();
return * this;
}
Proxy & operator+= ( const Proxy & i_val )
{
std::cout << "+= Proxy\n";
m_value += i_val.get();
return * this;
}
// operator -=
Proxy & operator-= ( const Type & i_val )
{
std::cout << "-= Type\n";
m_value -= i_val;
return * this;
}
template<typename RHSType> Proxy & operator-= ( const
Proxy<RHSType> & i_val )
{
std::cout << "-= Proxy<RHSType>\n";
m_value -= i_val.get();
return * this;
}
Proxy & operator-= ( const Proxy & i_val )
{
std::cout << "-= Proxy\n";
m_value -= i_val.get();
return * this;
}
// etc for all the operators you want
const Type & get() const
{
return m_value;
}
Type & m_value;
Lock m_lock;
};
struct BigNastyAttributeClass
{
int m_val1;
float m_val2;
MutexType m_lock;
Proxy<int> val1()
{
return Proxy<int>( m_val1, m_lock );
}
Proxy<float> val2()
{
return Proxy<float>( m_val2, m_lock );
}
};
void Tester( BigNastyAttributeClass & foo )
{
std::cout << "do 1\n";
foo.val2() = 3 + ( foo.val1() += 3 );
std::cout << "do 2\n";
foo.val2() -= ( foo.val1() += 4 );
std::cout << "do 3\n";
foo.val2() = 4, foo.val1() += 9;
std::cout << "return\n";
}
int main()
{
BigNastyAttributeClass a;
Tester( a );
}
This code spits out:
do 1
lock is taken
+= Type
converted to Type
= Type
lock is released
do 2
lock is taken
+= Type
-= Proxy<RHSType>
lock is released
do 3
lock is taken
= Type
+= Type
lock is released
return
Note how "lock is taken" and "lock is released" neatly wrap assignment
and conversion (getter) operations. I hope this helps.