Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Introduction to C++ for iOS Developers: Part 2

$
0
0
Hello, C++!

Hello, C++!

Welcome back to second part of the Introduction to C++ for iOS Developers series!

In the first part of this series, you learned about classes and memory management.

In this second second and final part of the series, you’ll dive deeper into classes and look at some more interesting features. You’ll see what a “template” is and then have a look at the Standard Template Library.

Finally, you’ll round it all off with learning about Objective-C++, which is a way to mix C++ into Objective-C.

Ready? Let’s get cracking!

Polymorphism

Polymorphism is not a parrot that changes shape, contrary to what it might sound like!

parrot_lion

Ok, I admit, that’s a terribly bad joke, :]

Put simply, polymorphism is the notion of overriding a function in a subclass. In Objective-C you probably have done this many times, for example when subclassing UIViewController and overriding viewDidLoad.

In C++, polymorphism goes quite a bit further than Objective-C. So stick with me while I explain this powerful feature.

To start with, here is an example of overriding a member function in a class:

class Foo {
  public:
    int value() { return 5; }
};
 
class Bar : public Foo {
  public:
    int value() { return 10; }
};

But look at what happens if you do the following:

Bar *b = new Bar();
Foo *f = (Foo*)b;
printf(%i”, f->value());
// Output = 5

Wow — that’s probably not the output you expected! I suspect you thought it would output 10, right? This is where C++ differs drastically from Objective-C.

In Objective-C, it doesn’t matter if you cast a subclass pointer to its base class pointer. When you send a message (i.e. call a method) to any object, it is the runtime which looks up the class of the object and calls the most derived method. Therefore in this scenario in Objective-C, the subclass method on Bar is called.

This highlights the compile-time versus run-time difference that I mentioned in the first half of this series.

When the compiler encounters the call to value() in the above example, its job is to work out what function needs to be called. Since the type of f is a pointer to a Foo, it emits code to jump to Foo::value(). The compiler knows nothing about the fact that f is actually a pointer to a Bar.

In this simple example you could be forgiven for thinking that the compiler could reason that f is a pointer to a Bar. But consider what would happen if f were actually the input to a function. In that case the compiler could have no way of knowing that it’s actually a pointer to a class derived from Foo.

Static Binding and Dynamic Binding

The above example illustrates perfectly the crucial difference between C++ and Objective-C; static versus dynamic binding. The behaviour seen above is an example of static binding. The compiler is responsible for resolving which function to call, and that behaviour is therefore baked in to the binary after compilation. There is no ability to change this behaviour at runtime.

This is in contrast to method calling in Objective-C, which is an example of dynamic binding. The runtime itself is responsible to decide which function should be called.

Dynamic binding is what makes Objective-C so very powerful. You may already be aware that it’s possible to add methods to a class at runtime, or swap method implementations. This could never be done in a statically bound language, where the calling behaviour is baked in at compile time.

But wait — there’s more to it than that in C++! While C++ is generally statically bound, there are mechanisms available to use dynamic binding; these are known as “virtual functions”.

Virtual Functions and the Virtual Table

Virtual functions provide a mechanism for dynamic binding. It defers the choice of which function is called until runtime through the use of a lookup table — one for each class. However, this does introduce a slight overhead cost at runtime when compared to static binding. The table lookup needs to happen in addition to calling the function. With static binding, only the calling of the function needs to be performed.

Using virtual functions is as simple as adding the “virtual” keyword to the function in question. Taking the previous example and using virtual functions instead would look like the following:

class Foo {
  public:
    virtual int value() { return 5; }
};
 
class Bar : public Foo {
  public:
    virtual int value() { return 10; }
};

Now consider what happens when you run the same code as before:

Bar *b = new Bar();
Foo *f = (Foo*)b;
printf(%i”, f->value());
// Output = 10

That’s better! That’s the output you presumably expected earlier, right? So dynamic binding can be done in C++, but you need to decide whether you want static or dynamic binding depending on the scenario you face.

This type of flexibility is commonplace in C++; it’s what makes C++ a multi-paradigm language. Objective-C largely forces you into strict patterns, especially if you are using the Cocoa framework. C++, on the other hand, leaves a lot of decisions up to the developer.

It’s now time to get dirty and look at how virtual functions work.

pic3

The Inner Workings of Virtual Functions

