[Objective] C++: What Could Possibly Go Wrong?

Considering the “beautiful madness” of Objective-C++, Peter Steinberger looks at the situations where it’s a great tool to use, those where you really shouldn’t, and how to avoid shooting yourself in the foot while trying. For example, did you know that modern C++ has its own version of ARC, weak, and blocks? We learn to identify common performance bottlenecks, and focus on a small subset of C++ that will be an immediately useful addition to your toolbox.


[Objective] C++ (0:00)

My name is Peter Steinberger, @steipete on the Internet, and I’m glad that so many people have come to hear about Objective-C++. I expected most people to run away as soon as they saw C++ on the title slide. Let’s first talk a little about whom this is for. I expect that you’ve seen C++ before and that you care about elegant and performant code. Maybe your world doesn’t end with iOS or Mac, and you want to come to the dark side. Even if you’ve never seen or any written C++ before, you’ll be able to follow. It’s not that hard, and I hope to remove some of your fears. I’ve worked on a PDF framework since 2011. We were on iOS for a long time, and we’re on Android now as well. There’s a high chance that you have at least one copy, if not multiple copies, on your phone. If you have Dropbox, Microsoft SkyDrive, Box Silent Mode, or ScanPod, you have a copy. We’re also in pretty much every board-meeting app on the planet. We traditionally avoided C++ like the plague and worked around it in Objective-C. Only at the beginning of the year did we see the light and make the switch, and it brought better performance and way less blocks in our product.

Every Malloc is a Mistake (2:15)

I want to start with an interesting statement. Every malloc is a mistake. I have written a ton of mallocs and frees. However, there are so many ways you can shoot yourself in the foot with it. Of course, you could work around it. You could use NSMutableData and then use that pointer so don’t have to call malloc and free, but in essence it’s still a pointer and inherently unsafe.

What about Swift? (2:57)

How does Swift play into all of this? We’ve seen the keynote, we have a great Swift tool announcement, and Swift is really awesome. I’m a fan of Swift. It’s a very different tool with different strengths and drawbacks. First, Swift interoperates with Objective-C and C, but it doesn’t work with C++ directly. Therefore, if you want to use Swift, being knowledgeable of Objective-C++ might actually help you, because it enables you to write a wrapper that wraps any third party library that might only exist in C++ with Objective-C. Then, you can use that library in Swift. Our main concern is binary compatibility, so we create a binary framework. You pay money for our framework, and it works great with some minor external updates, because Apple updates the Swift standard library. If it were incompatible, you would not like us. That’s a very convincing reason why we can’t use Swift, so lets look more into Objective-C++.

This is not your Grandmother’s C++ (5:50)

At first, C++ was horrible, but now things are going really well. Every major compiler supports C++14 and they’re close to releasing C++17, further refining the language. C++ has great tooling, a great compiler (it’s Clang, after all), and a great ecosystem. It’s cross platform so it pretty much compiles everywhere you can compile C. It is really, really fast. It has a powerful standard library — granted there are unexpected holes, but there is a vibrant third party ecosystem, so if you miss anything from the standard library, Google “Boost”, it’s basically whatever you wished for or ever wanted. There’s also a small culture, so beware, if you use CocoaPods you actually get down-ranked when you use Objective-C++.

This is not your grandmother’s C++. We have auto, which is type deduction. We have shared pointers, which is basically ARC. We have weak, which is also something we have in the Objective-C run time. We have lambdas, which is just a fancier name for blocks, and we have move semantics, which is something I’ll explain later because it’s a little crazy.

Get more development news like this

Auto and Range-Based For Loops (6:13)

First of all, lets look into auto and range-based for loops. As I said, C++98 is horrible and now maybe you understand why. Using vector with CGPoints is the best way to store many points. With C++98, you had to create an iterator, call begin and end, increase the iterator, and then reference it to get the object. C++11 made this a lot nicer and introduced us to for in syntax, so you only have to give the type and director. They didn’t stop there. You don’t even have to give the type, because the compiler already knows what’s in the vector because it is generic, and you can just give the order. You don’t have to stop there, either. You don’t have to say auto at all, because the compiler knows what you want. This was already was in Clang, but got removed again because there were edge cases that you haven’t yet figured out. It’s planned for C++17. So they’re constantly tweaking, improving, and simplifying syntax.

