Click here to Skip to main content
15,885,896 members
Articles / Programming Languages / Java / Java SE / J2EE

C++ Property Accessors

Rate me:
Please Sign up or sign in to vote.
4.00/5 (6 votes)
14 Mar 2009CPOL9 min read 51.5K   132   21   14
Shows how to create C++ property accessors that support C#-like syntax

Download my code

No, it's not modern art. It's not a photonegative scan of a bunch of text. It's a *free* DOS app! Put your credit card back in your fanny pack and just hit download...

Introduction

If programming languages were musical artists or bands, what would the music be in the software world? First off, the pervasive Java would be U2. It's everywhere and everyone kind of likes it. C# would be the Rolling Stones, for similar reasons. JavaScript/AJAX would be the new school hip hop -- overplayed. LISP, the language that has a trippy level of self awareness, would be the Grateful Dead or Phish. The entire VB language has been visually altered numerous times, and the end result is much less attractive than the original BASIC. There's a platoon of musicians that map to VB. Fortran is Elvis. Befunge is RadioHead, post-2000. COBOL is the past his prime musician that keeps releasing albums. Let's not name any names!

Although XML is not really a programming language (unless you like to hand-code ANT), I am being convinced by the reptilian parts of my brain to mention it. How did it ever become popular, and why is it still in use? It's like the boy band phenomenon. It didn't start out with a lot of potential (it's SLOW to parse), and it ain't gonna get any better!

What about C and C++? C can be verbose. It's followers tend to want it to stay complex. It can be fun (like when we wrap in C++), but we do things to make it less fun (like keep things in structures and procedures). C is Rush, the masters of "math rock". I'm an old fan and an old C programmer, so I can say this with impunity. In fact, I'm just old, period. The C culture of complexity carries over into C++, and we have additional new code smells to suck all the joy and efficiency out of coding, like template madness and multiple inheritance. How can we make C++ usable to a wider audience? We can start with how we code! We can create C++ code outside of the math rock genre and push it into mass appeal. There are things we can do with code, even under the current C++ standard, to make C++ the Beatles of the software world.

In this article, I will zero in on one programming language concept that makes code more intuitive, and I'll show how to implement it in C++, where it's not directly supported.

Background

I switch between a lot of languages in my work. They all have their pluses and minuses. I always come to the same conclusion when evaluating the merits of a language: the bottom line is intuitiveness. The completeness of the available APIs are important, but APIs can be built around any programming language. So for the purpose of this discussion, I'll stick to intuitiveness or readability as the primary objective. Can you show a block of code to your boss and feel confident that he/she can tell what it does? This depends a lot on the language grammar. Show the boss a block of SQL, and you will have to explain it. Show a block of Java, and it'll probably make some sense to a non-technical audience.

Before I start gushing about a feature of C#, let me first express my concern about the direction the language is taking under the guidance of its one member steering committee. I understand the need for features like lambdas, but in any language, my tiny little brain struggles to follow the logic in the code. I'll deal with lambdas since they seem worthwhile, but it could be what opens the lid to Pandora's box. I'm concerned that Microsoft is going to throw the kitchen sink into the language and promote an ever growing suite of keywords and flow logic that mere humans can't visualize under the 4 dimensional limit. Using my criterion that languages should be intuitive, I just don't see things like Linq inline queries and the new yield keyword in C# as progress. The underlying Linq technology is cutting edge, but it can be used in full without the muddy syntax. Why make C# code look like SQL? It's kind of like a mash-up of the Rolling Stones and thrash metal!

Enough rant. I carry a joyful message I want you to save away in the leftmost node of your brain. There is a language feature in C# that I consider the single largest improvement in that language family since C became C++ or Java became, well, Java. Accessors are something we take for granted now in C#. They're like the bass guitarist -- in the background and underappreciated. What would the music be like without the bassist? It would be less pleasing to the ear. Likewise, classes in languages without real accessors are less pleasing to the eye. In my opinion, accessor-less classes are actually harder to use. Why? Accessors distinguish between property and method, and autocomplete features give you different icons. What is the difference between a property and a method in a class? A property is a way to set and get an attribute on a instance. It's more noun than verb. A method is more of an action, and calling an action implies that more work will happen. If the class is Cup, then LiquidVolume is a property and fill is a method. LiquidVolume is atomic, so calling it involves a very small cost. Calling fill requires more work by the Cup instance or the CoffeePot filling it up.

Since we're talking about math rock, let's stick with math-heavy code. Here's a snippet in C# and Java of our imaginary Cup class:

C#
//
// Some C# code
class Cup
{
  double _diameter,
      _liquidHeight;
  public double LiquidVolume {
    get {
      double radius = _diameter / 2.0;
      return 3.14159 * (radius * radius) * _liquidHeight;
    }
  }

  public double LiquidHeight
  {
    get {
      return _liquidHeight;
    }
    set {
      //validate, throw if necessary
      //...
      _liquidHeight = value;
    }
  }

  ///some stuff not shown for brevity

  public void fill(CoffeePot pot, double pouringTime, double outputVelocity)
  {
    //Do a bunch of calculations that take some CPU cycles
  }
}

//using the class in C#
Cup cup = new Cup();
cup.LiquidHeight = 2;  //By inspection, looks like a prop set
cup.Diameter = 2;      //Ditto!
cup.fill(pot, 100, 50);	//Looks like we're doing some non-atomic operation here, 
			//calling a method!
Trace.WriteLine( cup.LiquidVolume.ToString() );
Java
//
// Some Java code
class Cup
{
  double _diameter,
      _liquidHeight;
  public double getLiquidVolume()
  {
    double radius = _diameter / 2.0;
    return 3.14159 * (radius * radius) * _liquidHeight;
  }

  public double getLiquidHeight()
  {
    return _liquidHeight;
  }

  public void setLiquidHeight(double value)
  {
      //validate, throw if necessary
      //...
      _liquidHeight = value;
  }

  ///some stuff not shown for brevity

  public void fill(CoffeePot pot, double pouringTime, double outputVelocity)
  {
    //Do a bunch of calculations that take some CPU cycles
  }
}

//using the class in Java
Cup cup = new Cup();
cup.setLiquidHeight(2);  	//Is this method with some computations or a simple, 
			//atomic operation?
cup.setDiameter(2);      	//Yeah, ditto that!
cup.fill(pot, 100, 50);  	//Does this do more work internally than the 
			//preceding two lines?
System.out.println( cup.getLiquidVolume().ToString() );

The classes in the snippet illustrate the difference between Java and C# with regard to properties. I don't play Sun versus Microsoft, so take these comments in the proper context. The C# property accessors are an improvement to the language family. It clearly makes code more readable when you have visual cues. This is something every programmer knows already; readability is why we indent and create coding standards.

Note that I follow a non-Microsoft naming convention for classes, methods, and properties. Upper case words in English and similar languages usually denote a noun (like Chris). In German, all nouns begin capitalized. Therefore, it makes sense for classes and properties to be in upper case. Putting methods in lower case, in direct contrast with coding standards used by Microsoft, is a way to distinguish nouns from verbs or actions from attributes. I don't worry about this with local variables, and I've never seen any need when I keep functions short.

The snippet also illustrates that accessors allow you to drop the () when calling a property. This lets the code reviewer to sort out nearly "atomic" operations from those that require more CPU cycles. This may not sound important, but in a loop, it's a critical distinction. You can call a property get accessor from inside a loop, but you should save the result of a heavier method call to a local variable.

Enough About Java and C#! What about C++?

If you program out of a textbook, it's not at all obvious that you can create classes in C++ with accessor syntax exactly like that of C#. I guess few people have ever seen the need, because I've never seen any APIs designed this way. It's actually really straightforward in C++ to support accessor syntax. It's just a bit more verbose than what you do in C#! That's OK for us math rockers. The basic ingredient is using instances of inner classes with overloaded operators. The inner classes are the "accessor" code, and the overloaded operators make the desired syntax possible.

The sample application is a DOS harness around a Trigonometry class. It illustrates how to build accessor classes and how to call into them. Here's the guts of the source code:

C++
#include "stdafx.h"
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <iostream>

using namespace std;

const double PI = 3.141592653589793238462643,
		TWOPI = 2 * PI,
		DEGREE = PI / 180.0;

class Trigonometry
{
public:
	Trigonometry()
		: Sine(_radians), AngleInDegrees(_radians), 
				Radians(_radians), _radians(0.0)
	{
	}

protected:

	//All the accessors share and expose the value of this member variable.
	double _radians;

	///////////////// Utility functions

	static bool approximatelyEqual(double d1, double d2)
	{
		return fabs( d1 - d2 ) <= 0.0001;
	}

	//Ensures that the value of target is 0 < target < 2*PI
	static void setRadians(double &target, const double newVal)
	{
		target = fmod(newVal, TWOPI);
		while( target < 0 )
			target += TWOPI;
	}

	//Exposes the value of _radians in actual radians.
	class RadianAccessor
	{
		double &_target;	//Holds a reference to the variable 
				//in the outer class.
	public:

		RadianAccessor(double& target)
			: _target(target)
		{
		}

		//Allows this class to intrinsically cast to double.
		operator double () const 
		{
			return _target;
		}

		//The "set" accessor function.
		double operator = (double newVal)
		{
			setValue(newVal);
			return value();
		}

		//This is a prefix operator, not postfix.  Increments the radians
		//value by 1.0 modularly.
		double operator ++(){
			setValue(_target + 1.0);
			return _target;
		}

		//Comparison to double operator.
		bool operator == (double d) const
		{
			return approximatelyEqual( d, value() );
		}

	protected:
		double value() const 
		{
			return _target;
		}
		void setValue(double newVal)
		{
			setRadians(_target, newVal);
		}
	};

	class AngleAccessor
	{
		double &_target;	//Holds a reference to the variable 
				//in the outer class.
	public:
		AngleAccessor(double& target)
			: _target(target)
		{
		}

		//Allows this class to intrinsically cast to double.
		operator double () const 
		{
			return value();
		}

		//The "set" accessor function.
		double operator = (double newVal)
		{
			setValue(newVal);
			return value();
		}

		//This is a prefix operator, not postfix.  
		//Increments the angle by one degree.
		double operator ++(){
			setValue(value() + 1);
			return value();
		}

		//Increments the angle by the supplied number of degrees.
		double operator +=(double addend){
			setValue(value() + addend);
			return value();
		}

		//Comparison to double operator.
		bool operator == (double d) const
		{
			return approximatelyEqual( d, value() );
		}

	protected:
		double value() const 
		{
			double res = _target / DEGREE;
			return res < 0 ? res + 360 : res;
		}
		void setValue(double newVal)
		{
			setRadians(_target, newVal * DEGREE);
		}
	};

	class SineAccessor
	{
		double &_target;	//Holds a reference to the variable 
				//in the outer class.
	public:
		SineAccessor(double& target)
			: _target(target)
		{
		}

		//Allows this class to intrinsically cast to double.
		operator double () const 
		{
			return value();
		}

		//The "set" accessor function.
		double operator = (double newVal)
		{
			setValue(newVal);
			return value();
		}

		//This is a prefix operator, not postfix.  Increments the sine
		//value by 0.1.
		double operator ++(){
			setValue(0.1 + sin(_target));
			return value();
		}

		//Comparison to double operator.
		bool operator == (double d) const
		{
			return approximatelyEqual( d, value() );
		}

	protected:
		double value() const
		{
			return sin(_target);
		}
		void setValue(double newVal)
		{
			if( newVal > 1.0 )
				newVal = 1.0;
			else if( newVal < -1.0 )
				newVal = -1.0;
			setRadians(_target, asin(newVal));
		}
	};

public:
	//Accessor that maintains the _radians member by its sine value.
	SineAccessor	Sine;
	//Accessor that maintains the _radians member converted to degrees.
	AngleAccessor	AngleInDegrees;
	//Accessor that maintains the _radians member as radians.  Like all
	//the accessors, it ensures that _radians stays in the 0..2*PI range.
	RadianAccessor	Radians;
};

//A DOS app for trig fans...
int _tmain(int argc, _TCHAR* argv[])
{
	Trigonometry trig;
	//Set the radians by the SINE value.
	trig.Sine = -1.0;	//Note the syntax -- not very C-like.

	//Test assertion.
	assert( trig.AngleInDegrees == 270.0 );
	
	//Try the prefix operator out on SINE
	while( trig.Sine < 1.0 ){
		cout << "sin(" << trig.Radians << ") = " << trig.Sine << endl;
		++trig.Sine;
	}
	cout << "Press ENTER to continue or just wait for your computer to crash: 
		" << endl;
	getchar();

	//Set the radians using degrees...
	trig.AngleInDegrees = 630.0;	//Looks like a simple set member statement,
					// but actually validates
					//in the set accessor...
	assert( trig.AngleInDegrees == 270.0 );	//... and makes sure 
						//_radians < 2 * PI
	for( int n=0; n < 72; n++ ){
		cout << trig.AngleInDegrees << "Degrees => " << trig.Radians 
							<< " Radians" << endl;
		trig.AngleInDegrees += 10;
	}
	cout << "Press ENTER to continue.  Don't worry; it won't burn your fingers: " 
								<< endl;
	getchar();

	return 0;
}

Points of Interest

Admittedly, the accessor class code is a bit long, especially when you start overloading binary operators. You cannot support the unary and binary operations without overloading those operators. I only implemented a couple of operators here for brevity. So why even bother writing accessor classes?

It's a good thing to contemplate whether writing accessors is worthwhile in production code. There is one clear case in which I use this technique in class design -- APIs. If you're re-using code, it needs to be really intuitive how you use a class. Let me give an example. Today, I was working with an API for manipulating file and directory paths in C++. Simply looking at the autocompletion lists in the IDE was not good enough to tell me what function to call on an instance. The only way I could tell a class property from a method was to check for a corresponding 'set' function (almost all methods were const). I had to keep referring to the documentation, which basically expanded the function name into a sentence. Unless these classes are fresh in my memory, their use will slow my productivity. The corresponding classes in the .NET APIs are actually easier to use, not because they're better, but because of clear separation between noun and verb. I rarely need to reference the documentation when using the file and directory classes in C#.

There's a potentially interesting solution for generating C++ accessor classes. You can "metacode" your accessors in a language like Python, run the script as a pre-build step, and #include the meta-generated C++ code. You can also embed scripting code in a #pragma and write a separate preprocessor script to read, evaluate, and expand the #pragma script inline. This allows you to mass produce the operators, filtering the list appropriately. It also lets you solve the syntax enigma thing once and for all with prefix and postfix operators -- postfix has a very interesting syntax.

Conclusion

Elegant, readable code makes us a better investment for the people who pay us. We become more productive when we re-use code that speaks to us. We don't have to stop and rewind a song when the lyrics are clear, and we don't have to kill time searching Google when the APIs we use are intuitive. We should seek ways to make the C++ language easier to use, especially when it doesn't require a new standard to do so. The language itself is just the bottom of the "code stack"; what you do on top of the language's grammar has a dramatic impact with readability. Hey, that's why we all indent, right? If you can do things to maximize the usefulness of autocompletion lists, you will save time from browsing documentation. Separate the nouns and verbs in your classes, and you will re-use those classes over and over. Readable code is like The Beatles' Greatest Hits -- it's going to be around for a very long time.

Rock on!

History

  • Version 1.0, March 2009

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) CDK Global, LLC
United States United States
Director of Software Engineering for a startup software/hardware solution provider based in Silicon Valley. Currently in search of a new position with another company.

