Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 35.  Properties


35.3. Field Properties

There are four kinds of field properties, representing read or write access, and internal or external implementation. These are represented in the four classes, field_property_get, field_property_set, field_property_get_external, and field_property_set_external, which we'll look at now. Obviously there's no point combining field property get and set accesses, for example, field_property_getset and field_property_getset_external, since that is semantically equivalent to a public variable, and would be a grand waste of effort.

35.3.1 field_property_get

Let's look at how we might implement a read-only field property. Consider that we have a linked list class, and we want to provide a Count property, to indicate how many items are in the list. In any sane list implementation,[3] the count of items is maintained as a member variable, as opposed to conducting a count by walking the list. Since it makes no sense to assign to the count of a list, Count will provide a read-only view onto a private integer member variable. We could implement this as shown in Listing 35.4.

[3] For an insane one, refer to more of my misdemeanors in Appendix B.

Listing 35.4.


class ListElement;


class LinkedList;





class LinkedListCountProp


{


public:


  operator int() const


  {


    return m_value;


  }


private:


  friend class LinkedList; // Allow LinkedList access to m_value


  int m_value;


};





class LinkedList


{


// Properties


public:


  LinkedListCountProp   Count;


// Operations


public:


  void Add(ListElement *e)


  {


    . . .


    ++Count.m_value; // Update the count


  }


};



The property Count is defined as of type LinkedListCountProp, and is a public member variable of LinkedList. It looks a little strange, since there's no m_ prefix,[4] and it's uppercase. This is my naming convention for properties, in order that they are disambiguated from member variables; you're free to choose your own. For example, if you need them to appear as public member variables, for generalization, then they'd be defined according to the convention of the type(s) you're emulating.

[4] Which may not be much of a loss to most of you. It's not exactly a popular notation, though I suspect that's for political more than technical reasons (see Chapter 17).

Client code can refer to the Count member of instances of LinkedList, and in that way access the (int) value, via the implicit conversion operator. Client code cannot change the value of LinkedListCountProp::m_value because it is private, but the friendship declaration means that it can be altered by Rectangle. Simple really, isn't it?

The downside is all too clear. LinkedListCountProp is a purpose-written class, and an equivalent would be required for every containing class and every value type. Unless we want a lot of typing, code-bloat, and maintenance headaches, we want a generalized solution. Naturally, templates are the answer, in the form of field_property_get, which is shown in Listing 35.5.

Listing 35.5.


template< typename V   /* The actual property value type */


        , typename R   /* The reference type */


        , typename C   /* The enclosing class */


    >


class field_property_get


{


public:


  typedef field_property_get<V, R, C> class_type;


private:


  // Does not initialize m_value


  field_property_get()


  {}


  // Initialises m_value to given value


  explicit field_property_get(R value)


    : m_value(value)


  {}


  DECLARE_TEMPLATE_PARAM_AS_FRIEND(C);


public:


  /// Provides read-only access to the property


  operator R () const


  {


    return m_value;


  }


private:


  V m_value;


// Not to be implemented


private:


  field_property_get(class_type const &);


  class_type &operator =(class_type const &);


};



The template takes three parameters: the type of the internal member variable, the reference type by which its value may be accessed, and the type of the containing class. The separation of value type and reference type increases flexibility and efficiency. For example, if you were using std::string as the value type, you would likely choose to use std::string const& as the reference type.

As with the specific case of LinkedListCountProp, read-only access is achieved by making the member variable m_value and the constructors private, but providing a public implicit conversion operator to the reference type.

Note that the default constructor does not perform explicit initialization of the member variable. This is in common with my personal predisposition to making efficiency a high priority, and that unnecessary initialization is a waste of time. Initialization of the value is achievable by the nondefault constructor. You may take a different point of view, and therefore implement the default constructor to also explicitly default construct the member variable.



field_property_get()


  : m_value(V())


{}



As before, the containing type needs write access to the member variable, which is provided by granting it friendship of the template using the technique we saw in section 16.3.

Using this general class template, we can now define LinkedList as:



class LinkedList


{


// Construction


public:


  . . .


// Properties


public:


  field_property_get<int, int, LinkedList>   Count;


};



There's an added bonus in that the declaration of the property Count is also self-documenting: it is a field-based read-only (get) property, whose value and reference types are both int.

Client code expressions are also readable and unambiguous:



LinkedList  llist(...);


for(. . .)


{


  . . . // Add items to the list


}


int         count = llist.Count; // Gives the count of items





llist.Count = count + 1;   // Error! Write access denied



The copy constructor and copy assignment operator are hidden to prevent nonsensical use such as the following:



field_property_get<int, int, Count>  SomeProp(llist.Count); // Copy ctor





llist.Count = llist.Count;  // Copy assignment