Before you can understand how virtual functions work, you need to understand how non-virtual functions work. Consider the following code:

MyClass a;
a.foo();

If foo() is non-virtual, then the compiler will convert this into code that jumps directly to the foo() function of MyClass.

But remember, this is where the problem with non-virtual functions lies. Recall from the previous example that if the class is polymorphic then the compiler can’t know the full type of the variable, and therefore can’t know which function it should jump to. There needs to be a way to lookup the correct function at runtime.

To accomplish this lookup, virtual functions make use of a concept known as the virtual table or v-table; this is a lookup table that maps functions to their implementations and each class has access to one. When the compiler sees a virtual function being called, it emits code that retrieves the object’s v-table and looks up the correct function.

Look back at the example from above to see how this works:

class Foo {
  public:
    virtual int value() { return 5; }
};
 
class Bar : public Foo {
  public:
    virtual int value() { return 10; }
};
 
Bar *b = new Bar();
Foo *f = (Foo*)b;
printf(%i”, f->value());
// Output = 10

When you create b, an instance of Bar, its v-table will be the v-table for Bar. When this variable is cast to a Foo pointer, it doesn’t alter the contents of the object. The v-table is still the v-table for Bar, not Foo. Therefore when the v-table is looked up for the call to value(), the result is Bar::value() which will be called.

Constructors and Destructors

Every object goes through two very important stages in its life: construction and destruction. C++ allows you to control both of these stages. The equivalent of these stages in Objective-C is the initializer method (i.e. init or any other method starting with init) and dealloc.

Constructors in C++ are defined by functions that share the same name as the class. You can have any number of them that you like, just as you can have any number of initializer methods in Objective-C.

For example, here is a class that has a couple of different constructors:

class Foo {
  private:
    int x;
 
  public:
    Foo() {
        x = 0;
    }
 
    Foo(int x) {
        this->x = x;
    }
};

Here there are three constructors. One is considered the default constructor: Foo(). The other takes a parameter to set the member variable.

If all you’re doing in the constructor is to set internal state, as in the above example, there’s a way to do that with less code. Instead of implementing the setting of the member variables yourself, you can use the following syntax:

class Foo {
  private:
    int x;
 
  public:
    Foo() : x(0) {
    }
 
    Foo(int x) : x(x) {
    }
};

Usually if you’re just setting member variables you would use this syntax. If, however, you need to perform some logic or call other functions, then you would implement the function body. You can also use a combination of the two.

When using inheritance, you need a way to call up to the super-class’s constructor. In Objective-C you do this by always calling the super-class’s designated initializer first.

In C++, you would do it as follows:

class Foo {
  private:
    int x;
 
  public:
    Foo() : x(0) {
    }
 
    Foo(int x) : x(x) {
    }
};
 
class Bar : public Foo {
  private:
    int y;
 
  public:
    Bar() : Foo(), y(0) {
    }
 
    Bar(int x) : Foo(x), y(0) {
    }
 
    Bar(int x, int y) : Foo(x), y(y) {
    }
};

The call to the super-class’s constructor is indicated by the first element in the list after the function signature. You can call through to any super-class constructor you want.

C++ code doesn’t tend to have a single designated initializer. Until recently, there was no way to call through to a constructor of the same class. In Objective-C, it’s common to have one designated initializer that every other initialiser calls, and only the designated initializer calls through to the super-class’s designated initializer. For example:

@interface Foo : NSObject
@end
 
@implementation Foo
 
- (id)init {
    if (self = [super init]) { ///< Call to super’s designated initialiser
    }
    return self;
}
 
- (id)initWithFoo:(id)foo {
    if (self = [self init]) { ///< Call to self’s designated initialiser
        // …
    }
    return self;
}
 
- (id)initWithBar:(id)bar {
    if (self = [self init]) { ///< Call to self’s designated initialiser
        // …
    }
    return self;
}
 
@end

In C++, while you can call through to a super-class’s constructor, until recently it was illegal to call through to one of your own constructors. Therefore the following workaround was quite common:

class Bar : public Foo {
  private:
    int y;
    void commonInit() {
        // Perform common initialisation
    }
 
  public:
    Bar() : Foo() {
        this->commonInit();
    }
 
    Bar(int y) : Foo(), y(y) {
        this->commonInit();
    }
};

