C++ Apologetics

Posted by Daniel Lyons Mon, 11 Jun 2007 06:07:00 GMT

This is my third try to give an opinion about C++. Even having an opinion of this language is hard.

Firstly, C++ suffers from having evolved quite a bit during its life. A lot of the information out there about it is outdated. It’s not hard to find tutorials that have include files ending in .h and not adding a using namespace std; declaration. It’s also not hard to find example code for g++ that doesn’t compile.

Secondly, C++ is huge. Most people don’t use a fraction of its capabilities. A lot of its capabilities are rather new; there is a general fear of using new features for loss of compiler compatibility, which has been a problem for a long time with C++ because of the general size and complexity of the language. Even though it adds an additional pile of keywords onto C, the keywords it adds sometimes have different meanings in different contexts (virtual is the prime offender). In other places, you have to do some truly strange things to get C++ to do as you intend, pure virtual functions being defined with a tailing “= 0” in the class definition being the best example.

Partly because C++ is huge and partly because it has been around for so long, students are not exposed to all of it, or even necessarily the important parts of it. It takes a long time to learn, and with outdated or poor information at their disposal, people pick up bad habits. The popularity of the language only makes this a bigger problem.

Thirdly, and perhaps most importantly, C++ strives to maintain compatibility with C. This explains many of the faults of the language, especially the type/class dichotomy.

The language D is clearly trying to reimagine C++ without so much of the evolutionary baggage. That’s a noble goal, but by this late stage C++ compatibility is a big concern also. If you sacrifice that, you might as well sacrifice some of the other constraints and wind up with something more like OCaml.

Finally, C++ suffers from having some rather brain-dead defaults. The virtual declaration must be used extremely liberally to achieve what’s normally understood as OO—calling a function with a pointer to a parent class and having the instance’s class code get executed. The explicit keyword exists solely because the language is very friendly with implicit casting.

The more I learn about C++ , the more hilarious I find claims of Java’s superiority. If Gosling is such a genius, why did generics get added to Java so late in the game, mirroring evolutionarily their late addition to C++? I haven’t looked at it very closely, but I have trouble imagining that in Java you can do anything as cool as a templated average function which can average across any numeric-looking type in any data structure for which sequential access is possible.

I also remembered today one reason why I wanted to learn C++ back in the day: BeOS is completely written in it. Right now the only reason I can think of to use it would be to use ICE. For my purposes, C++ is basically defunct, unless I want to program in QT for some reason. Even that I’d have a hard time convincing myself to do, since I don’t care much to program for Windows or (frankly) Linux/BSD.

So again I’m having trouble trying to summarize my opinion. Could C++ have been a great language? Probably not, but it’s pretty good for what it’s trying to do. It’s not the fastest language on the planet, but it’s quite fast, often faster than its detractors claim. Much of the slow C++ out there is not using STL algorithms and data structures. Manual memory management is time consuming for programmers and the allocator thrashing can be more expensive on the processor than garbage collection, but this is largely a concession to C, and you can apparently add your own garbage collector in the form of a library anyhow. It has one of the most expressive inheritance mechanisms, perhaps second only to Eiffel’s, but comes at a cost of being harder to use (again the virtual keyword makes life interesting, along with the access control public/private/protected nightmare). It’s possible now, with the template system, to write terse, expressive code, but almost no one does, learning how is hard, and it produces code that’s quite hard to read. If you need speed, there are faster languages; if you need elegance, there are more elegant languages. Perhaps there is no alternative if compatibility is what you optimize for, but it’s hard for me to imagine wanting to start a new project in it. A truly mixed bag.

Tags , ,  | 1 comment

C++ "Generic" Programming Conundrum

Posted by Daniel Lyons Sat, 09 Jun 2007 20:01:00 GMT

Looking at C++, you sometimes see cool-looking code like this:

// load the numbers in the input file
copy(istream_iterator<double>(input), 
     istream_iterator<double>(), 
     back_inserter(numbers));

// get the sum
double sum = accumulate(numbers.begin(),
                        numbers.end(), 
                        0.0);

// compute the average
double average = sum / numbers.length();

Of course, this traverses the vector twice, so you might think of a better way to do it would be to do a right fold across the vector, adding to the sum and incrementing the count until the vector is exhausted, and then do the division. You know, take this kind of code (untested):

double sum = 0;
int count = 0;
for (vector<double>::iterator i = numbers.begin(); 
     i != numbers.end(); 
     i++)
{
  sum += *i;
  count++;
}
double average = sum / count;

In OCaml or Haskell, we’d consider converting it into a right fold along these lines:

let average l = 
  let acc (sum, count) x = (sum + x, count + 1) in
  let (sum, count) = List.fold_right acc l (0,0) in
    sum / count

But this is C++, so instead of getting a closure or a fold function, we get accumulate, which takes a start and ending iterator and an initial value. It works just like a fold, but uses operator+ if you pass in an object. So, we can create an averaging functor like this:

class averaging_accumulator : 
  public binary_function<double,
           averaging_accumulator<double>,
           averaging_accumulator<double> >
{
public:
  int count;
  double sum;

  // constructor
  averaging_accumulator() : sum(0), count(0) {}

  // compute the actual average
  double average()
  {
    return sum / count;
  }

  // implement the binary operation between the two types
  averaging_accumulator operator+(double d)
  {
    sum += d;
    count += 1;

    return *this;
  }
};

So we can now implement an average with something along these lines:

averaging_accumulator acc;
double average = accumulate(first, last, acc).average();