Hopefully you'll agree that we've achieved read-only access in a very simple fashion. On all the compilers (Appendix A) tested, the field_property_get template causes no speed or space overhead, so it is 100 percent efficient. The only downside is that it uses the currently nonstandard, though very widely supported, mechanism of granting friendship to a template's parameterising type.[5] But we're imperfect practitioners, and we want something that works (and is portable). This does (and is).

[5] The other downside, for the code aesthetes, is the use of a macro. I really don't like doing so, but in this case it is the preferred option, since it allows the class definition to be succinct and readable, and therefore comprehensible and maintainable.

35.3.2 field_property_set

I must say that I cannot think of any use for a write-only field property, but we're going to look at the mechanism for pedagogical purposes, as this will inform on our implementation of the write-only method properties (see section 35.4), which do have valid applications.

Write-only properties are the semantic opposite of read-only, so the field_property_set template replaces the public implicit conversion operator with an assignment operator, as in:

Listing 35.6.


template< typename V   /* The actual property value type */


        , typename R   /* The reference type */


        , typename C   /* The enclosing class */


    >


class field_property_set


{


public:


  typedef field_property_set<V, R, C> class_type;


private:


  field_property_set()


  {}


  explicit field_property_set(R value)


    : m_value(value)


  {}


  DECLARE_TEMPLATE_PARAM_AS_FRIEND(C);


public:


  /// Provides write-only access to the property


  class_type &operator =(R value)


  {


    m_value = value;


    return *this;


  }


private:


  V  m_value;


// Not to be implemented


private:


  field_property_set(class_type const &);


  class_type &operator =(class_type const &);


};



This template has the same efficiency, portability, and legality attributes as its read-only brother. Everything gets inlined, and it's conformant code apart from the (highly portable) friend relationship.

35.3.3 Internal Fields Properties: Coda

We've now seen the core aspects of the property technique. Read-only access is achieved by providing a public implicit conversion operator in the property class, and hiding the copy constructor and copy assignment operators to prevent misuse. The containing type is made a friend of the property so that it can set the value, which is otherwise inaccessible due to being declared private.

Write-only access is achieved by providing a public assignment operator, and not providing an implicit conversion operator. The containing type is once again made a friend, this time so it can access the value of the property. For the ultra-cautious (which includes me) it is tempting to declare, but not define, a private implicit conversion operator. We'll see why this is not wise shortly.

For the remaining permutations we'll take these aspects as read (pardon the pun!) and focus on the individual mechanisms.

35.3.4 field_property_get_external

The internal field properties are the best option if we have full control over the implementation of the containing class to which we are adding the properties. However, sometimes this is not the case, and what we seek to do is augment a class with properties based on existing member variables. This is achieved using a reference. Hence, field_property_get_external is defined as:

Listing 35.7.


template< typename V   /* The actual property value type */


        , typename R   /* The reference type */


    >


class field_property_get_external


{


public:


  field_property_get_external(V &value) // Takes a reference


    : m_value(value)


  {}


// Accessors


public:


  /// Provides read-only access to the property


  operator R() const


  {


    return m_value;


  }


// Members


private:


  V  &m_value;


};



Ostensibly, this property looks simpler than the internal equivalent. field_property_get_external is parameterized with the value type and the reference type to the variable, and holds a reference to the value type, which is passed in its constructor.

Since the property object holds a direct reference to the member variable for which it acts, it does not need to know, or be friends with, the containing class. Hence, the template is 100 percent language compliant. Furthermore, it presents no challenge to even very template-deficient compilers; even Visual C++ 4.2 compiles this!

In terms of speed efficiency, the template is amenable to even cautious optimization, such that the notional indirection through the reference can be optimized into a direct reference. The downside is that the reference results in space inefficiency: on 32-bit machines, the reference costs 4 bytes per property. Furthermore the property is a little more complex to use, since it must be initialized with a reference to the variable for which it will act.

Listing 35.8.


class LinkedList


{


// Construction


public:


  Rectangle(. . .


    : Count(m_count)


    , . . .


// Properties


public:


  field_property_get_external<int, int, LinkedList>   Count;


// Members


private:


  int m_count;


};



At first glance, this looks like a member initializer list ordering problem (see section 2.3), but in fact it's not. The reason is that Count is passed m_count as a reference, not as a value. Hence, the only way in which this could itself be an ordering problem would be if Count was being used in another initializer, which would be an ordering problem all its own and doesn't have anything to do with the property per se. Nonetheless, it's worth pointing out, since member ordering dependencies are in principal bad things, and you should be mindful of the pitfalls whenever you (think you) see them.

35.3.5 field_property_set_external

field_property_set_external provides the semantics of field_property_set with the same reference mechanism as field_property_get_external.

35.3.6 Hacking Away

When we look at method properties in the next section we're going to see that there's a way to hack away at the space inefficiencies by using esoteric techniques. While it's possible to do the same for external field properties, I've not done so simply because they are so very rarely used. Of course, they may be so rarely used because they have the space cost, but we're getting philosophical, so you take the chicken and I'll take the eggs, and we'll move on.


      Previous section   Next section