std::vector is probably the most useful thing there is. You can put in anything and it’s a continuous block of memory, so if you put in CGPoints you can always ask it for the raw point pointer, then can use C functions to access it, and it will throw exceptions if you do anything wrong. But beware, raw pointer access, which is written into invalid memory, might cause your app to crash. It also has really powerful mutators like rotate. For consistency reasons, there’s also a standard array, which is kind of like a vector, but fixed in size and even more efficient. In practice, a vector is so fast you never need the other one.

Next up is initializer lists. In C++, if you had a vector and you wanted to fill it, it was a little bit annoying, but now they’ve fixed that.

// C++11
std::vector<int> v = {1, 2, 3, 4, 5};

You’ll notice the syntax actually looks very similar to what we have in Objective-C, and C++ actually had it before. Imagine a function that returns a vector of developers. Every developer is an object that’s allocated somewhere the vector for all these pointers. The problem is, after you’re done with it, you have to make sure you clean it. We don’t have ARC, like in C++, so what you actually had to do is, with the old syntax, iterate over it and then call delete on every object. Smart pointers are the ARC concept for C++. So, what we do instead is use a unique pointer of developers, and if you look at the memory layout there is no difference. A unique pointer is basically just a pointer. Everything else is done in code, but the compiler makes sure you add the new and delete accordingly. You don’t have to think about it and everything will be correct. This is how you add such a vector, so you would call push back, create a unique pointer, cast it, and then you create and new developer. However, we are breaking the rule number one, that every malloc is a mistake. In the new C++ world, every new and every delete is a mistake. We’re not actually using this syntax. We’re not saying push back, but in place back, which is more optimized. Then, we use make unique, which is basically the same but even more memory efficient. Under the hood, make unique does only one malloc call and calls all the space you need for developer and for the pointer at once.

// C++17
for (point : v) {
    reticulateSpline(point);
}

Pointers (11:17)

There are four main pointers. There’s auto pointer, unique, shared, and weak — you forget the first one immediately because it’s terrible. It took years to figure out how to do this right, and in a way that works under pretty much every condition, so my advice: never use auto pointer. You want to use unique pointer or shared pointer if you share an object. You can even convert between the two very easily. Sometimes you need something weak, but it’s a rare case, much like you have rare uses for weak in Objective-C. It’s basically the same syntax as with the raw C array and, as I said, it converts automatically between unique pointer and shared pointer.

Move Semantics (12:14)

Move semantics, are actually my personal favorite. Imagine another function where you have a vector and you want points. So you call get points and it returns a vector of points. What would happen in the old world is maybe a get points function creates one million points and returns those one million points and they get copied. This will not be really fast, and that’s why you always return your pointer in C. Of course, we’re going to get rid of pointers, so in C++ they figured out move semantics. Move semantics is that weird saying with two ends. It’s called an rvalue, but a compiler basically figures out that what you create is not used anywhere else, just where you capture it. Then, instead of copying it, the compiler just moves the data. This works because both the vector that’s being created and the vector that captures it are the same, and they know each other, so the compiler just calls this move method. It creates the new vector, which knows where the old vector stores its secret data, then just steals that data and sets the data to null for the other object. So, in essence, you still copied the object, but in a way that’s very efficient and copies any of the data.

// get all the points
vector<CGPoint> points = get_points();
vector(vector&& that) {
    data = that.data;
    that.data = nullptr;
}

Here is a much simpler example. Think about a variable a and another variable b. Whenever you say a is b, you invoke a copy because b still exists and a is a new copy of it. But if you say a is x + y you invoke a move, because the result of x + y will be a new variable, a temporary one; the compiler will understand that nobody else is capturing this variable and then do the first thing.

auto a = b;     // copy
auto a = x + y; // move

Lambdas (14:32)

