Introduction:

 

It is well known, but perhaps less commonly admitted, that the greatest challenge facing modern software development is the terribly finite space between any coder's ears.  The flood of information about how to write code (for C++ alone there's Schildt, pages 1 -> 1000; plus Meyers, 90 ways and counting; plus dozens of tidbits on the web; and so forth) leaves us less room than we'd like to concentrate on the application and the needs of the people that will be using it.

pseudoPODs convert what appear to be simple reads and writes of POD (Plain Old Data) members of a class to get() & set() calls, so a coder using a class no longer has to remember what members are POD and what are get() & set() calls, nor how each class spells get() & set() (possibilities include get_thing(), GetThing(), etc.).

 

Using POD (Plain Old Data, such as an integer, a long, or a pointer to a character string) that's part of a C++ class or struct is easy, you just define the POD member in the class, then read or write it directly, e.g. for an integer int1 in class fred:

class fred_c {  public:

     int int1;     // int1, an ordinary POD integer.

};

void test_fred_c (void) {

     fred_c fred;  // create an instance of

                             // fred_c named fred.

     fred.int1 = 0;     // set fred.int1 to 0

     ++fred.int1;       // increment fred.int1

     if (fred.int1 != 1) {cout << "ERROR" << endl;}

}

If a member of a class has a constant value, or one that the class' code can ensure is always up to date (such as the length of a file in bytes, which can be updated each time the program writes more data to the file), then it's best to use a POD member because raw reads and writes are fast.

Suppose, however, that a class represented a house, and the house had a motorized garage door whose opening ranged from 0" when fully closed to 80" when fully open.  Using a POD member to represent the door opening won't work, because setting a POD member to 0 won't automagically close the door, we instead have to execute code to tell the motor to open or shut the door.

In C++ the usual way to handle this situation is to substitute get() & set() function calls for the simple POD member, so that rather than reading and writing the POD member using:

i = fred.int1;           fred.int1 = i;

we would instead use:

i = fred.get_int1();     fred.set_int1(i);

 

This approach has some disadvantages.

The code is harder to read, because we, for example, use:

fred.set_int1(fred.get_int1()+1);

rather than:

++fred.int1;

Designing a class is harder, because we have to decide in advance what elements of the class should be POD and what elements should be handled with get() & set() functions.

Using the class is harder, because we have to remember what elements are PODs and what are get() & set() functions, and write the code that reads and writes the elements differently for each type.

Maintaining the code is harder, because if we have to change an element of the class from POD to smarter get() & set() functions, we not only have to change the code within the class, we also have to change code in all the places outside the class that read or write the element of the class.

 

In short, the get() & set() approach violates the principle of modularity by making changes within the class' implementation visible to the code outside the class.  Whether the member is smart or dumb is a detail of the internal implementation of the class, which should ideally be invisible to the users of the class.

Development History

 

I disliked the get() & set() function approach from the time I first learned C++, and off and on over the years looked for a way of getting around it.  Eventually, while wandering through a library in east central Illinois, I found a dusty book with yellowing pages that lacked the polished uniformity of modern texts, that appeared to have been written as C++ was being developed, and in some cases even explained why C++ works the way it does.  It told of something called nested classes, which, from the point of view of the coder using the class, were read and written as though they were PODs in the class, but which called functions instead of simply reading and writing values in PODs.  This didn’t solve the whole problem (for example, the functions called in the nested class are intentionally blocked from accessing data in the enclosing class), but it did provide the essential element I needed, that of invisibly converting what appears to be a simple read or write of a POD to a function call.

I wrote experimental code to verify that this technique worked, found workarounds for some problems, extended the technique to handle more data types, then wrote macros to reduce the labor required to use pseudoPODs and organized them in a .h file, wrote a couple of thousand lines of test code, and finally documented everything.

The dusty book, by the way, turned out to be not an archaeologist's relic, a forgotten repository of lost lore, but just an unusually old copy of the ARM ("The Annotated C++ Reference Manual" by Ellis & Stroustrup), which was once the primary reference for C++.  It's still in print, and available from amazon.com for as little as 21 cents for a used copy.

 

Oh, So That's What It's Called

 

After I'd finished this paper, I accidentally discovered that what I'd been working on already had a name - adding properties to C++ - and that several pages were already on the web.  As far as I know, however, this is the only paper that makes properties/pseudoPODs easy to use.  In particular:

 

pseudoPODs don't require that the coder learn anything new about C++.  To convert a class member from POD to pseudoPOD, the coder need only:

