Chris Gregory

Home Blog LinkedIn Github

Java to C++ - Pointers and Classes

9/25/2015

Pointers are fundamentally simple. They simply tell you where a value is rather than giving you the actual value. However, because of how destructors work, they can be dangerous to use in certain situations. Let's look at an example of a pointer:

#include <iostream>

int main() {
    int* int_ptr = new int(0);
    std::cout << *int_ptr << std::endl;

// memory leak!  This is not caught by compiler!  int_ptr
// is never deleted add ``delete int_ptr`` to the end, or
// use a value type int instead
}

This shows the basic usage of a pointer. In Java, ints are always value types, so you have to return an int to modify a variable. In C++, everything is a value type unless explicitly marked as a pointer or reference.

Pointers are different from references in that their semantics require a dereference before usage. You can dereference a pointer into a value bye using the unary * operator. However, the * operator is annoying as it is right associative. This means that *ptr.y = 10; would be invalid as the ptr isn't dereferenced before usage! To overcome this, use the -> operator, which basically replaces ptr->y with (*ptr).y. In C++ the this keyword is just a pointer to self, meaning that you have to use this->field to access something rather than this.field. Note that Java's . operator is literally the exact same as C++'s -> operator.

Pointers allow you to access native memory locations, so a pointer and int pair is all that an array really is! Let's implement a simple class to represent an array:

#include <stdexcept>   // std::out_of_range

// forces generic usage of this like Array<int> for
// types, Array by itself is compile error!
template<class T>
class Array {
    private:
    T* array_start;
    unsigned int length;

    public:
    // constructor
    Array() {
        array_start = new T[10];
        length = 10;
    }

    // destructor
    ~Array() {
        // not null test
        if(array_start)
            delete[] array_start;
        // non pointers auto destructed
    }

    // random access - could commit suicide
    T& operator[](int index) {
        return array_start[index];
    }

    // checked access - throws exception if out of bounds
    T& at(int index) {
        if(index < 0 || index >= length)
            throw std::out_of_range("Index out of range");
        return array_start[index];
    }
}; // all structures must end with semicolon

A potential problem is that any copies of this Array don't make a deep copy, they just copy the pointer location, resulting in a possible double deletion! It also just isn't good style to have constructors written this way, because if any of the values were const, then it would be a syntax error! Let's fix the constructors:

Array() : array_start(new T[10])
        , length(10) { }

This now constructs the values with new T[10] and 10 rather than copying them in after they have been constructed. It also separates the initialization from the code and makes it cleaner overall.

Now let's get into the real problem with the class, any copies are not true copies! C++ has an implicitly defined method called a copy constructor on every object. It is called to initialize a copy of an object. For this reason, there is no generic clone method, just use <type> copy = old;! The auto generated copy constructor works great for classes without pointers but not so well when there are pointers. Every copy constructor should be accompanied by a copy assignment operator, as show below. The copy constructor and copy assignment operators need to take a const reference to the type, such as const Array&.

#include <algorithm>   // std::copy

// copy constructor
Array(const Array& other) : array_start(new T[other.length])
                          , length(other.length) {
    std::copy(other.array_start, other.array_start + length, array_start);
}

// copy assignment operator
Array& operator=(const Array& other) {
    length = other.length;

    if(array_start) delete[] array_start;
    array_start = new T[length];
    std::copy(other.array_start, other.array_start + length, array_start);

    // remember to always ``return *this;`` so it can be
    // assigned to in function arguments.
    return *this;
}

As you can see, pointers and pointer arithmetic are really cool and powerful but they make it easy to "shoot yourself in the foot" if you don't know what to do with them.