We also have lambdas. As I mentioned, lambdas are just another word for block. The syntax is different because everybody has to have different block syntax, and the other one was already taken. If you compare them to cars, blocks would be like automatic cars. They get you everywhere, and they’re nice and really easy to use. Lambdas are like manual cars, you have to know a little better how to use them, but when you do, you can get to your destination a lot faster and have more fun while you’re doing it. Depending on how you define fun, of course. Lambdas either capture nothing, capture by reference, or capture by making a copy. In Objective-C, usually we copy everything and ARC does the right thing; here, we have this underscore underscore block to the reference so, in essence, you get everything that you have in Objective-C and more. There is something even better about lambdas: imagine this very, very hypothetical code space where you have a CGPDF dictionary; because it’s a C function, you have to have a way to iterate over the dictionary, and the only way to do that in C is to call an apply function, which will need a function pointer. Maybe that call is very deep in your code, so you have to scroll a few lines up, place this parse function, then do all the casting; it’s really not a great experience to read this. What you could do instead is use a lambda, and just show it in the lambda. A lambda doesn’t capture any variables, it’s guaranteed to be like a function pointer. You don’t want to know how this works under the hood but it does, it is defined in the spec, it’s actually a very late edition, but it works beautifully and makes the code a lot easier to read once you get over those square brackets. But you’re Objective-C developers, you like square brackets anyway. This is one of those little details that makes parsing code a lot nicer.

auto parseFont = [] (const char *key, CGPDFObjectRef value, void *info) {
    NSLog(@"key: %s", key);
};
CGPDFDictionaryApplyFunction(fontDict, parseFont, (__bridge void *)self);

Where do I Start? (16:57)

So where do you start? C++ is so big and so crazy. You could either start with C++ tutorials or, if you already know a little, just dive in and buy Effective Modern C++. It will overwhelm you at first, but there are a lot of good resources on the Internet, you can just Google all the little details that you don’t know and you will be inspired by the ways problems can be solved differently. Objective-C++ holds the promise of freely mixing C++ and Objective-C. Apple has great documentation, which unfortunately is no longer online (hunt for it!) — they don’t publicly care about it. Of course there are limits, so you can’t just create a subclass in C++ from Objective-C or the other way around. Memory models are very different. You can, however, put C++ objects into ivars of the Objective-C object or the other way around. The goal isn’t really to write UIKit with C++. You use C++ when it’s necessary, and when it’s the better choice.

Gotchas (18:10)

There are gotchas! One thing you’ll notice when you play with Objective-C++ is compile time. Depending on how much of it you start using, things will get slower, and they might take twice or three times as long. It’s still much faster than compiling Swift (at the moment), but it is noticeable and you should be mindful of not blindly renaming all your files to .mm because you can, only where it makes sense, and where you should. There is another gotcha, which is properties. Apple fixed a lot of problems around properties with C++ objects. They still don’t always work, so I would just suggest using ivars and writing your accesses manually. Maybe they’ll now work in Xcode, but if you use ivars you’re on the safe side. C++ is also a stricter compiler, so it could happen that you just renamed your files to .mm and then you get warnings or errors. Usually, that’s actually a good thing, because you are doing something that was legal under C but not legal in C++. Maybe you were doing a wicked cast, or using some enum in a way that it shouldn’t be used, but in most of the cases it’s very easy to fix. If you’re really doing something weird, and you know it’s weird, you can just cast it away, but usually it’s a good thing.

Projects Using Objective-C++ (19:48)

Lets talk about projects that use Objective-C++. First of all, we have the Objective-C runtime — if you have ever wondered how to actually implement weak, that’s a C++ dense_hash_map. WebKit is C++, WKWebView and UIWebView are heavily Objective-C++ projects. Realm, the mobile database, is C++ at the core, and they use Objective-C++ not just to communicate with the core but also to wrap things like C arrays, and other nice additions where it makes sense. If you want to see really crazy C++, Facebook’s Pop uses a template-based spring solver in C++, so that’s definitely an interesting read. There’s also ComponentKit, which was one of Facebook’s attempts at improving layouting, and one of the components that drives the new speed in Facebook. Maybe Facebook’s future is in React Native, maybe it is ComponentKit, but ComponentKit remains a very interesting project for heavy use of C++. Another great project that we use is Dropbox’s Djinni. Djinni is like an interface definition language that creates C++, Objective-C, and Java, and does all the wrapping via Objective-C++ or JNI. This is especially useful when you do cross platform work.