However, this is rather cumbersome. Why can’t you just have Bar(int y) call through to Bar() and then have the contents of Bar::commonInit() in Bar()? That’s exactly how it works in Objective-C, after all.

In 2011, the latest C++ standard became a reality: C++11. In this updated standard it is indeed possible to do just this. There is still a lot of C++ code out there that hasn’t been updated for the C++11 standard, which is why it’s important to know both approaches. Any post-2011 C++ code will more likely do the following:

class Bar : public Foo {
  private:
    int y;
 
  public:
    Bar() : Foo() {
        // Perform common initialisation
    }
 
    Bar(int y) : Bar() {
        this->y = y;
    }
};

The only slight snag with this approach is that you can’t set member variables at the same time as calling a constructor from the same class. In the case above, the y member variable has to be set as part of the constructor body.

Note: C++11 became a full standard in 2011. It was originally called C++0x while being developed. This is because it was meant to mature sometime between 2000 and 2009, where the ‘x’ would be replaced by the last digit of the year. However it went on longer than expected and therefore ended up being called C++11! All modern compilers, including clang, now fully support C++11.

That covers construction, but what about destruction? That happens when an object is either deleted, if it’s a heap object, or goes out of scope, if it’s a stack object. In this function you’re required to perform any necessary cleanup of the object.

A destructor cannot take any arguments, because that wouldn’t make much sense if you think about it for a moment. dealloc cannot take any arguments in Objective-C for the same reason. Therefore there can only be one destructor for each class.

The name of the destructor is the class name prefixed by a tilde (~). Here’s an example destructor:

class Foo {
  public:
    ~Foo() {
        printf(“Foo destructor\n”);
    }
};

Take a look at what happens when you have a class hierarchy:

class Bar : public Foo {
  public:
    ~Bar() {
        printf(“Bar destructor\n”);
    }
};

If you were to leave the code like this, then something strange would happen if you deleted an instance of Bar via a Foo pointer, like so:

Bar *b = new Bar();
Foo *f = (Foo*)b;
delete f;
// Output:
// Foo destructor

Err, that doesn’t seem right, does it? It’s Bar that’s been deleted, so why has Foo’s destructor been called?

Recall that this same problem happened earlier and you used virtual functions to get around it. This is the exact same problem. The compiler sees that it’s a Foo that needs deleting, and since Foo’s destructor is not marked as virtual, it thinks that is the function to call.

The way to fix this is to mark the destructor as virtual, like so:

class Foo {
  public:
    virtual ~Foo() {
        printf(“Foo destructor\n”);
    }
};
 
class Bar : public Foo {
  public:
    virtual ~Bar() {
        printf(“Bar destructor\n”);
    }
};
 
Bar *b = new Bar();
Foo *f = (Foo*)b;
delete f;
// Output:
// Bar destructor
// Foo destructor

That’s closer to the desired outcome — but the end result is different than what happened when virtual was used previously. This time, both functions are called. First the destructor for Bar is called, then the destructor for Foo is called. What gives?

This is because destructors are special cases. The Bar destructor automatically calls the Foo destructor because that’s the super-class’s destructor.

This is exactly what’s required; just as in the pre-ARC days of Objective-C where you would call super’s dealloc.

I bet you’re thinking this:

pic4

You’d think that the compiler would just do this for you. Well, it could, but that wouldn’t be optimal in all cases.

For example, what if you never inherited from a certain class? If that destructor was virtual then there would be an indirection via the v-table every time an instance is deleted, and maybe you don’t want this indirection. C++ gives you the ability to make the decision yourself — yet another example of C++ being very powerful — but the developer really needs to keep on top of what’s going on.

Let this be a warning to you. Always make destructors virtual, unless you’re absolutely sure that you won’t inherit from that class!

Operator overloading

This next topic doesn’t exist at all in Objective-C, so you might find the concept a little bit daunting at first. But fear not, it’s really not that complicated after all!

An operator are entities such as the familiar +, -, *, or /. For example, you would use the + operator with standard scalars as such:

int x = 5;
int y = x + 5; ///< y = 10

The + here is obvious in what it does: it adds x to 5 and returns the result. It might not be obvious, but this is effectively a function which could have been written like this:

int x = 5;
int y = add(x, 5);

In our imaginary function, add(…) adds the two parameters and returns the result.

