Taligent Logo
Porting Paper
dot_clear
dot_clear

Java Cookbook: Porting C++ to Java

Next Steps

Introduction, Basics, Next Steps, Well-Mannered Objects, Esoterica, Background, Index


The following sections take you further through the steps necessary to convert your program from C++ to Java.


Who owns what?

In C++, you have to be extremely careful about who owns a pointer--that is, whether the object or a caller has the right, and the responsibility (or blame), for deleting the pointer. There are three general cases in C++:

Adoption
The caller hands over ownership to the object. The caller should relinquish any pointers to the object, so that it does not mistakenly delete or alter the contents of the pointer.
 
Aliasing
The caller keeps ownership, and just passes in a pointer. The cleanest case is when the pointer is const, since it is clear that the object cannot own the pointer. (Unfortunately, there is no way of indicating in C++ that the object can make changes to the pointer but cannot delete it.)
 
Assignment
The caller keeps ownership of what it passes in, and the object makes its own copy. This is the safest mechanism, but must often be avoided because of performance considerations.
  • Note that if the pointer can be a subclass, you had to use some extension of RTTI in C++ instead of new: otherwise your object will be sliced. Since even standard RTTI does not support this, most people end up having a base class member function called copy or clone that all derived classes override.
  • To make our examples simpler, we will write the C++ code as if you had two global template functions:
    • ::Copy(x,y), which creates a polymorphic copy of y.
    • ::ReplaceByCopy(x,y), which deletes x, sets it to NULL, then sets it to a copy of y.
      (After you delete a pointer field in C++, you must null it out before assigning it with a function call (including new). This is a subtle point, but unless you do this the object's destructor will do a double deletion if the function call throws an exception!)

Java is considerably simpler, as you will see.


Garbage in...

Java has built-in garbage collection, which relieves you from much of the grunt work of memory access. (Not all of it--even if you don't have to worry about who can delete objects, you still have to worry about who can change objects: see Bullet-proofing). In general, you will just remove all destructors and deletions, and replace copy constructors by clone.

Removing Destructors and Deletions

C++

Java

// destructor
 ~MyObject() {
   delete field1;
   delete field3;
 }
 
// pointer field adoption
 void setField1 (Foo* newValue) {
  delete field1;
  field1 = newValue;
 }
 
// pointer field aliasing
 void setField2 (Foo* newValue) {
  field2 = newValue;
 }
 
// pointer field assignment
 void setField3 (const Foo& newValue) {
  delete field3;
  field3 = NULL;
  aValue = new Foo(newValue);
 }
// no destructor
 
 
 
 
 
// field assignment
void setField1 (Foo newValue) {
 
  field1 = newValue;
 }
 
// pointer field aliasing
 void setField2 (Foo newValue) {
  field2 = newValue;
 }
 
// field assignment
 void setField2 (Foo newValue) {
 
 
  aValue = newValue.clone();
 }

If the object is not going out of scope or going to be reset soon, then you should replace a deletion by setting to null. That allows the garbage collector to get rid of the object without waiting for it to go out of scope. For example:

Enabling Garbage Collection

C++

Java

// big block with lots of stuff
 {
   ...
   Foo x = new Foo();
   ...
   delete x;
   ...
 }
// no destructor
 {
   ...
   Foo x = new Foo();
   ...
   x = null;
   ...
 }

The only case you have to worry about is where your destructor must also release system resources (such as open files), or perform some other global action (such as removing a corresponding object from a global list). In that case, you may need to put some of the guts of your destructor into a finalize method. Unfortunately, you can't control when this method gets called very well, so you may instead have to add an explicit release method, and call it in all the places where your object was destroyed in C++.


Difficult assignments

In C++, you generally define a copy constructor and an assignment operator. Both of these should be closely linked in the way they work. In Java, you could replace them both by the use of clone. However, to minimize the changes to your C++ code on the calling side (especially for output parameters), it is often easier to go ahead and write an assign method.

An assign method may also be faster, since it avoids the cost of making a new object.

You must be careful when writing correct clone, equals, and hashCode operators--see Well-Mannered Objects for more information.

Fixing Assignment

C++

Java

// defining 
Foo(const Foo& other) {
 
 field1 = other.field1;
 field2 = ::Copy(other.field2);

}
 
Foo& operator= (const Foo& other) {
 if (&other != this) {
  SuperOfFoo::operator=(other);
  field1 = other.field1;
  ::ReplaceByCopy
   (field2,other.field2);
 }
 return *this;
}
 
// using 
Foo a = Foo(c);
a = b;

void getStuff(Foo& foo, Bar& bar) {
 foo = otherFoo();
 bar = otherBar();
}
// defining 
public Object clone (Object other) {
 Foo result = (Foo) super.clone();

 field2 = other.field2.clone();
 return result;
}

public Foo assign (Foo other) {
 if (other != this) {
  super.assign(other);
  field1 = other.field1;
  field2 = other.field2.clone();

 }
 return this;
}

// using 
Foo a = c.clone();
a = b.clone();

public void getStuff(Foo foo, Bar bar) {
 foo.assign(otherFoo());
 bar.assign(otherBar());
}


Decolonization

Double-colons occur in two places in C++: with statics and with direct base class methods. In both cases, Java has a different syntax, but there are few opportunities for error since the compiler will catch most mistakes.

All statics must be defined in a class, so you have to move your "unclassy" static data or global functions into an appropriate class, or make up a new class such as Globals .

Statics & Base Class Methods

C++

Java

// declaring
class Foo {
 static Foo x;
 void someMethod();
}


int myGlobalFunction() {...
static Foo y;
 
 
// using 
a = Foo::x;
b = y;
c = myGlobalFunction();

// declaring
class Fii : Foo {
 void someMethod() {
  Foo::someMethod();
 }
}
// declaring
class Foo {
 static Foo x;
 void someMethod();
}
 
class Globals {
 static int myGlobalFunction() {...
 static Foo y;
}
 
// using 
a = Foo.x;
b = Globals.y;
c = Globals.myGlobalFunction();

// declaring
class Fii extends Foo {
 void someMethod() {
  super.someMethod();
 }
}

In Java, above the immediate superclass, you can't call base class methods directly. Luckily, calling higher base classes is rarely done in C++, so you should have few instances of it. If you do run into a case like this, then you will have to introduce some artificial methods of the class you want to call.


It's all conditional

Conditionals look very similar, except that Java enforces the type boolean. If the condition is flagged as an error by the compiler, then put it in parentheses, and add != 0.

Fixing Conditionals

C++

Java

if (x == 3) {}
 
if (x++) {}
 
if (x = y) {}
if (x == 3) {}
 
if ((x++) != 0) {}
 
if ((x = y) != 0) {} 

Notes

  • The Java restriction has the extra benefit of ferreting out the unintended use of = instead of ==. One only wonders how many millions of dollars in time that little gem in C and C++ has cost overall; it is surprising that no one has yet filed a class-action suit against K & R!

Java has no #if or #ifdef. In many cases, these conditionals are not required since they are often used for marking machine-specific code, which is not a problem for Java. Generally, these macro conditionals can be replaced by use of a simple conditional, since Java optimizes away conditionals that evaluate to false at compile time.

Replacing #ifdef

C++

Java

#define DEBUG false


#if DEBUG
 ...
#endif
class Globals {
 static final boolean DEBUG = false;
}

if (DEBUG) {
 ...
}

However, where you have commented out parts of a block or more than one method, there is just no good substitute for #ifdef in Java . Occasionally, /*...*/ will substitute; but you have to be careful of premature termination since these marks do not nest, and people often have these comment blocks at the front of each method. The last resort is to copy the commented-out material to another file to preserve it, and then to put a comment in pointing to that file.

As a side issue, there is one slight change you might have to make to for statements, since declarations inside a for statement are scoped slightly differently for older C++ compilers. If there are outside dependencies you might have to pull the declarations out to a higher level, as shown below. The compiler will warn you of these.

Fixing for-Loop Declarations

C++

Java

for (int i = 0; i < j; ++i) {
  x += i * i;
}
z = i;
int i;
for (i = 0; i < j; ++i) {
  x += i * i;
}
z = i;


A sign from above

The Java primitives do not have signed and unsigned variants. The char type is always unsigned, while the others are signed. First remove all signed keywords. (Since the char type in Java is larger than char in C++, removal of signed doesn't make a difference. This discussion presumes that you have already converted non-character C++ char to be byte, as in To protect the innocent).

Once you have removed signed, take a look at the unsigned types. If you really need the range they provide, then you will have to change them to the next higher type.

If your C++ code was portable, you made few assumptions about the sizes of int since it could be 16, 32, or even 64 bits wide in C++, and the only one to watch for is unsigned short.

The C and C++ languages officially say that bitwise operations on signed integers are not portable. People do it anyway, assuming that all machines are now two's-complement. Thankfully, Java officially defines signed integers to be two's-complement, and bitwise operations on them are reliably portable.

Once you are done, drop the unsigned keywords.

Unsigned

C++

Java

// can be > 2,147,483,648
unsigned int x;
 
// otherwise
unsigned int x;
 
// can be > 32,767
unsigned short x;
 
// otherwise
unsigned short x;
signed int y;

z = x >> 1;
z = y >> 1;
 
long x;
 
 
int x;
 
 
int x;
 
 
short x;
int y;
 
z = x >>> 1;
z = y >> 1;

Warning!

If you are right-shifting an unsigned value, you will need to change to use >>>. Search for all instances of >>, and check the type of the arguments. Luckily, most code doesn't use this construct very much.

Also, watch out for number-wrapping assumptions. For example, with a 16-bit C++ int, (x >> 8) gives the high byte. With a 32-bit Java int, you need to mask off possible garbage in the top bits with ((x >> 8) & 0xFF) to get the same result.


Defaults

Java does not have default parameters. If you really want them, you have to use overloaded methods, one for each defaulted parameter. (You can make them final, which with a good compiler will remove the overhead of overload.) You may find it simpler to replace the call sites instead, depending on your code.

Default Parameters

C++

Java

int method(int x = 3, 
 char c = 'a');
public int method(int x, char c) {
...
}
public final int method(int x) {
 return method(x,'a');
}
public final int method() {
 return method(3,'a');
}


Exceptional situations

The exception mechanism works pretty much the same in C++ and Java. The main differences are that--

  1. As usual, you will need to create the exception with new.
  2. The "catch everything" clause (...) is replaced by Exception, which is the base class for Java exceptions. (More precisely, Throwable is, but you generally don't need to worry about that.)
  3. For the most part, you must declare which exceptions could be thrown by your class. These could be also thrown by Java class methods that you call, such as System.in.readline(). The compiler will let you know this, so you can generally just declare the ones it tells you to.

Exceptions

C++

Java

void someMethod() {
 try {
  ...
  throw RangeException();
  ...
 } catch (const RangeException& e) {
  ...
 } catch (...) {
  ...
 }
}

void otherMethod() {

 ...
 throw BadNewsException();
 ...
}
void someMethod() {
 try {
  ...
  throw new RangeException();
  ...
 } catch (RangeException e) {
  ...
 } catch (Exception e) {
  ...
 }
}

void otherMethod() 
   throws BadNewsException {
 ...
 throw BadNewsException();
 ...
}

Notes

  • C++ also lets you declare thrown exceptions, but it doesn't force you to.
  • More precisely, you must declare exceptions, except for those exception classes that descend from RuntimeException or Error. These latter exceptions are for situations which could occur in essentially all code, such as for out-of-memory or file-system-full. Java doesn't require declaring them, since it would be cumbersome and pointless to declare them in essentially all code.


Checking it twice

Unfortunately, Java has no enums. You will have to replace all of your enums by constants, and you will get no type-checking, and no overloading of methods based on the difference in types. Since you have no type-checking, callers are not prevented from mistakenly passing in some random integer instead of an enum value.

Enums

C++

Java

class Button {
 enum ButtonState {inactive, active, 
                   mixed, inherited};



 ...

void method1(ButtonState newState) {
 if (newState == inactive) {...
}

// usage
x.setState(Button::inactive);
class Button {
 // ButtonStates
 public static final byte INACTIVE = 0;
 public static final byte ACTIVE = 1;
 public static final byte MIXED = 2;
 public static final byte INHERITED = 3;
...

void method1(byte newState) {...
 if (newState == INACTIVE) {...
}

// usage
x.setState(Button.INACTIVE);

Notes

  • Instead of constant numbers, you could make each item relative to the previous. This will save time renumbering if you often insert items in the middle of the list. For example,
    active = inactive + 1.
  • If you use byte instead of int, you recover a little bit of safety, since you can't just pass any number in. Most enums are small numbers, which enables this trick.
  • Java coding conventions require you to uppercase constants. This may be a pain to do, since you have to change all the call sites as well as all the definitions. So, you may want to just leave them lowercased to minimize the work.
  • If you really, really wanted your enums to be safe, you could encode them as a class. However, you probably will not find it worth the effort, since you have to make considerable changes to your method definitions and call sites, as you see below:

Replacing enum with a Class

C++

Java

class Button {
 enum ButtonState {inactive, active, 
                   mixed, inherited};

 void method1(ButtonState newState) {
  if (newState == inactive) {...
}
class Button {...



 void method1(ButtonState newState) {...
  if (newState == ButtonState.INACTIVE) {...
}

final class ButtonState {
 public static final ButtonState INACTIVE
  = new ButtonState(0);
 public static final ButtonState ACTIVE
  = new ButtonState(1);
 public static final ButtonState MIXED
  = new ButtonState(2);
 public static final ButtonState INHERITED
  = new ButtonState(3);

 public int toInt() {
  return state;
 }
 
 private ButtonState(int state) {
  this.state = (byte) state;
 }
 private byte state;
}

Notes

  • ButtonState is one of the few classes where == is the same as equals.
  • If you use this technique, you have to change your switch statements to chains of if statements, since the compiler won't determine that ButtonState.INACTIVE.toInt() is a constant integer expression.


Not gooey at all

For simple text-only applications, or for debugging, you will want to know how to deal with arguments and how to print from the console.

Text-only Applications

C++

Java

// fetching command-line arguments

int main(int argc, char *args[]) {
 for (i = 1; i < argc; ++i) {
  doSomething(args[i]);
 }
...

// C-style simple output
printf("%s%i", "abc", 3);

// C-style file output

FILE* output = fopen("aFile","r");
if (output == NULL) {
 handleProblem();
}
fprintf(output, "%s%i", "abc", 3);
fclose(output);




// C++-style simple output
cout << "abc" << 3;
// fetching command-line arguments
public class MyApplication {
 public static void main(String args[]) {
  for (i = 0; i < args.length; ++i) {
   doSomething(args[i]);
  }
...

// simple output
System.out.print("abc" + 3);

// file output
try {
 PrintStream output = new PrintStream(
  new FileOutputStream("aFile"));


 output.print("abc" + 3);
 output.close();
} catch (java.io.IOException e) {
 handleProblem();
}

// simple output
System.out.print("abc" + 3);

Notes

  • Use plus signs instead of commas to separate operands in the print statements.
  • The file output code is reordered in handling error conditions

Unfortunately, an array doesn't have a meaningful toString, despite the fact that it would be easy to iterate over the contents. For debugging it is useful to code a replacement, as shown below:

Printing Arrays

// definition
static String arrayToString(Object[] array) {
 StringBuffer result = new StringBuffer("<");
 for (int i = 0; i < array.length; ++i) {
  if (i != 0) result.append(", ");
  result.append(array[i].toString());
 }
 result.append('>');
 return result.toString();
}

// usage
System.out.println(arrayToString(foo2));

This example also illustrates a common idiom: allocating a StringBuffer, successively appending to it, then returning its conversion to a String. This is much, much faster than using String concatenation, since the equivalent result += array[i].toString() constantly creates new objects.


Introduction, Basics, Next Steps, Well-Mannered Objects, Esoterica, Background, Index

dot_clear dot_clear dot_clear dot_clear dot_clear
navbar
Products Object Resources In the News Company Info Search
 
If you encounter problems with this service please contact webmaster@taligent.com.

© Copyright. All rights reserved. Taligent, Inc., IBM Corp.