Using C++ safely in Blocks (and lambdas) for the Objective C programmer

Dave MacLachlan
7 min readDec 3, 2018

--

Block syntax was introduced by Apple to clang in 2008 and has slowly been sprinkled throughout the operating system frameworks ever since. It pre-dated lambdas in C++ (which appeared in C++11) and is compatible with C, Objective C and Objective C++.

A lot of documentation has been written about using Blocks in Objective C, such as the infamous gosh-darn-block-syntax site, and the perils of retain loops in blocks. The “strong-self/weak-self” pattern is well established for Objective C programmers, but very little appears to have been written about using (Objective) C++ safely with blocks, which is unfortunate, because it is really easy to mess up.

Let’s take the “simple” Objective C example:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
});

How would we code that with C++ classes instead of Objective C classes. Something akin to:

__weak __typeof__(this) weakThis = this;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(this) strongThis = this;
strongThis->DoSomething();
});

Note that the above code does compile and “run”(assuming you’ve defined the required bits and pieces), and only gives you a warning in Xcode 10.1:

 ‘__weak’ only applies to Objective-C object or block pointer types; type here is ‘typeof (this)’ (aka ‘MyClass *’)

This is unfortunate because ignoring the warning is very hazardous (may I suggest -Werror=ignored-attributes to make this an error). The __weak qualifier here is a complete no-op on the C++ this pointer.

Officially “It is undefined behavior to perform an operation with weak assignment semantics with a pointer to an Objective-C object whose class does not support __weak references.”. I couldn’t find anything in the ARC documentation about __weak on a pointer to a non-Objective-C object, so I guess that makes it undefined as well.

So semantically (ignoring the undefined behavior, and focusing on the observed behavior) the code above is equivalent to:

dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
this->DoSomething();
});

which unfortunately compiles cleanly without any warnings/errors at all.

The good news about this code is that we can’t get a retain loop in C++ because there is no reference counting. The bad news is that if our C++ object gets disposed of before our block is finished, the this pointer is going to be a dangling bad pointer.

It should be noted that there is a part of the Block specification devoted to C++ objects which says: To support member variable and function access the compiler will synthesize a const pointer to a block version of the this pointer. Which means that the block above can be simplified even more to:

dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
DoSomething();
});

but it still contains a (arguably even more hidden due to the implicit this) dangling pointer problem. Also note that it is a const pointer to this as opposed to a const pointer to a const this so the object referred to by this is mutable.

So what we need is something semantically equivalent to the “strong-self/weak-self” pattern that we can use in C++.

C++ does have a “strong-self/weak-self” pattern using std::shared_ptr/std::weak_ptr which seems to fit the bill. What we might naively think we want is something like this:

std::weak_ptr<MyClass> weak_this(this);
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});

Unfortunately there is no weak_ptr constructor that takes a naked pointer, so this doesn’t compile at all. The only way to get a weak_ptr is from a shared_ptr. You may be tempted to do:

std::shared_ptr<MyClass> shared_this(this);
std::weak_ptr<MyClass> weak_this(shared_this);
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});

which will compile just fine. Unfortunately you will find your this pointer being deallocated when shared_this goes out of scope which likely means it is going to be deallocated twice (once when the shared_ptr goes out of scope, and once when whatever allocated this decides to dispose of it), and very unlikely that weak_this is ever going to give you a strong_this because shared_this is likely to go out of scope before the asynchronous block gets a chance to run.

Fundamentally this is a problem with std::shared_ptr in that getting a std::shared_ptr for this is not possible for a this that is already being managed by a std::shared_ptr without having a reference to that managing std::shared_ptr. Luckily the C++ standard has a solution for us, and that solution is std::enable_shared_from_this.

So that means we have to expand our example out a bit to catch all the relevant bits:

#include <memory>
#include <dispatch/dispatch.h>
class MyClass : std::enable_shared_from_this<MyClass> {
public:
MyClass() {
std::weak_ptr<MyClass> weak_this(shared_from_this());
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});
}
private:
void DoSomething() { printf("Hi!\n"); }
};
int main() {
MyClass a;
sleep(5); // To give the async op time to run
}

The code sample above will compile, but sadly it is absolutely riddled with errors.

When it is first run it will abort withterminating with uncaught exception of type std::__1::bad_weak_ptr: bad_weak_ptr thrown from shared_from_this(). Reading carefully through the documentation you will find that:

It is permitted to call shared_from_this only on a previously shared object, i.e. on an object managed by std::shared_ptr.

Alright, so we update our code to fix that (updated code in bold):

#include <memory>
#include <dispatch/dispatch.h>
class MyClass : std::enable_shared_from_this<MyClass> {
public:
MyClass() {
std::weak_ptr<MyClass> weak_this(shared_from_this());
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});
}
private:
void DoSomething() { printf("Hi!\n"); }
};
int main() {
std::shared_ptr<MyClass> a(new MyClass);
sleep(5); // To give the async op time to run
}

