Introduction to C++ for iOS Developers: Part 2

In part 2 of this introduction to C++ for iOS developers, you will learn about polymorphism, virtual functions, templates, the STL, and more. By Matt Galloway.

Leave a rating/review
Save for later
Share
You are currently viewing page 5 of 6 of this article. Click here to view the first page.

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.

Contributors

Over 300 content creators. Join our team.