In C++ it’s possible to define what happens when you use an operator on any of your own classes. This can be extremely powerful. Of course it doesn’t always make sense. What would it mean to add a Person to a Person for example? Maybe they become married!? :]

Nevertheless, this feature can be extremely useful. Consider the following class:

class DoubleInt {
  private:
    int x;
    int y;
 
  public:
    DoubleInt(int x, int y) : x(x), y(y) {}
};

It might be nice to be able to do this:

DoubleInt a(1, 2);
DoubleInt b(3, 4);
DoubleInt c = a + b;

We want c to be equivalent to DoubleInt(4, 6) here – i.e., add x and y of each DoubleInt instance together. This is simple, it turns out! All you need to do is add a method to DoubleInt that looks like this:

DoubleInt operator+(const DoubleInt &rhs) {
    return DoubleInt(x + rhs.x, y + rhs.y);
}

The function called operator+ is special; it’s used whenever the compiler sees a DoubleInt on either side of a +. This function is called on the object on left hand side of the +, passing in the object on the right hand side as the parameter. That’s why the parameter is often named “rhs”, standing for “right hand side”.

The parameter to the function is a reference because taking a copy would be unnecessary and might alter the value, as it would be a new object being constructed. It’s also constant, because it’s illegal for the right hand side to change during the addition.

C++ can go even further than this. Maybe you don’t want to just add a DoubleInt to a DoubleInt. Maybe you want to be able to add an int to a DoubleInt. That’s possible as well!

To achieve this you would implement the following member function:

DoubleInt operator+(const int &rhs) {
    return DoubleInt(x + rhs, y + rhs);
}

Then you could do the following:

DoubleInt a(1, 2);
DoubleInt b = a + 10;
// b = DoubleInt(11, 12);

Super! And very powerful, I’m sure you’ll agree.

It doesn’t just stop at addition though. The same can be done with any operator. You can overload ++, --, +=, -=, *, ->, and many more. Going through all of them here would take far too long. I suggest if you want to read more about operator overloading then head on over to learncpp.com where there is a whole chapter dedicated to operator overloading.

Templates

Roll up your sleeves now. Here comes a rather interesting topic of C++.

How often have you written a function or a class and then found yourself writing the same function or class again — but with different types? As an example, consider a function to swap two values. You might write a function like this:

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

Note: Notice the use of pass by reference here so that the values that are passed in will actually be swapped. If the values were passed by value then it would be the function’s own copy of the values that would be swapped. This is another good example of a function that benefits from C++’s reference feature.

That works only on integers. If you want this to work for floats, then you would need to write another function:

void swap(float &a, float &b) {
    float temp = a;
    a = b;
    b = temp;
}

It’s a bit silly that you’ve had to duplicate the body of the function. C++ introduces syntax that allows you to effectively ignore the type. You can accomplish this via a feature known as templates. Instead of writing the two methods above, in C++, you can write the following:

template <typename T>
void swap(T a, T b) {
    T temp = a;
    a = b;
    b = temp;
}

Then, when you use swap with any type whatsoever, your function will swap them! You could call your function in any of the following ways:

int ix = 1, iy = 2;
swap(ix, iy);
 
float fx = 3.141, iy = 2.901;
swap(fx, fy);
 
Person px(“Matt Galloway”), py(“Ray Wenderlich”);
swap(px, py);

You need to be careful with templates, though; this approach only works if the implementation for the template function is in a header file. This is due to the way templates are compiled. When the compiler sees a template function being used, it compiles a version for that type if one doesn’t already exist.

Given that the compiler needs to see the template function implementation, you need to put the implementation in a header file and include that everywhere you use it.

Similarly, if you edit the implementation of a template function, every other file that uses that function needs to be recompiled. This is in contrast to editing a function or class member function implemented in an implementation file; in that case, only the one file needs to be recompiled.

For this reason, extensive use of templates can make an application cumbersome. But their power is extremely useful, so like many things in C++, it’s always a balancing act between power and simplicity.

Template Classes

Templates don’t stop at functions, however. It’s also possible to use templates with entire classes!

Imagine you want a class that holds a triplet of values — that is, three values that you’re going to use to hold some data. At first you want to use it with integers, so you write it like this:

class IntTriplet {
  private:
    int a, b, c;
 
  public:
    IntTriplet(int a, int b, int c) : a(a), b(b), c(c) {}
 