Let’s go back to ComponentKit, because it is a really nice example of where Objective-C shines. Here (see below), to create a stack layout component and to use aggregated initialization, they add three children. I think this is really nice to read. If you do that in Objective-C, the main problem is that there is no default method. You could make the Objective-C version a little nicer if you implement all these four lines with our top heading, left heading, bottom heading, but that’s very tiresome. You’d have to do all of this manually and then refactor, and it’s still not as nice as the C++ version.

[CKStackLayoutComponent
    newWithStyle:{
        .direction = CKStackLayoutComponentDirectionVertical,
    }
    children:{
        {[HeaderComponent newWithArticle:article]},
        {[MessageComponent newWithArticle:article]},
        {[FooterComponent newWithArticle:article]},
}];

Aggregate initialization is not a C++ feature, but a C feature. Maybe you have already used it, but that is a very simple and very stupid example of how you could do this with points. You could just set the part, and the rest will be automatically initialized zero. Since you pointed mine doesn’t make much sense, but for the example before it made a lot of sense.

CGPoint p1 = (CGPoint){ .x = 3 };
CGPoint p2 = (CGPoint){3, 0};

Why Objective-C++? (21:17)

Another nice feature is type safety. At this year’s WWDC we were given Objective-C generics, which are some nice technical sugar. I’m very excited about them, I laughed at the edit, but all you get is a compile warning much like with nullability. You can still add three layout components in one string and then things will blow up magically at run time. With the vector it will not let you compile things unless you do an evil cast. One reason is efficiency. C++ is a lot faster with loops or data structures. Whenever you have trouble, C++ will save the day.

Another reason to use Objective-C++ is nil safety. If you look at the sample below, maybe these are not strings, maybe they are dynamically created layouts — some of them might be nil because you don’t yet have the footer, or you don’t yet have the picture, and they might just be nil. So, the first line would just crash at run time. The second line will work as expected because vector actually allows you to put in null pointers: it will say it has four objects, and the object with the index two is null pointer and that is perfectly fine. There are ways to filter that out, we can use a lambda and it’s very easy to compact it to only have actual data in it. This example with Objective-C++ looks really compact and creates a vector. The same example with Objective-C might be a lot worse to write. It’s more than twice as much code and isn’t faster.

auto vector = vector<NSString*>{@"a", @"b", nullptr, @"d"};
vector.erase(remove(begin(vector), end(vector), nullptr), end(vector));

children:{
headerComponent,
messageComponent,
attachmentComponent,
footerComponent
}

Block Syntax (24:50)

Another nice feature is Objective-C block syntax, which can be used to create an inline variable by hand. I’ve written code in Objective-C since 2009, and I still have to look up f*ckingblocksyntax.com because I don’t get it right and miss a weird character. I create inline blocks a lot, because they make the code nicely structured. What you could do instead is just use id, because every object of C block is an object, then feed it into NSURLSession and you’re good to go. But perhaps you messed up the signature, perhaps you forgot that you’re getting NSData, perhaps you think you already have the string and if you use id the compiler will not know what it is, and will assign the e then blow up at runtime. If you can help it, you really don’t want to blow up your customers, you want to blow up at compile time. Instead of using id you can use auto. auto does the same thing but directly in first type, without having to give the type. This feature is purely a compiler feature, you don’t even need to link it with a library. Sure, you need to rename it with .mm, but that is actually one of my favorite little helpers which makes using the language that little bit nicer.

auto handler = ^(NSData *data, NSURLResponse *response, NSError *error) {
    // parse data
};
[NSURLSession sharedSession] dataTaskWithURL:URL completionHandler:handler];

Operator Overloading (27:08)

Another small example that we use is comparison. I’m sure everyone has written code like this before (see below), where you need an NSComparisonResult and you need it for integers, or enumerations, or CGFloat, and so on. And then, you have four or five different functions that are all the same. You can solve this with a macro, but you really don’t want to have more macros in your code, do you? You can solve this with a template, which you only have to write once, and it will be correct every time. You also can overload operators. Before Swift, everyone complained about operator overloading. Now, with Swift, everybody is super excited about operator overloading. You can do the same with C++. So, we just have operators for CGPoints, CGSize, your edge index, adding, removing, multiply, equal, all those little things that make the code a little nicer to read and not really any more complicated to write.

