The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next
Sponsored Link

The C++ Source
As Simple As Possible?
An Editorial
by Chuck Allison
August 17, 2005

Page 1 of 2  >>

Advertisement

As Simple As Possible?
Summary
While the C++ Standards committee is about midway through formulating the next official version of C++, Chuck ponders the relationship between power and complexity.

I recently spent a month teaching advanced C++ and introductory Python at the world's fourth largest software company. The C++ students were sharp, experienced developers. The Python students were talented testers but fairly new to development. Naturally I couldn't keep myself from comparing these two important languages, as well as the two teaching experiences overall.

This was the first time I taught Python to non-programmers—my usual audience is my senior Analysis of Programming Languages class. It is also the first time I ever had students thank me as a class for each day's experience; they sincerely thanked me after each of the eight sessions, and even applauded on the last day. While I'd like to think that they were applauding me, I know that they were also applauding Python itself. It is easy to learn, yes, but it is also very powerful. Lists, tuples, and dictionaries are built-in abstractions, and the syntax is so clean it squeaks. On day one we were doing what it takes a std::map to do in C++ (and remember these were non-programmers). To show Python's object-oriented power, here's a nifty little example I got from Kent Johnson on one of the python.org mailing lists�

Like Smalltalk, Python supports class methods, which differ from static methods in that the exact dynamic type of the object being operated on is an implicit parameter to the method (as a type object). The following example keeps a separate per-class object counter for every class in a hierarchy (in this case, a Shape hierarchy):

class Shape(object):
    _count = 0	# A shared value for Shape classes with no current objects
  	
    @classmethod
    def _incr(cls):
        cls._count += 1         # Create/update class attribute
      
    @classmethod
    def showCount(cls):
        print 'Class %s has count = %s' % (cls.__name__, cls._count)
        
    def __init__(self):		# A constructor
        self._incr()

class Point(Shape): pass	# An empty subclass of Shape
class Line(Shape): pass		# Ditto

This requires some perusing if you're new to Python. Python requires indentation to show logical subordination (whereas good programmers of other languages indent by convention), so you can readily see that the class Shape has three methods, _incr, showCount, and __init__. The latter is the special name used for constructors. The other two methods are class methods (indicated by the @classmethod prefix), so their parameter is the unique class object for the instance on which they are invoked. (The term "class object" here refers to a unique, bona fide object that describes a class type, similar to, but more robust than, std::type_info objects in C++.) The method named _incr is only called by the constructor.

Now consider what happens when the following lines execute:

p1 = Point()
p2 = Point()
p3 = Point()
Point.showCount()
Line.showCount()
x = Line()
Line.showCount()
The variable p1 is bound to a new instance of Point, whose constructor calls self._ incr(Point). (The implicit parameter I have named self is just like this in C++, except it must appear explicitly in the method's parameter list.) Since _incr is a class method, it implicitly receives the class object for Point in its cls parameter, p1 being bound to a Point object. The statement
cls._count += 1
is equivalent in this case to
Point._count = Point._count + 1
The first time _incr executes on behalf of the Point class object, the attribute Point._count doesn't exist, so the expression Point._count on the right-hand side of the assignment accesses Shape._count (which is 0) by inheritance. The assignment then creates a new attribute named _count inside the Point class and initializes it to 1. At this point, Point._count is 1, and Line._count doesn't yet exist (all attributes are bound at runtime in Python). When p2 is created, Point._count already exists, so it is incremented. Calling Line.showCount() the first time refers to cls._count, which is really Line._count, but no such attribute exists yet in the Line class, so the _count attribute in Line's base class, Shape (still 0!), is used. When the Line variable named x is created a few lines down, Line._count is created and initialized to 1, just as Point._count was earlier. The final output of this code is:
Class Point has count = 3
Class Line has count = 0
Class Line has count = 1

After three years of study, I have concluded that Python is about as simple as a full-powered object-oriented language can get. My inner programmer just loves it. In a recent interview Scott Meyers was asked which language he thought would be ideal for introducing programming to novices. He replied:

"� a first language should foster a feeling of power and accomplishment out of the box — it should get people excited about the limitless things that can be accomplished in software. Among other things, such a language would offer an extensive and powerful library�" [1]

I know of no language to which these words apply more than Python.

A C++ implementation of this counting idea is none other than the canonical example for a Curiously Recurring Template Pattern (aka a Self-parameterized Base Class), and goes like this:

// A base class that provides counting
template<class T> class Counted {
  static int count;
public:
  Counted() {
    ++count;
  }
  Counted(const Counted<T>&) {
    ++count;
  }
  ~Counted() {
    --count;
  }
  static int getCount() {
    return count;
  }
};

template<class T> int Counted<T>::count = 0;

// Curious class definitions
class CountedClass : public Counted<CountedClass> {};
class CountedClass2 : public Counted<CountedClass2> {};

Both CountedClass and CountedClass2 have inherited their own copy of a variable named count, because their base classes are different; e.g., CountedClass inherits from Counted<CountedClass>. This seems circular at first, but it works because the size of Counted<T> does not depend on T. The following test driver verifies that things work as expected:

#include <iostream>
using namespace std;

int main() {
  CountedClass a;
  cout << CountedClass::getCount() << endl;    // 1
  CountedClass b;
  cout << CountedClass::getCount() << endl;    // 2
  CountedClass2 c;
  cout << CountedClass2::getCount() << endl;   // 1
}
Which version do you find easier to grok? If you're not used to dynamically-typed languages, you may prefer the C++ version, but I think the Python version has a more out-of-the-box feel. (C++ novices don't learn templates, let alone CRTP, in their first week.)

Page 1 of 2  >>

The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us