    int getA() { return a; }
    int getB() { return b; }
    int getC() { return c; }
};

But then you continue developing your application and realise that you need a triplet that stores floats. This time you write another class, like this:

class FloatTriplet {
  private:
    float a, b, c;
 
  public:
    FloatTriplet(float a, float b, float c) : a(a), b(b), c(c) {}
 
    float getA() { return a; }
    float getB() { return b; }
    float getC() { return c; }
};

It would appear that templates would be helpful here too — and it turns out that they are! In the same way that functions can use templates, so can entire classes. The syntax is identical. The above two classes could be rewritten like this:

template <typename T>
class Triplet {
  private:
    T a, b, c;
 
  public:
    Triplet(T a, T b, T c) : a(a), b(b), c(c) {}
 
    T getA() { return a; }
    T getB() { return b; }
    T getC() { return c; }
};

Using such a template class, however, requires slightly altering how you use the class. Template functions work without changing code, because the types of the arguments permit the compiler to infer what’s going on. However you need to tell the compiler which type you want a template class to use.

Fortunately, it’s very straightforward. Using the template class above is as easy as this:

Triplet<int> intTriplet(1, 2, 3);
Triplet<float> floatTriplet(3.141, 2.901, 10.5);
Triplet<Person> personTriplet(Person(“Matt”), Person(“Ray”), Person(“Bob”));

Powerful, isn’t it?

pic5

But wait! There’s more!

Template functions and classes are not limited to just a single unknown type. The Triplet class could be extended to support any three types, rather than each value having to be the same type.

To do this, it’s just a matter of extending the template definition to supply more types, like so:

template <typename TA, typename TB, typename TC>
class Triplet {
  private:
    TA a;
    TB b;
    TC c;
 
  public:
    Triplet(TA a, TB b, TC c) : a(a), b(b), c(c) {}
 
    TA getA() { return a; }
    TB getB() { return b; }
    TC getC() { return c; }
};

Three different types form the template above; each is then used in the appropriate place in the code.

Using such a template is also very easy, as shown below:

Triplet<int, float, Person> mixedTriplet(1, 3.141, Person(“Matt”));

So that’s it for templates! Now let’s take a look at a library that makes heavy use of this feature – the Standard Template Library.

Standard Template Library (STL)

Any self-respecting programming language has a standard library that contains commonly used data structures, algorithms and functions. In Objective-C you have Foundation. This contains NSArray, NSDictionary among other familiar and not-so-familiar members. In C++, it is the Standard Template Library — or STL, for short — that contains this standard code.

The reason it is called the Standard Template Library is that it makes extensive use of templates. Funny that, eh? :]

There are many things that are useful in the STL; covering all of them would take far too long, so I’m just going to cover the most important bits here.

Containers

Arrays, dictionaries, and sets: all of these are containers of other objects. In Objective-C, the Foundation framework contains implementations of the most commonly used containers. In C++, the STL contains these implementations. In fact, the STL contains quite a few more container classes than Foundation does.

In the STL there are a couple of different equivalents to NSArray. The first is called vector and the second is called list. Both of these can represent a sequence of objects, but each has its own benefits and downsides. Once again, it’s a matter of choosing from the range of options given to you by C++.

First of all, let’s look at vector, which is used like this:

#include <vector>
 
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);

Notice the use of std::; this is because most of the STL lies within the std namespace. The STL puts all of its classes inside its own namespace called “std” to avoid potential naming conflicts with other code.

In the above code, you first create a vector to store ints, then five ints are pushed onto the end of the vector. At the end of this operation, the vector will contain 1, 2, 3, 4 and 5 — in that order.

One thing to note here is that all containers are mutable; there are no mutable and immutable variants, like there are in Objective-C.

Accessing elements of a vector is done like so:

int first = v[1];
int outOfBounds = v.at(100);

Both of these are valid ways to access elements of a vector. The first notation uses the square brackets; this is how you index into a C-style array. It’s also how you are now able to index into an NSArray since subscripting was added to Objective-C.

The second line above uses the at() member function. This works the same as the square brackets, except it also checks if the index is within the bounds of the vector. If it’s not, then it throws an exception.

A vector is implemented as a single, contiguous, block of memory. Its size is therefore the size of the object being stored (so four or eight bytes for integers, depending on if you have 32-bit or 64-bit architecture in use) multiplied by the number of objects in the vector.