Comments and Discussions

 
GeneralProblem [modified] Pin
UsYer18-Mar-09 7:52
UsYer18-Mar-09 7:52 
GeneralRe: Problem Pin
murti38619-Mar-09 23:49
murti38619-Mar-09 23:49 
GeneralRe: Problem Pin
Chris Grimes21-Mar-09 15:27
Chris Grimes21-Mar-09 15:27 
GeneralRe: Problem [modified] Pin
UsYer23-Mar-09 7:25
UsYer23-Mar-09 7:25 
GeneralRe: Problem Pin
Chris Grimes24-Mar-09 18:50
Chris Grimes24-Mar-09 18:50 
GeneralRe: Problem Pin
UsYer25-Mar-09 11:38
UsYer25-Mar-09 11:38 
GeneralC++ Accessors Pin
geoyar17-Mar-09 15:18
professionalgeoyar17-Mar-09 15:18 
GeneralRe: C++ Accessors Pin
Chris Grimes17-Mar-09 17:27
Chris Grimes17-Mar-09 17:27 
GeneralRe: C++ Accessors Pin
geoyar18-Mar-09 6:30
professionalgeoyar18-Mar-09 6:30 
GeneralRe: C++ Accessors Pin
6Qing883-Jun-09 23:05
6Qing883-Jun-09 23:05 
Generalclass size Pin
murti38617-Mar-09 8:13
murti38617-Mar-09 8:13 
thanks, i liked your style of explanation,well read
Once i thought about that, but i ended like you used, some reference/pointer to upper class or variable.
sizeof(mainClass) grown alot.
Any idea?

"As far as the laws of mathematics refer to reality, they are not
certain; and as far as they are certain, they do not refer to reality." Albert Einstein

GeneralRe: class size Pin
Chris Grimes17-Mar-09 17:50
Chris Grimes17-Mar-09 17:50 
General__declspec( property ... ) Pin
cmk15-Mar-09 5:25
cmk15-Mar-09 5:25 
GeneralRe: __declspec( property ... ) Pin
Chris Grimes15-Mar-09 7:08
Chris Grimes15-Mar-09 7:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.