Back in March 2011 I wrote an article about mixing Objective-C and C++, specifically about using Objective-C object type members in C++ classes. This article even got tweeted by @mrfungfung (Tak Fung of Supermono Studios the developer of one of my favourite iPhone apps: Epic Win). Now, over one year later, Apple introduced a major new feature into its language of choice: Automatic Reference Counting or ARC for short. This new feature forces me to apologise for and “correct” what I said back then. As it turns out, there is actually something to the doubts I was already hinting at in the footnote to that post.
Let me quickly reiterate the problem: Objective-C is a strict superset of C, which can also be imposed on C++, resulting in the dialect called Objective-C++. This means that you can freely mix Objective-C and C++ code; you can even have Objective-C objects (i.e. pointers to them) as members in a C++ class (and vice versa, but thats not what this article is about). The problem is this: Since a standard C++ compiler does not know about the Objective-C type system, every C++ header file that declares a class having such a member variable and every file including such a header file has to be compiled as Objective-C++, not “pure” C++. This is undesirable for example when you want to wrap some functionality from Apples Cocoa Framework (which is written in and for Objective-C) in a C++ only library.
The article linked above presented a solution to this using the preprocessor both Objective-C and C++ use. Let’s say we have an Objective-C class called Cat
:
#import <Foundation/Foundation.h> @interface Cat : NSObject { } @end
We want to use this Cat
class in a Person
class, which will be written in C++. We also want the Person.hpp
header to be compilable by a pure C++ compiler. Objective-C compilers define the preprocessor constant __OBJC__
; C++ compilers don’t, so we can use this to conditionally define an “alias” for our Cat
type, as my old article suggested. We might come up with something like this:
#ifndef PERSON_HPP_INCLUDED #define PERSON_HPP_INCLUDED #ifdef __OBJC__ @class Cat; typedef Cat * CatPtr; #else typedef void * CatPtr; #endif class Person { public: Person(); private: CatPtr const cat_; }; #endif
Let’s first implement Cat
. It does not have any methods declared in its interface but we want to override dealloc
, just to see whether the Cat
objects we create will get deallocated correctly. Note that we are using ARC and thus a call to [super dealloc]
is forbidden (and unnecessary).
#import "Cat.h" @implementation Cat - (void)dealloc { NSLog(@"Meow!"); } @end
Since we use ARC, the implementation of Person
is equally simple:
#import "Cat.h" #include "Person.hpp" Person::Person(): cat_( [[Cat alloc] init] ) { }
As I already pointed out in the old article, this can be problematic when Cat *
and void *
fail to be layout compatible. I still haven’t found any reference to whether this is the case or not; most of the time everything seems to work and you can even hear Apple engineers talk about casting Objective-C object pointer types to void *
(if only to advise against it) in some WWDC talks. However, ARC definitively demonstrates that this approach is not the way to go.
To see this, you will first have to compile the above code into a library with ARC enabled. To enable ARC in clang
on the command line, for example, you can use the -fobjc-arc
option. Next you will need a test program to see the effect. Like this:
#include "Person.hpp" int main() { Person p; }
Its only purpose is to create an instance of our Person
class which can then — when going out of scope — be destroyed again. If you save the above program with extension .mm
, compile it with ARC support and run it, you will see “Meow!” printed to your terminal. ARC worked it’s magic, obviously, calling our dealloc
method in Cat
without us ever having to invoke release
.
Since you cannot put objects managed by ARC into C struct
s, this might come as a surprise. In Objective-C++, ARC continues to work when the managed objects are members of a C++ class. The ARC specification explicitly states what happens in section 4.3.5 (as of 2012/09/22):
This restriction [of not being able to have ARC-managed objects inside of
struct
s] does not apply in Objective-C++. However, nontrivally ownership-qualified types are considered non-POD: in C++11 terms, they are not trivially default constructible, copy constructible, move constructible, copy assignable, move assignable, or destructible. It is a violation of C++’s One Definition Rule to use a class outside of ARC that, under ARC, would have a nontrivially ownership-qualified member.
What this means (or what I read from it, that is) is this: ARC basically works by inserting additional code into your files, specifically calls to retain
, release
and similar reference counting related methods. In Objective-C++ it seems to also generate destructors (among other things) to put these invocations into, if there is no explicitly defined one yet. To any Objective-C++ compiler with ARC enabled (as the one that compiled our library), our Person
class now seems to have a non-trivial destructor. However, a simple C++ does not know about ARC and consequently does not see this destructor. This is what that cryptic last sentence of the above quote means and its effect can be seen when you rename our example program to have the extension .cpp
and compile it with a C++ compiler. Nothing should be printed if you run the new executable. In violation to the One Definition Rule, our program works with another version of the Person
class than our library does.
So what’s the moral? You probably should not use the approach I advertised in my previous article. As I already stated there, the Pimpl pattern is a safe alternative. This article is not supposed to be a Pimpl tutorial but I ported my old example code to using it just to give you an idea. Another way around this problem is to just avoid using ARC in situations like this. However, we still don’t know what other problems possible layout inompatibility can yield, so I would highly recommend not to do this.