About Us
Education
Consulting
Gifts
Contact Us
Employment
Journal
Journal

Home

The Pantheon Systems Journal

Properties in C++
By Joe Gilray


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.

template <class T> class Property
{
public:
Property () {}
    explicit Property (const T& t) : t_(t) {}
    T operator() () const {return getter();}
    virtual T getter() const {return t_;}
    void operator() (const T& t) {setter(t);}
    virtual void setter (const T& t) {t_ = t;}

protected:
    T t_;

private:
    const Property<T>& operator= (const Property<T>&);
};

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:

class Manager; // forward declaration
class Employee
{
    friend ostream& operator<< (ostream& os, const Employee& emp);
public:
    Employee (const string& n, const double& s): boss(0),name(n),salary(s), permanent_id(count++) {}
    Property<const Manager*> boss;
    Property<string> name;
    Property<double> salary;
    const Property<int> permanent_id;
private:
    static int count;
};
int Employee::count;

class Manager : public Employee
{
public:
    Manager (const string& n, const double& s) : Employee(n,s) {}
};

ostream& operator<< (ostream& os, const Employee& emp)
{
    os << emp.name() << "--> Boss: " << emp.boss()->name() << ", ID: ";
    os << emp.permanent_id() << ", Salary: " << emp.salary() << endl;
    return os;
}
void main(int argc, char** argv)
{
    Manager Bill("Bill", 75000.00);
    Manager Kathy("Kathy", 70000.00);
    Employee Don("Don", 45000.00);
    Don.boss(&Kathy);
    Kathy.boss(&Bill);
    Bill.boss(&Bill);
    cout << Don;
    Don.name("Donald");
    Don.salary(Don.salary() + 2500.00);
    // Don.salary += 2500.00; // won't compile
    // Don.permanent_id(12); // compiler will complain
    cout << Don;
    cout << Kathy;
    cout << Bill;
    cout << *Don.boss();
}

The code above causes the following to written to stdout:

Don--> Boss: Kathy, ID: 2, Salary: 45000
Donald--> Boss: Kathy, ID: 2, Salary: 47500
Kathy--> Boss: Bill, ID: 1, Salary: 70000
Bill--> Boss: Bill, ID: 0, Salary: 75000
Kathy--> Boss: Bill, ID: 1, Salary: 70000

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:

Property<vector<int> > weekly_lottery_tickets;

Of course a container of properties will not compile if the container template requires its parameter to provide an assignment operator:

vector<Property<string> > pet_names; // won't compile

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:

template <class T> class CountedProperty : public Property<T>
{
public:
    CountedProperty () : gcount(0), scount(0) {}
    explicit CountedProperty (const T& t)
     : Property<T>(t), gcount(0), scount(0) {}
    virtual T getter () const {gcount++; return t_;}
    virtual void setter (const T& t) {scount++; t_ = t;}
    void print_counts (ostream& os);
    int get_gcount () const {return gcount;}
    int get_scount () const {return scount;}
private:
    mutable int gcount;
    int scount;
};
template <class T> void CountedProperty<T>::print_counts (ostream& os)
{
    os << "# of sets: " << get_scount();
    os << " # of gets: " <<get_gcount() << endl;
}
class Employee
{
    // ...
    CountedProperty<double> salary;
    // ...
};

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:

class Portfolio
{
public: // ...
    Property<double> value;
};

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:

class Portfolio
{
private:
    BCD val;
public: // ...
    double value() const {return_as_double(val);}
    void value(const double& d) {set_val_from_double(d);}
};

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:

struct EmpRecord
{
    Property<string> name;
    Property<double> salary;
};
class Employee
{
public:
    Employee (const string& n, const double& s) {
        record.name(n); // problem here
        record.salary(s); // and here
    }
    Property<EmpRecord> record;
};

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:

Employee (const string& n, const double& s) {
    EmpRecord tmp;
    tmp.name(n);
    tmp.salary(s);
    record(tmp); // now the problem is here

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?:

Employee Sam("Sam", 42000.00);
Sam.name() = "Samuel"; // instead of Sam.name("Samuel");

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.


Top
Journal Archives | Send Feedback

Copyright© Pantheon Systems, Inc., All rights reserved.