template <typename T>
inline NSComparisonResult PSPDFCompare(const T value1, const T value2) {
    if (value1 < value2) return (NSComparisonResult)NSOrderedAscending;
    else if (value1 > value2) return (NSComparisonResult)NSOrderedDescending;
    else return (NSComparisonResult)NSOrderedSame;
}

My last example is something that’s also from our code. You know PDF is a little crazy, well there is one annotation that is a line annotation and it’s kind of different from line endings. There are about ten different lines and types, so creating a path is actually quite complex. When you create a path there is a stroke and a fill path, so we need a helper for that. We have made a helper called CreatePathsForLineEndType (see below): we give it the line end type, the points, the point count, and the width, then we have two pointers to Core Foundation objects that give us the parse out. It’s actually simple enough, but there are a lot of ways that I can mess up calling this function. For example, maybe I’m just interested in the stroke and I don’t care about the fill, so I set nil on the fill, but I don’t know if that will actually work. If it’s well written it should work, but it could just try to de-reference the nil pointer and crash. Or it could be written in a way that leaks, because I get the CGPass but I still have to care about releasing it, and the usual way is done by convention because the function is called create. I just assume that I will get an object with a reading count of one, but also fear that this is auto release and I have to retain count it myself. That’s also something that a compiler, a static analyzer might be able to help you with. Of course, when I have a pointer generated that has two points, I will read invalid memory and bad things might happen.

void CreatePathsForLineEndType(PSPDFLineEndType const endType,
    CGPoint const* points,
    NSUInteger const pointsCount,
    CGFloat const lineWidth,
    CGPathRef *storedFillPath,
    CGPathRef *storedStrokePath);

The first step where we could convert this to make it a little better is merge points and pointsCount, and just use a vector. You’ve seen vector before. The nice thing: it’s about as efficient, but it knows how long it is, it knows what size is in there, so it’s almost impossible to mess up. If I use index and call outside of bounds it will throw an exception, I’ll know that I did something wrong. But, down there, you see how easy it actually is to convert raw pointer array to a vector. There are different ways — notice one is round and one is like an initializer list — so even if you have a code base with a lot of these functions you can convert it bit-by-bit, just create a vector on the fly and then call it in a safer way.

void CreatePathsForLineEndType(PSPDFLineEndType const endType,
    std::vector<CGPoint> const& points,
    CGFloat const lineWidth,
    CGPathRef *storedFillPath,
    CGPathRef *storedStrokePath);
// copy points into a vector
auto vector = std::vector<CGPoint>(points, points+pointsCount);
auto vector = std::vector<CGPoint>{point1, point2, point3};

At some point you will be done converting and then the performance benefits will really pay off, but we are not done yet. We can do better. We still have those pointers that gave us the output, and we didn’t really know if we could add nil or not. What if we change this to use a tuple, which is just a C++ object that has first and second, and stores new objects? We can actually return that and then get the path we want. Notice that I renamed the function a little bit to make it easier to understand, so it’s now FillAndStrokePathForLineEndType — first will be fill, the second will be stroke, and now it’s almost perfect. One problem is that I still have to care about memory management. Maybe that function now uses auto release, so this could be a viable solution but you don’t really do that with Core Foundation objects.

The last thing we can do is write our own smart pointer that wraps CGPathRef and knows how to deal with Core Foundation objects. Don’t think I’m crazy, this is actually what WebKit does internally, they deal with a lot of Core Foundation objects and they want to get it right. We use something that’s CFPointer, and it’s not really much code, maybe 100 lines if you want to have move semantics and all those nice little details. Yes, it’s a little tricky to write, but you only have to write it once and then you can use it anywhere. If you compare with what we had before, the second version is a lot harder to mess up; however I call it, nothing bad will happen. With the first version, I need to know a lot about the platform, about the conventions, and I need to make sure to call it directly. Debugging works really well, and LLDB is your friend. Sometimes it’s even better at helping than it is with Objective-C objects. You can open up a vector, you can open up a map and look into it, pretty much like you’d expect.

tuple<CFPointer<CGPathRef>, CFPointer<CGPathRef>>
FillAndStrokePathForLineEndType(PSPDFLineEndType endType,
    std::vector<CGPoint> const& points,
    CGFloat lineWidth);

And that’s the end of my talk! Thank you.

Peter Steinberger

Peter Steinberger