Monday, September 18, 2006

String manipulation: Qt vs Cocoa

Background

I am coding a raytracer program using C++ and Qt. It is program that supports many different algorithms and file formats. One of the algorithms had some bugs that I had problems finding, so I made a OpenGL visualizer of the scene with the hope to be able to visualize different steps in the algorithm to find the error.

Unfortunately the Qt OpenGL implementation proved extremely slow. Instead of looking all over the place to find a solution I decided to create the OpenGL viewer in Cocoa. It seems like Qt4 has several performance problems compared to Qt3. Qt Designer is quite slow when editing properties. Before the last update, it was so slow to type in a text field that I had to type in another window and then cut and paste the text in. Likewise Assistant was so slow I ended up using Safari instead to browse the help documentation.

The scene I read was written for the RenderBitch raytracer. The format is basically like this:

lookat 0 .15 -4.0 ''0 0 0 ''0 1 0
persp 40 1 4.3 .0

material cyan
r_diff .7 1 1
r_refl 0 0 0

# this adds vertices to the scene
vertex 1.25 1 1.3
vertex 1.25 1 -3.3

That is it is a text based file format with some command at each line. So to read the file I had to do a bunch of string manipulation.

I had already done code to read this text in using Qt, so it proved an interesting comparison doing the same task in Cocoa

String classes in Qt and Cocoa

The approach used by Qt and Cocoa is quite different. When looking through Cocoa documentation it reminded me more of how I did string manipulation in script languages like Python and Ruby. Except the methods names were extremely long as is usual in Cocoa (One of the things I dislike the most about Cocoa).

In Qt you deal with strings using the QString, QStringList, QTextStream and QRegExp classes. In Cocoa the equivalents would more or less be: NSString, NSArray and NSScanner. Cocoa does not have an class for regular expressions.

The difference caused by Cocoa being dynamically typed shows itself in the use of NSArray instead of a special string list container. It is general purpose and can hold any kind of objects. Since objects in general can create string representations of itself NSArray also contains methods to joint elements into a string with a separator like QStringList.

One of the creates strength of Qt comes is visible when dealing with primitive types like int and float. By using templates and operator overloading these can be handled a lot easier than Cocoa. In Cocoa you must explicitly box primitive types.

So with Qt I could easily read in data from the text file using QTextStream and have operator overloading automatically take care of type conversion

The example below shows how I can read in e.g. a vertex line in the scene file. Placing the vertex keyword in the keyword variable and the coordinates in x, y and z:

// Reading in a line of text using Qt
QString keyword;
float x, y, z;
QTextStream in(line);
in >> keyword >> x >> y >> z;

In Cocoa I would have to do it explicitly:

// Reading a line of text using Cocoa
NSString* keyword;
float x, y, z;
NSScanner* scanner = [NSScanner scannerWithString:line];
[scanner scanUpToString:@" " intoString:&keyword];
[scanner scanFloat:&x];
[scanner scanFloat:&y];
[scanner scanFloat:&z];

However I didn't actually end up using the operator overloading in Qt. There was some benefits to doing in another way and perhaps it was influenced by my experience dealing with strings in script languages that don't have operator overloading and templates.

So instead I chopped the lines into pieces and converted each string separate like this:

// Split element on line separated by whitespace
QString keyword;
float x, y, z;
QRegExp whitespace("\\s");
QStringList list = line.split(whitespace, QString::SkipEmptyParts);
keyword = line[0];
x = line[1].toFloat();
y = line[2].toFloat();
z = line[3].toFloat();

This looks more complicated but it didn't turn out much different when considering that I read parameters in bulk and they were usually all floats or ints.

In Cocoa I could to similar with this code (although ironically I ended up using the stream approach there):

// Split element on line separated by whitespace
NSString* keyword;
float x, y, z;
NSArray* list = [line componentsSeparatedByString:@" "];
keyword = [list objectAtIndex:0];
x = [[list objectAtIndex:1] floatValue];
y = [[list objectAtIndex:2] floatValue];
z = [[list objectAtIndex:3] floatValue];

It is not entirely equal because it separates on single space and not on any whitespace. This is why I ended up with NSScanner because it has more options on separators.

Unicode

Both Qt and Cocoa uses unicode strings. However in Qt this is not something you have to think much about due to operator overloading. You can write line[0] == '#' and it works, despite line[0] actually representing a unicode character and '#' is not. But overloading of == takes care of the translation trasparently.

With Cocoa it is a different story, because of operator overloading.So to compare a unicode character to a range of other characters one would have to use the NSCharacterSet class.

// line[0] == '#'
NSCharacterSet* hash = [NSCharacterSet characterSetWithCharactersInString:@"#"];
if ([hash characterIsMember: [line characterAtIndex:0]]) {
''''// Do somehting...
}

Of course in this particular instance when we are just checking for a character at the start of the line there is a simpler solution:

// line[0] == '#'
if ([line hasPrefix:@"#"]) {
''''// Do somehting...
}

Experiences with the whole development cycle

It is of course hard to compare the development effort for both environments for this task given that I have much more experience with Qt and did the program in Qt first and then later in Cocoa. So it might even out in the sense that I didn't have to think that much about program design in Cocoa but I had to look up more documentation on how to deal with strings.

With debugging I think Cocoa overall was easier to deal with than Qt. Although I have noticed earlier that the weak typing used in C can cause from problems in Cocoa programs. A number of type mistakes are more easily caught using Qt because it uses C++ which has stronger typing than C.

Some problems with C was also enhanced by how Apple has chosen to let xCode build by default. It uses a Zero linking technique which I thought was great at first because it severely cuts down the compile and link process. However it is quite dangerous because it lets you compile and run programs with calls to functions that don't exist, or with the wrong number of parameters. Now one might argue that this is the case with most dynamic languages in general like script languages. However the combination of late binding and C is a very bad one. There are no proper error handlers when something goes wrong so you remain quite clueless to what went wrong.

However apart from that it is much easier to trace through and debug Objective-C programs in gdb than C++ programs. Almost all Objective-C classes give descriptions of themselves so they can easily be inspected. C functions and Objective-C methods are easier to call because they don't have problems with name mangling like C++ methods and functions.

For instance to debug Qt program I frequently had to make standalone methods that printed out specific objects I had to inspect.

Not an ideal solution because you have to add that code to every Qt program you debug and you have to make code for every class you want to inspect.

Since C++ methods are tricky to call, in debug mode it is hard to change their state through method calls in the debugger.

Conclusion

Although Qt and C++ have some distinct benefits like strong typing, templates and less verbose code, I found it quicker to develop my Cocoa solution because it was much faster to hunt down bugs in the program because debugging was easier.

No comments: