Wed, 10 Mar 2010 06:31:53 GMT

Retaining C++ Objects

C++ and Objective-C do not always mix too well, although the two languages are fully compatible.

With respect to compilation phases, alternating between C, C++, Objective-C and Objective-C++ presents no problem. You just write your software accordingly, freely mixing C, Objective-C, C++ and Objective-C++. Just make sure that the compiler knows what each translation unit contains, normally by source file extension: c for C; cc or cpp or cxx for C++; m for Objective-C; and mm for Objective-C++.

That being specified correctly, your sources compile and link without issues. Only when you start running the code side-by-side however, you might find that memory management can become something of a mishmash. Basic C++ fundamentally has no memory management; I mean nothing automatic. Asking for memory when you want it, freeing memory when you’re done with it: that is all bare C++ gives you. So you typically just roll your own, or find some more-advanced alternative such as Boehm’s garbage collector.

Wouldn’t it be nice however, if you could just simply retain, release and auto-release your C++ objects, just like you do with NSObjects? It would be very nice.

This article outlines a little “trick” for retaining C++ objects on Mac and iPhone. It takes advantage of C++ inheritance; and is simple, so no dependence on internals of CFTypeRef or NSObject. The memory cost is low; just one additional NSObject-derived object per retained C++ object. Final feature, implementation separates from interface by design. Hence you can include retaining within bodies of source without the things being retained having any knowledge of Objective-C. Translation units remain plain old C++, no additional dependencies. The implementation exists apart.

MIT-licensed code provided in full.

Who Cares?

The audience for this topic are developers wanting to integrate C++ libraries with Objective-C on iPhone or Mac under the retain-release management paradigm. C++ objects become subject to standard Apple memory management, even without classes being aware of it.

Start With a Test

Always best: start with a test.

Basic failing test

Allocate a C++ object. Then autorelease it. Place this within an autorelease pool context. Initially test-failing gives a leak. Test-success eliminates the leak. This is the test.

In code form, it looks like this.

// Instantiate an instance of TestClass. Retain then auto-release it. Within
// the context of an auto-release pool, will it delete?
pool = [[NSAutoreleasePool alloc] init];
(new TestClass)->retain().autorelease();
[pool release];

TestClass has the following basic definition.

class TestClass : public c_plus_plus_retained
{
public:
    TestClass()
    {
        log("hello");
    }
    ~TestClass()
    {
        log("goodbye");
    }
private:
    void log(const char *message)
    {
        // print message
    }
};

While c_plus_plus_retained has the following declaration.

struct c_plus_plus_retained
{
    // constructors & virtual destructor
    c_plus_plus_retained() : retainer_object(0) {}
    c_plus_plus_retained(const c_plus_plus_retained &copy) : retainer_object(0) {}
    virtual ~c_plus_plus_retained() {}

    // mirroring NSObject semantics
    c_plus_plus_retained &retain();
    void release();
    c_plus_plus_retained &autorelease();

    // accessor
    void *retainer();

private:
    void *retainer_object;
};

The above code declares a pure C++ public interface; notice there are no includes hence no other dependencies. Any general C++ translation unit can see this interface declaration without baulking. It contains nothing about Objective-C or Objective-C++. Such dependencies appear at link- and run-time, not at compile-time.

This is by design. You can include this header in any C++ source.

Successful Implementation

The implementation allocates an NSObject-derived retainer which deletes the retained C++ instance during its -dealloc method.

c_plus_plus_retained &c_plus_plus_retained::retain()
{
    if (retainer_object)
    {
        [(CPlusPlusRetainer *)retainer_object retain];
    }
    else
    {
        retainer_object = [[CPlusPlusRetainer alloc] initWithRetained:this];
        // Hereafter you must never delete this object. Doing so will break the
        // implicit design contract. Asking to retain actually places the
        // retained object within the scope of retain-release memory
        // management. Only releasing hereafter will release the retainer and
        // delete the object. Henceforward, delete becomes release. Calling
        // delete will double-delete this object.
    }
    return *this;
}

void c_plus_plus_retained::release()
{
    if (retainer_object)
    {
        [(CPlusPlusRetainer *)retainer_object release];
    }
}

c_plus_plus_retained &c_plus_plus_retained::autorelease()
{
    if (retainer_object)
    {
        [(CPlusPlusRetainer *)retainer_object autorelease];
    }
    return *this;
}

void *c_plus_plus_retained::retainer()
{
    if (retainer_object == 0)
    {
        retain().autorelease();
    }
    return retainer_object;
}

Semantically, the c_plus_plus_retained class initially has no retain count. It remains initially invisible respecting retain-release memory management. This changes when you retain it for the first time. At that point, you no longer delete the object. You must release it, doing so manually or by auto-releasing.

Beware the C++ Copy Constructor!

By default the compiler supplies a bitwise copy that copies everything verbatim. If you create a copy however, the duplicate has no retainer. If you want duplicates to retain then invoke retain(). If you want duplicates to auto-release invoke retained->retain().autorelease() assuming ”retained” is a pointer to c_plus_plus_retained or its sub-class.

Grab a Copy

The repositories live on GitHub at Library of useful C++ classes for iPhone and Mac and iPhone app project encapsulating tests for c_plus_plus_retained. Drop the iPhone app project into the Library folder then open, build and run. You’ll see something like this (memory addresses will vary, of course).

Screenshot


Trackbacks

Use the following link to trackback from your own site:
http://blog.pioneeringsoftware.co.uk/trackbacks?article_id=19

Comments

Leave a comment

(never displayed)

Markdown enabled