Using operator->

R

Roger Leigh

I've written a simple container template class to contain a single
value. This emits a signal when the value is changed (it's used as a
notifier of changes), and listeners can connect to its changed signal.

i.e. field<int> i(2);
i = 4; // field<int>::m_value = 4; changed signal is emitted.

Currently, the contained value may be accessed via get_value() and
set_value() methods, and for ease of use, operator= and some type
conversions are provided. However, accessing a member function
requires a call to get_value(), and I'd like to overload operator->
and operator* to allow access. Ideally, I'd like a const version
(changes to the value are not allowed) and a non-const version, which
allows changes and emits a signal. However, I can't see that this is
possible (condensed for brevity):

template<typename T>
class field {
private:
value_type m_value;
SigC::Signal0<void> m_signal_changed; // libsigc++ signal type.
public:
typedef T value_type;

field(): m_value() {}
field(const value_type& value): m_value(value) {}
field(const field<value_type>& rhs): m_value(rhs.m_value),
m_signal_changed() {}
virtual ~field() {}

value_type& get_value() { return m_value; }
const value_type& get_value() const { return m_value; }

void set_value(const value_type& value)
{ m_value = value; m_signal_changed.emit(); }

field<value_type>& operator = (const field<value_type>& rhs)
{ set_value(rhs.m_value); return *this; }

field<value_type>& operator = (const value_type& rhs)
{ set_value(rhs); return *this; }

operator const value_type& () const { return m_value; }

SigC::Signal0<void>& signal_changed() { return m_signal_changed; }
}; // class field

If I provide a method like this:

const value_type *operator -> () { return &m_value; }

this is OK, but for the non-const version, I want to do this:

value_type *operator -> () { return &m_value; m_signal_changed() }

i.e. I want to emit the changed signal /after/ the caller has got the
pointer, used the method/member chosen and finished. If I emit the
signal before, the change won't yet have happened. However, I need to
return the pointer, so this is obviously impossible.

In addition, I'd like to make the const version the default (if a
const method is called from a non-const pointer), if possible, so that
the changed signal is only emitted on a genuine state change.

Are either of these possible using standard C++? If not, could anyone
suggest a different design to achieve the goal (signal emission after
value change)?


Many thanks,
Roger
 
L

lilburne

Roger said:
i.e. I want to emit the changed signal /after/ the caller has got the
pointer, used the method/member chosen and finished. If I emit the
signal before, the change won't yet have happened. However, I need to
return the pointer, so this is obviously impossible.

In addition, I'd like to make the const version the default (if a
const method is called from a non-const pointer), if possible, so that
the changed signal is only emitted on a genuine state change.

Are either of these possible using standard C++? If not, could anyone
suggest a different design to achieve the goal (signal emission after
value change)?

You can't make the compiler use the const version of a function that is
overloaded on const alone i.e., the non-const method will always be
called on non-const objects. You have to differentiate the functions by
name i.e, rename non-const version of operator->() to be something like
get_value_to_change(). The long name encourages use of const values.

If you want to broadcast changes after the modification then you'll need
to use a set method and abandon the operator-> idea.
 
M

Michael Mellor

Roger said:
I've written a simple container template class to contain a single
value. This emits a signal when the value is changed (it's used as a
notifier of changes), and listeners can connect to its changed signal.

i.e. field<int> i(2);
i = 4; // field<int>::m_value = 4; changed signal is emitted.

Currently, the contained value may be accessed via get_value() and
set_value() methods, and for ease of use, operator= and some type
conversions are provided. However, accessing a member function
requires a call to get_value(), and I'd like to overload operator->
and operator* to allow access. Ideally, I'd like a const version
(changes to the value are not allowed) and a non-const version, which
allows changes and emits a signal. However, I can't see that this is
possible (condensed for brevity):

template<typename T>
class field {
private:
value_type m_value;
SigC::Signal0<void> m_signal_changed; // libsigc++ signal type.
public:
typedef T value_type;
This typedef needs to go before the line (value_type m_value).
field(): m_value() {}
field(const value_type& value): m_value(value) {}
field(const field<value_type>& rhs): m_value(rhs.m_value),
m_signal_changed() {}
virtual ~field() {}

Are you not risking the value being changed and not having a signal
emitted with the following method.
value_type& get_value() { return m_value; }

const value_type& get_value() const { return m_value; }

void set_value(const value_type& value)
{ m_value = value; m_signal_changed.emit(); }

field<value_type>& operator = (const field<value_type>& rhs)
{ set_value(rhs.m_value); return *this; }

field<value_type>& operator = (const value_type& rhs)
{ set_value(rhs); return *this; }

operator const value_type& () const { return m_value; }

SigC::Signal0<void>& signal_changed() { return m_signal_changed; }
}; // class field

If I provide a method like this:

const value_type *operator -> () { return &m_value; }

this is OK, but for the non-const version, I want to do this:

value_type *operator -> () { return &m_value; m_signal_changed() }

i.e. I want to emit the changed signal /after/ the caller has got the
pointer, used the method/member chosen and finished. If I emit the
signal before, the change won't yet have happened. However, I need to
return the pointer, so this is obviously impossible.