It compiles and runs and still throws an exception at the same place. Reading carefully through more documentation:

Publicly inheriting from std::enable_shared_from_this<T> provides the type T with a member function shared_from_this.

I added the emphasis on ‘publicly’. Would be really nice if the compiler enforced that little gotcha’. So we fix that up:

#include <memory>
#include <dispatch/dispatch.h>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
MyClass() {
std::weak_ptr<MyClass> weak_this(shared_from_this());
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});
}
private:
void DoSomething() { printf("Hi!\n"); }
};
int main() {
std::shared_ptr<MyClass> a(new MyClass);
sleep(5); // To give the async op time to run
}

It compiles, runs and still throws an exception at the same place. Reading carefully yet again:

…in particular, shared_from_this cannot be called in a constructor

since the std::shared_ptr that is going to wrap this has not been constructed yet.

#include <memory>
#include <dispatch/dispatch.h>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void Register() {
std::weak_ptr<MyClass> weak_this(shared_from_this());
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});
}
private:
void DoSomething() { printf("Hi!\n"); }
};
int main() {
std::shared_ptr<MyClass> a(new MyClass);
a->Register();
sleep(5); // To give the async op time to run
}

which finally compiles and runs to completion. Note that to make our class safe for other users who don’t know about the shared_ptr requirements, we should probably hide our constructor and give our users a factory method to work with that forces good behavior:

#include <memory>
#include <dispatch/dispatch.h>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
static std::shared_ptr<MyClass> Create() {
return std::shared_ptr<MyClass>(new MyClass());
}
void Register() {
std::weak_ptr<MyClass> weak_this(shared_from_this());
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
auto strong_this = weak_this.lock();
strong_this->DoSomething();
});
}
private:
MyClass() {}
void DoSomething() { printf("Hi!\n"); }
};
int main() {
auto a = MyClass::Create();
a->Register();
sleep(5); // To give the async op time to run
}

Hurrah! Finally a block safe call back (I think…).

Note that I used std::shared_ptr<MyClass>(new MyClass()); instead of std::make_shared<MyClass>();. This is for a couple of reasons:

  1. Since I made my constructor private, I would have to go through a lot of convolutions to get the compiler to allow me to compile.
  2. Even if I get it compiling, std::make_shared is not always necessarily the best choice when working with std::weak_ptrs because of potential memory issues.

You may be tempted to think that you could avoid all of these convolutions with your own wrapper class. Something with a mutex that protected a naked pointer to this that you would reset in your destructor. The versions that I have seen have wrapped the this pointer in an Objective C class with a mutex. Probably because using Objective C kept the weak-self/strong-self paradigm that they were already familiar with. Something like:

#import <Foundation/Foundation.h>@class Wrapper;class MyClass {
public:
MyClass();
void DoSomething() { printf("Hi!\n"); }
private:
Wrapper *_wrapper;
};
int main() {
MyClass a;
sleep(5); // To give the async op time to run
}
@interface Wrapper : NSObject {
MyClass *_wrapped;
}
@end
@implementation Wrapper
- (instancetype)initWithMyClass:(MyClass *)wrapped {
if ((self = [super init])) {
_wrapped = wrapped;
}
return self;
}
- (void)reset {
@synchronized(self) {
_wrapped = nullptr;
}
}
- (void)doSomething {
@synchronized(self) {
if (_wrapped) {
_wrapped->DoSomething();
}
}
}
@end
MyClass::MyClass() {
_wrapper = [[Wrapper alloc] initWithMyClass:this];
__weak Wrapper *weakWrapper = _wrapper;
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
Wrapper *strongWrapper = weakWrapper;
[strongWrapper doSomething];
});
}
MyClass::~MyClass() {
[_wrapper reset];
}

Unfortunately you will always have a race condition between the time your object (MyClass) starts being destroyed, and when you can null out that naked pointer. If MyClass has a subclass it’s possible that it could be in a partially destroyed state if the callback comes between the time the destructor starts running and [_wrapper reset] is called. As long as you guaranteed that MyClass had no subclasses, and [_wrapper reset] was the first thing you called in your destructor you may be alright here.

Also see my post on Objective C Encoding, and discover that by mixing C++/Objective C the way it’s being done above is just setting yourself up for some really nasty Objective C metadata generation depending on how complex MyClass is.

Note that all of the above generally applies to lambdas as well. There is nothing magical about lambdas that will save you. These are not lambda/block problems, they are threading problems. Blocks and lambdas just make it easier to get bitten by them.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Dave MacLachlan
Dave MacLachlan

Responses (2)

Write a response