![]() |
Properties in C++ If you've programmed under Borland's C++Builder, you are familiar with the concept of properties as provided through the __property keyword. Basically, properties allow accessor functions to be associated with class or struct attributes. In C++Builder component properties also have design time ramifications as they can be preset directly by tools in the development environment. Most designers and programmers agree that the concept of properties is very useful, but many don't like the idea of extending the C++ language by adding new keywords. This article explores the question of whether or not it is practical to provide the basic functionality of properties within the standard C++ language? The main benefit of using accessor functions over allowing clients direct access to member variables is the ability to change the underlying implementation, without changing the client's interface. To be powerful, properties must provide this benefit as well as giving their user a consistent interface that is as close to using member variables as possible. Properties must also be type-safe (which includes const-safe). The following template class has been proposed as a way to achieve much of the property functionality. It is based on the implementation given by Colin Hastie in the Design Patterns column in C++ Report, November-December, 1995.
The Property<T> class overloads operator() to provide accessor functions. All functions are inline to minimize overhead (for a lightweight, but less extensible implementation, the virtual functions may be eliminated their functionality rolled into the accessor functions). Both default and copy constructors are provided, implicit conversions via the copy constructor are not allowed. Also restricted is the use of the assignment operator. This is important as it forces all calls which set the contained variable to go through operator()(const T&). It is straightforward to use the Property template. For example the following code uses several:
The code above causes the following to written to stdout:
Notice in the code above that const Property<T> is allowed and that since Property<T>operator()(const T&) is a non-const member function, the compiler will complain if it is called on a const Property<T>. The only rub here is that some compilers only issue a warning in this case when an error is called for. Your first thought to fix this problem might be to try Property<const T>. Unfortunately this solution will not compile as Property<T>::setter(const T&) is not allowed to modify t_ if it is const. If you are using the simpler version of the Property template without virtual functions, it is Property<T>::operator()(const T&) which will not compile. Also notice that to modify a property you must go through operator()(const T&). Such is the case with salary in the program above. This requirement causes the Property template to act less like member variables than we'd like. It also leads to code which does a lot of copying of values (in the code above, salary is returned by value, modified, then passed back by reference to be copied to the private variable in the Property). Although the Property template's primary use is for simple variables it will also work for containers:
Of course a container of properties will not compile if the container template requires its parameter to provide an assignment operator:
Extending the Accessor Functions What if we want to take advantage of the flexibility of accessor functions by changing their behavior without, of course, the client code being any the wiser? The Property template doesn't exactly shine in this case. First the good news: Simple extensions can be accommodated by the Property template. For example, to provide a function that can tell clients how many times a property has been accessed you only need to derive a new type of Property template from the one given above and override the getter and/or setter functions:
The new type, CountedProperty<T>, takes advantage of the protected state of t_ and overrides the virtual accessor functions in Property<T>. It also offers a new function to extract the access counts to an ostream. Note that gcount needs to be declared mutable as it is modified inside of a const member function. Note that when extending the accessor functions by changing the property type, client code will have to be recompiled. Now the bad news: If the extension requires modifications to the meaning of the property, the Property template falls short. Say you had created a class which contained a value property which held an amount of money:
If you wanted to modify the class to use a binary coded decimal implementation while still offering the same interface, namely a function, value() which returns a the underlying amount as a double and a function, value(const double&) which sets the underlying amount, you could not simply derive a new Property template. The basic problem is that the type of the underlying implementation variable is tied directly to the public declaration of the property. The only way to accomplish the task above is to remove the Property template and replace it by a private implementation and public accessor functions:
In this case, starting with the Property template and extending the accessor functions by going back to simple accessor functions tied to a private member variable, client code will also have to be recompiled. If you had started with simple accessor functions and a reference (pointer) to an implementation class (utilizing the Bridge design pattern), changing the behavior of the accessor functions would only require client code to be relinked. More Limitations There are even more limitations than mentioned above when using the Property template. First, a Property parameter cannot be a class or struct that contains properties itself. For example the following code will not compile:
The problem with this code is that the Property<EmpRecord> type doesn't have member functions with the signatures void name(const string&) and void salary(const double&), it only has void operator()(const EmpRecord&). Even if we try to do something in the Employee constructor as pathological as the following, the code still won't compile:
This time the problem is that compiler cannot generate an assignment operator for EmpRecord because to do it's job of memberwise assignment, EmpRecord::operator=() would need to call Property<string>::operator=() and Property<double>::operator=() which we expressly forbade (remember?!) Second, since Properties seem to look and act like member variables, it becomes tempting to treat them like member variables (if it looks like a duck, quacks like a duck, ). Although Sam.name = "Samuel"; won't compile because of the restricted assignment operator (and the explicit copy constructor), what happens if a client tries the following?:
This code compiles fine, but since Property<string>::operator()() returns the member variable t_ by value, it is the returned temporary object which is modified by the assignment not Sam.name.t_, ouch! Wrap-up So, what's the bottom line? Using accessor functions can help to make classes more easily extensible while maintaining an interface for clients. For simple variables the lightweight Property template (the version without virtual functions) can be appealing, but it does force clients to understand its use and limitations. Further it does not succeed in allowing clients to be able to treat properties like member variables. Ironically, the Property template becomes even less appealing when you want to actually extend the behavior of the accessor functions. Extending the Property template requires the use of virtual functions and only works for simple modifications. Even moderately complex extensions require the programmer to abandon the Property template and go back to supplying accessor functions directly in the class. The Property template is a simple example of the Adapter pattern. If you'd like to learn more about C++ or Design Patterns see our course catalog.
Copyright© Pantheon Systems, Inc., All rights reserved. |