Thursday, May 26, 2011

Checklist while porting from C++ to Java

I have been working in a project which involves porting the code base from C++ to Java (to be precise, J2ME). The code base is large and we have been under severe pressure to get it working for the first release. What we have been doing is basically copy-paste the code of a class from C++ to Java, then removing errors, adjusting syntax and the like. What we often overlooked is the semantic differences between these two languages and hard-to-find bugs crept in insidiously.

So I think a checklist would be handy for this:

(1) C++ copy constructor and overloaded assignment operator:

ClassA obj1;
// ...
// code modifying obj1
// ...

ClassA obj2;
obj2 = obj1; // overloaded assignment operator gets called


// or

ClassA obj2 = obj1; // copy constructor gets called


If ClassA defines a copy constructor, then obj2 will be initialized to data with same value but with different allocated memory (deep copy). So subsequent changes made to obj2 does not affect obj1 and vice versa.

Now while porting, this statement (obj2 = obj1) does not generate compile error in Java and reference of obj1 is copied to obj2. So both obj1 and obj2 refer to, in fact, the same instance. We did not notice it and since changes in obj2 or obj1 affected the same object/instance, we were in trouble as it was not meant in the original C++ code.

The right way of doing this is to add an overloaded constructor of ClassA.

public ClassA(ClassA anInstance) {
// copy data from the given argument.

// deep copy if needed.

}


Instead of

obj2 = obj1;

change this in the ported code:

obj2 = new ClassA(obj1);

(2) C++ overloaded == operator:

if (obj2 == obj1) {
// ....
}


This statement also does not generate compile error. In C++, if ClassA overloads == operator, then the overloaded == method gets called. But in Java, this statement simply compares references of obj2 and obj1 which is not intended at all. This statement should be replaced by

obj2.equals(obj1)

and Object::equals() must be overridden in ClassA.

(3) C++ pointer/reference used to receive output from a method:

I do not consider it a good practice to pass pointer to a function in order to receive output. Example:
bool getWidthHeight(int& width, int& height)
{
width = this->width;
height = this->height;
return true;
}

Return value indicates success or failure. This is pervasive in the code base I ported. So how do I have to simulate this? In Java, I defined a class like this:
public class pInt {
public int value;
public pInt(int value) {
this.value = value;
}
}

So I ported that method like this in Java:
boolean getWidthHeight(pInt width, pInt height) {
width.value = this.width;
height.value = this.height;
return true;
}

This had to be done for every built-in or custom type for which a pointer was used to receive output. This is tedious and for mobile platform is not acceptable. But for the first release, I think, nobody will mind this workaround.

(4) C++ conditional compilation:
#ifdef CONDITION_1
// ....
#else
// ....
#endif

I maintained a separate class, say Configurations, for these conditions and set value true or false for them. Then the ported code looked like:
if (Configurations.CONDITION_1 == true) {
// ........
} else {
// ....
}

No comments:

Post a Comment