Of course, once we have this working we might want to make it work for floats also. So we make it into a template:

template <typename T>
class averaging_accumulator : 
  public binary_function<T, 
    averaging_accumulator<T>, 
    averaging_accumulator<T> >
{
public:
  int count;
  T sum;

  averaging_accumulator() : sum(0), count(0) {}

  // compute the actual average
  T average()
  {
    return sum / count;
  }

  // implement the binary operation between the two types
  averaging_accumulator operator+(T d)
  {
    sum += d;
    count += 1;

    return *this;
  }
};

Another thing we might want is to make a function to compute the average. And naturally we’d want to make it a nice generic function along the lines of the builtins. The best example of that kind of programming I could find led me to write this:

template<typename InputIterator, typename T >
T average(InputIterator first, InputIterator last)
{
  averaging_accumulator<T> acc;
  return accumulate(first, last, acc).average();
}

Now, what I don’t understand is that I have to call this function like so:

double avg = average<vector<double>::iterator, double>
(numbers.begin(), numbers.end());

Why do I have to include the iterator type and result type, when the internal call to accumulate does not? I couldn’t get it to compile any other way. Same thing with the other generic functions copy and transform—you never need to tell them what iterator type they’re receiving. I’d rather be able to call it like this:

double avg = average(numbers.begin(), numbers.end());

If anybody knows how to do this “the right way,” please let me know.

Update: I’ve figured out the way to do this. Replace your average function with this:

template<typename InputIterator>
typename iterator_traits<InputIterator>::value_type
average(InputIterator first, InputIterator last)
{
  averaging_accumulator<typename iterator_traits<InputIterator>::value_type> acc;
  return accumulate(first, last, acc).average();
}

In short, instead of having a type parameter T, derive it from your iterator’s type using iterator_traits. That class is parameterized by an iterator and provides a number of different type definitions, but the only one you’ll care about is value_type. Due to changes in the C++ standard, you now need a prefix typename before a template expanded type name like that, so your complete type is going to look like typename iterator_traits<InputIterator>::value_type as in the above code. Then, the compiler will be able to figure out the template parameters (but only for functions, not classes). If you have a longer function and you need the value_type more than once, you can make a typedef local to this function.

Tags , ,  | no comments

Four Times Annoyed

Posted by Daniel Lyons Sun, 26 Nov 2006 07:59:24 GMT

This last week I got some interesting bad news about my eyes: apparently my pupils over-dilate in darkness or semi-darkness. I have my first old-man disorder at the ripe old age of 25. On long drives at night I will have to turn the dome light on to prevent my eyes from perceiving every light source as a giant luminous splotch with an echo above or below it. This is also why I needed new glasses recently, but why I didn’t notice until I left my job and started working nights. There is no cure for this problem except time; as I age my pupil dilation should get less responsive, which will counteract this problem.

I just finished reading Demian by Hesse. A great book, except for all the cult stuff near the end. There are a number of passages that I really identified with, but the strongest was this:

“Sometimes when I ran through the streets in the evening, unable to return before midnight because I was so restless, I felt that now at this very moment I would have to meet my beloved—as she walked past me at the next street corner, called to me from the nearest window. At other times all of this seemed unbearably painful…”

I was very annoyed that I could identify so thoroughly with a character who goes on to basically endorse Anton LaVey satanism. Tsk-tsk.

I was thinking today about programming in Erlang and how much I enjoy it, but at the same time, though I enjoy my big functional languages, not one of them has a decent Mac library; most of them don’t even have decent Unix GUI libraries or web frameworks. Erlang at least has a promising-sounding web framework but I am tired of web programming for the moment. It’s so very occupational.

I became annoyed thinking about OCaml and how much I had liked it. OCaml is basically the marijuana of functional languages—you start there, and then you get into the heavy stuff like Lisp, Haskell and Erlang. Or else you remain trapped with OCaml, perhaps consuming liters of the Russian vodka of languages—C++—at the same time. “Well, it could be worse.” Yes.

I am disappointed that nobody has anything to say about my quicksort post. I suspect that one is for the ages, and in a couple years, someone will be quite glad it’s there. Nobody pointed out that I am doubling the constant factor in my partition, or that I should use median-of-three for better performance against pre-sorted lists. I expected Lance to show up and demand some merge sorts, which I haven’t coded since my second year of CS.

For myself in response to the Brick Science article, I wrote a small linked list library in C. It turned out to be about 55 lines of code, and it took me about 15 minutes to write, which reassured me after reading that the author expects 155 lines/hour. I would not expect anyone to achieve that in a reasonable language, but with C you can really fluff things up with meaningless brackets and wasted type declarations. Even Lisp, which is the most text-liberal functional language, is a factor of two or three improvement over C. I remember days at Clearwired where I was productively producing about 10 or 20 lines of Python an hour. Of course, HTML is a bit cheaper.

I have become a real dick about movies. Elitism is the opiate of the Dan, but I have tried to be conservative about which things I am an elitist about: heavy metal, programming languages and religion being the primary fields, but also somewhat about politics. I have more-or-less ejected politics as a synonym for theft, graft, stupidity, and waste, no matter the incarnation, so into the open slot I have tossed movies. My brother hasn’t helped, we have been alternating classics I have loved for some time with known-good classics of his interest, and both learning a lot. Plus now we have a lot of pretentious hatred for new movies. It’s a perfect fit with the rest of our hatred set (music and television).

I am now going to attempt to read another classic book, since I usually read about four non-programming books a year and Demian is number one for 2006.

Tags , , , , , , , , , , , , , ,  | 2 comments