Adding elements to a vector is expensive, because a new block of memory needs to be allocated for the new size vector. However accessing a certain index is fast, because it’s a matter of simply reading the right number of bytes into the memory store.

std::list is quite similar to std::vector; however, a list is implemented slightly differently. Instead of being a contiguous block of memory, it is implemented as a doubly linked list. This means that each element of the list contains the data at that element, along with a pointer to the next and previous elements.

Because it is a doubly linked list, insertion and deletion operations are trivial. However accessing the n’th element in the list requires walking from the 0’th to the n’th.

That said, using a list is very similar to vectors:

#include <list>
 
std::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);

Just like the vector example above, this creates a sequence 1, 2, 3, 4 and 5 in order. This time, however, you cannot use the square brackets or at() member function to access a certain element in the list. Instead you must use a concept known as iterators to walk through the list.

Here’s how you would loop through each item of a list:

std::list<int>::iterator i;
for (i = l.begin(); i != l.end(); i++) {
    int thisInt = *i;
    // Do something with thisInt
}

Most container classes have the concept of an iterator. The idea is that an iterator is an object that can move back and forth through the collection and points to a specific member. You increment the iterator to move it forward, or decrement it to move is back.

Obtaining the value at the current location of the iterator is as simple as using the dereference operator (*).

Note: In the above code, there are a couple of instances of operator overloading to be seen. The i++ is the iterator overloading the increment operator ++. *i overloads the dereference operator *. The STL makes heavy use of operator overloading like this.

In addition to vector and list, there are many other containers in C++. They all have different features. Just as in Objective-C there is a set, std::set, and a dictionary, std::map. Another commonly used container is std::pair which stores just two values.

Shared Pointers

To revisit memory management for a moment: recall that when you use heap objects in C++ you have to handle memory yourself; there is no reference counting. That’s certainly true of the language as a whole. But in C++11 a new class was added to the STL which adds reference counting. It’s called shared_ptr, meaning a “shared pointer”.

A shared pointer is an object which wraps a normal pointer and performs reference counting on the underlying pointer. It can be used in much the same way as you would use pointers to objects in Objective-C under ARC.

For example, the following shows how to use shared pointers to wrap a pointer to an integer:

std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3 = p1;

After these three lines of code, the reference count of each of the shared pointers is 3. The reference count drops when each shared pointer is destroyed or is reset. Once the last shared pointer holding onto its underlying pointer is destroyed, the underlying pointer is deleted.

Since shared pointers are stack objects themselves, they will be destroyed when they go out of scope. Therefore they will behave in much the same way that pointers to objects do under ARC in Objective-C.

A full example showing shared pointers being created and destroyed is as follows:

std::shared_ptr<int> p1(new int(1)); ///< Use count = 1
 
if (doSomething) {
    std::shared_ptr<int> p2 = p1; ///< Use count = 2;
    // Do something with p2
}
 
// p2 has gone out of scope and destroyed, so use count = 1
 
p1.reset();
 
// p1 reset, so use count = 0
// The underlying int* is deleted

The assignment of p1 to p2 takes a copy of p1. Remember that when a function parameter is pass by value, it’s a copy of the parameter which is given to the function. This is useful because if you pass a shared pointer to a function, a new shared pointer is passed to the function. This will of course go out of scope at the end of the function and will be destroyed.

So for the lifetime of the function, the use count of the underlying pointer will be incremented by one. Exactly how reference counting works in Objective-C under ARC!

Of course, you need to be able to get at or use the underlying pointer; there’s two ways to do that. Both the dereference (*) and arrow (->) operators are overloaded so that a shared pointer works essentially the same as a normal pointer, like so:

std::shared_ptr<Person> p1(new Person(“Matt Galloway”));
 
Person *underlyingPointer = *p1; ///< Grab the underlying pointer
 
p1->doADance(); ///< Make Matt dance

Shared pointers are a great way to bring reference counting to C++. They of course add a small amount of overhead, but usually this overhead is worthwhile given the clear benefits.

Objective-C++

C++ is all well and good, but what does it have to do with Objective-C you may ask? Well, through the use of Objective-C++ it is possible to mix Objective-C and C++. The name simply comes from the fact that the two languages can be mixed; it’s not an entirely new language, but a combination of the two.