In addition, I'd like to make the const version the default (if a
const method is called from a non-const pointer), if possible, so that
the changed signal is only emitted on a genuine state change.

Are either of these possible using standard C++? If not, could anyone
suggest a different design to achieve the goal (signal emission after
value change)?
I am not sure I completely understood what you want but I will try. Are
you wanted to provide an operator-> for class field so that if T is a
structure you can access the individual fields? If so I don't think you
will be able to do that.

Michael Mellor
 
R

Roger Leigh

Michael Mellor said:
Roger Leigh wrote:
This typedef needs to go before the line (value_type m_value).

For what reason? Is this just a style issue? (Maybe this is just the C
programmer in me declaring everything before it's used?)
Are you not risking the value being changed and not having a signal
emitted with the following method.

Yes, since this is the only possible way to call non-const methods in
contained compounds. If you call this, you need to manually emit the
changed signal (field said:
I am not sure I completely understood what you want but I will
try. Are you wanted to provide an operator-> for class field so that
if T is a structure you can access the individual fields? If so I
don't think you will be able to do that.

This is exactly what I want to do, and I agree with your conclusions.
What I've done is just provide an "operator -> () const", so it's
effectively read-only.

I've found a solution for non-const methods: If the contained class
uses the same signal mechanism as the field class (SigC++), I can
connect the changed signal of the contained class to the changed
signal of the field contained, so that the signal cascades, and I get
the behaviour I want. That is, I listen to the changed notification
and then notify my own listeners. This is useful, since it can nest
arbitrarily deeply for complex contained data structures.


Many thanks to you (and lilburne) for increasing my understanding!


Regards,
Roger
 
M

Michael Mellor

Roger said:
For what reason? Is this just a style issue? (Maybe this is just the C
programmer in me declaring everything before it's used?)
What compiler do you use that allows a type to be used before the typedef?
I get:

$ g++ -Wall -pedantic c.cc
c.cc:4: error: 'value_type' is used as a type, but is not defined as a type.

and

"ComeauTest.c", line 4: error: identifier "value_type" is undefined
value_type m_value;

Michael Mellor
 
R

Roger Leigh

Michael Mellor said:
What compiler do you use that allows a type to be used before the typedef?
I get:

$ g++ -Wall -pedantic c.cc
c.cc:4: error: 'value_type' is used as a type, but is not defined as a type.

and

"ComeauTest.c", line 4: error: identifier "value_type" is undefined
value_type m_value;

My apologies--I moved the private part of the class to the top in my
posting to make the problem clearer. It's actually right at the
bottom. I'm using GCC 3.3.3.

The full class (licence boilerplate stripped) is:
// database field container -*- C++ -*-
#ifndef PQXX_OBJECT_FIELD_H
#define PQXX_OBJECT_FIELD_H

#include <sigc++/signal.h>

namespace pqxxobject
{
/**
* Database field template class.
* This class is used to represent a single field in a row of a
* table. This is a single value belonging to a column in a row,
* rather than the whole column.
*
* As well as storing value, the class has the ability to emit
* signals when the field value is changed. Listeners (e.g. user
* interface widgets) may connect to the signal and will receive
* notification of changes as they occur.
*/
template<typename T>
class field
{
public:
typedef T value_type;

/// The constructor.
field():
m_value()
{}

/// The constructor.
field(const value_type& value):
m_value(value)
{}

/// The copy constructor.
field(const field<value_type>& rhs):
m_value(rhs.m_value),
m_signal_changed()
{}

virtual ~field()
{}

/// Access member functions.
const value_type *operator -> () const
{
return &m_value;
}

const value_type& operator * () const
{
return m_value;
}

/// Overloaded assignment operator.
field<value_type>& operator = (const field<value_type>& rhs)
{
set_value(rhs.m_value);
return *this;
}

/// Overloaded assignment operator.
field<value_type>& operator = (const value_type& rhs)
{
set_value(rhs);
return *this;
}

/// Conversion operator.
operator const value_type& () const
{
return m_value;
}

/**
* Get the contained value by reference.
* @returns a reference to the value.
*/
value_type& get_value()
{
return m_value;
}

/**
* Get the contained value by constant reference.
* @returns a constant reference to the value.
*/
const value_type& get_value() const
{
return m_value;
}

/**
* Set the contained value.
* @param value the value to set.
*/
void set_value(const value_type& value)
{
m_value = value;
m_signal_changed.emit();
}

/**
* Signal emitted on value change.
* @returns the signal.
*
* For example:
* @code
* field<int> col;
* someclass listener;
* col.signal_changed().connect
* ( SigC::slot(listener, &someclass::eek:n_col_changed() );
* @endcode
* i.e. a class method (listener.on_col_changed()) will be called
* when the value is changed.
*/
SigC::Signal0<void>& signal_changed()
{
return m_signal_changed;
}

private:
/// The contained value.
value_type m_value;
/// The changed signal.
SigC::Signal0<void> m_signal_changed;

}; // class field

}; // namespace pqxxobject

#endif // PQXX_OBJECT_FIELD_H
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,796
Messages
2,569,645
Members
45,371
Latest member
TroyHursey

Latest Threads

Top