1) include the pseudoPOD.h file, i.e. #include "pseudoPOD.h"

2) change the POD declaration to a macro call, e.g. change

int int1;           to                ppod(fred_c, int, int1);

3) add a line to the class constructor, e.g. int1.ep=this;

4) write the same get() & set() functions that he'd have to write anyway.

 

pseudoPODs overcome the barrier that prevents nested classes from accessing data in the enclosing class, without making the coder think about the barrier, so the scope of his get() & set() functions includes all the data in the class that they could normally access, a detail that most papers on this subject ignore.

 

pseudoPOD Pros:

 

Improves modularity & abstraction by hiding whether the internal implementation of the member is POD or smart (POD => Plain Old Data, smart => executes code to read or write the member).

Simplifies writing code using the class, since you don't have to remember what members are POD and what ones are smart, i.e. whether you have to read & write PODs or call get() & set() functions.

Better code readability, since you use:

fred.int1 = 0;

not

fred.set_int1(0);

++fred.int1;

not

fred.set_int1(fred.get_int1()+1);

--fred.int1;

not

fred.set_int1(fred.get_int1()-1);

fred.int1 += 10;

not

fred.set_int1(fred.get_int1()+10);

fred.int1 -= 10;

not

fred.set_int1(fred.get_int1()-10);

The writer only has to remember "fred.int1", not whether the Get() call was fred.getint1() or fred.get_int1() or fred.GetInt1(), so is less likely to spend time looking through the instructions for the exact call format.

To the reader the "=" sign in fred.int1=0; more intuitively suggests that "0" is being copied to int1's state.

Simplifies defining the class, since there's less pressure to decide in advance what members should be POD and what members should be smart.

Reduces the cost of changing a member from POD to smart, since the code changes only within the class, not at each and every place the member is read or written (with some exceptions, such as variadic argument lists, see Possible Code Changes: below).

Simplifies debugging, because you can add functions to monitor how a POD member is used without changing code outside the class.

Legacy Code, Invisibly Mended - can be particularly useful with legacy code using C style structs, because you can replace the raw reads and writes of the struct members with function calls without modifying the legacy code, except for converting the struct's definition to a C++ style class.

Easily coded - requires 1 additional line to include the pseudoPOD.h file, plus 1 line per pseudoPOD member, beyond that already required for the familiar get_int1() & set_int1() function calls.

The coder writing the code for the class doesn't have to learn a new style of coding, because the pseudoPOD calls are converted to the familiar get() & set() calls, e.g.

fred.int1 = 35;

is converted to

fred.set_int1(35);  , and

i = fred.int1;

is converted to

i = fred.get_int1();   .

The familiar get() & set() calls retain their normal scope, that is they can directly read and write all the members of the class as they normally do, and the code in the class can continue to refer to a pseudoPOD member named int1 as "int1" rather than int1.value or some other variation.

Uses ordinary C++, so should work with all compilers (tested with gcc. MSVC, and IBM Rational Purity).

Handles all 13 assignment operators (=,++, --, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=), without consuming additional memory unless you actually use them.

Handles pointers, arrays, and chaining.

Includes 2000 lines of test code, thereby providing both coder and author with some confidence that this unorthodox technique works.

 

pseudoPOD Cons:

 

Speed - should be nearly as fast as visible get() & set() function calls, since most of the code is inline, so will be handled at compile time, not executed at run time.  However, each read or write of a pseudoPOD (e.g. fred.int1=3; or i=fred.int1;) will require an additional instruction to load a pointer.

Memory - consumes more data memory, one pointer per smart member per instance.  To me this doesn't matter much, since I don't have many working copies of a class in existence at once (when an app requires many copies, e.g. remembering all the files in a tree of files, I store nearly all the copies in a customized database that compresses the pointers out of them).  Be warned, however, that arrays require one pointer for each element of the array, so should be used more cautiously than individual pseudoPODs.

Recoding - in a few cases, such as variadic argument lists, you'll have to change some of the code outside the class.  See Possible Code Changes: below.

 

Alternative Methods:

 

Some compilers, such as MSVC, have ways of adding properties to class members, but they are unique to each of those individual compilers, so using them locks your code to a particular compiler.  The problem of combining code written by different people is already severe, I'd rather not compound it by requiring that different files of code be compiled by different compilers before they're linked together, so prefer to stick with code that works with all compilers.

Some languages, such as C#, have built-in support for properties, but C++ is more commonly used and more widely supported.