By mixing Objective-C and C++, you can use both sets of language features. It’s possible to mix and match by having C++ objects as instance data of Objective-C classes and vice versa. This can be extremely useful if you want to use a C++ library in an app.

It’s very easy to make the compiler understand a file as Objective-C++. All you need to do is change the filename from .m to .mm. When you do this, the compiler will consider this file differently and will allow you to use Objective-C++.

An example of how you can use one object in another is as follows:

// Forward declare so that everything works below
@class ObjcClass;
class CppClass;
 
// C++ class with an Objective-C member variable
class CppClass {
  public:
    ObjcClass *objcClass;
};
 
// Objective-C class with a C++ object as a property
@interface ObjcClass : NSObject
@property (nonatomic, assign) std::shared_ptr<CppClass> cppClass;
@end
 
@implementation ObjcClass
@end
 
// Using the two classes above
std::shared_ptr<CppClass> cppClass(new CppClass());
ObjcClass *objcClass = [[ObjcClass alloc] init];
 
cppClass->objcClass = objcClass;
objcClass.cppClass = cppClass;

It’s as simple as that! Note the property is declared as assign, as you cannot use strong or weak because these don’t make sense with non-Objective-C object types. The compiler cannot “retain” or “release” a C++ object type, because it’s not an Objective-C object.

The correct memory management will still happen with assign because you’ve used a shared pointer. You could use a raw pointer, but then you would need to write the setter yourself to delete the old instance and set the new value as appropriate.

Note: There are limitations with Objective-C++. It is not possible for a C++ class to inherit from an Objective-C class and vice versa. Exceptions are also an area to be careful with. Recent compilers and runtimes do allow C++ exceptions and Objective-C exceptions to co-exist, but care should be taken. Make sure to read the documentation if you use exceptions.

Objective-C++ can be extremely useful because sometimes the best library for the task is written in C++. Being able to use it without any issues in an iOS or Mac app can be invaluable.

It should be noted though that Objective-C++ does have its cautions. One is memory management. Remember that Objective-C objects are always on the heap, but C++ objects can be stack or heap allocated. A stack object as a member of an Objective-C class is therefore a bit strange. It will actually be on the heap, because the entire Objective-C object is on the heap.

The compiler ensures this happens by automatically adding in code to alloc and dealloc to construct and destruct “stack” C++ objects. It does this through creating two methods called .cxx_construct and .cxx_destruct for alloc and dealloc to call respectively. In these methods, all the relevant C++ handling is performed as necessary.

Note: ARC actually piggybacks on .cxx_destruct; it now creates one of these for all Objective-C classes to write all the automatic cleanup code.

This handles all stack based C++ objects, but you should remember that any heap based C++ objects would need creating and destroying as appropriate. You would create them in your designated initializer and then delete them in dealloc.

Another caution with Objective-C++ to be aware of is leaking the C++ dependency. This should be avoided as much as possible. To see why this is a problem, consider the following class which makes use of Objective-C++:

// MyClass.h
#import <Foundation/Foundation.h>
#include <list>
 
@interface MyClass : NSObject
 
@property (nonatomic, assign) std::list<int> listOfIntegers;
 
@end
 
// MyClass.mm
#import “MyClass.h”
 
@implementation MyClass
// …
@end

The implementation file for MyClass has to be a .mm file because it’s making use of C++. That’s fine, but consider what would happen if you wanted to use MyClass; you would need to import MyClass.h. But in doing so, you are importing a file that makes use of C++. Therefore that other file needs to be compiled as Objective-C++, even though it doesn’t want to use C++ itself.

Therefore it’s best to minimize the amount of C++ usage in your public headers if at all possible. You can use private properties or instance variables declared in the implementation to achieve this.

Where to Go From Here?

C++ is a great language to learn about. It has similar roots to Objective-C, but it’s chosen to do things in a very different way. Learning about C++ can give a good insight into object oriented programming in general. In turn, this helps you make better design decisions in your Objective-C code.

I encourage you to read more about C++ and try it out for yourself. There is a great resource over at learncpp.com if you want to read more about the language.

If you have any comments or questions on this, or C++ in general, come join the discussion below!

Introduction to C++ for iOS Developers: Part 2 is a post from: Ray Wenderlich

The post Introduction to C++ for iOS Developers: Part 2 appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>