Objective-C – Disadvantages to Bridging With C++?

So, I was bored today, and decide to mess with C++/Obj-C interpolation, and I found a way to create a very interesting setup.

@protocol NSCPPObj <NSObject>

-(id) init;
-(id) initWithInt:(int) value;
-(int) somethingThatReturnsAValue;
-(void) doSomething;

@end

class NSCPPObj : objc_object {
public:    
    static Class cls();

    int iVar;

    NSCPPObj();
    NSCPPObj(int);

    int somethingThatReturnsAValue();
    void doSomething();
};

As you can see, the interface is quite straightforward, and easy to understand. We create two (almost) identical interfaces, one for a C++ object, and another for a Obj-C protocol.

  • Calculating number of seconds between two points in time, in Cocoa, even when system clock has changed mid-way
  • Having trouble with fstream in Xcode
  • Building pure Swift Cocoa Touch Framework
  • Restrict NSTextField input to numeric only? NSNumberformatter
  • Image edge smoothing with opencv
  • Comparing float and double data types in objective C
  • Now, I found a way to implement this, but brace yourself, this gets ugly:

    // NSCPPObj.mm
    #import <objc/runtime.h>
    #import <iostream>
    
    #import "NSCPPObject.h"
    
    Class NSCPPObj_class = nil;
    
    __attribute__((constructor))
    static void initialize()
    {
        NSCPPObj_class = objc_allocateClassPair([NSObject class], "NSCPPObj", 0);
    
        class_addMethod(NSCPPObj_class->isa, @selector(alloc), imp_implementationWithBlock(^(id self) {
            return class_createInstance(NSCPPObj_class, sizeof(struct NSCPPObj));
        }), "@@:");
    
        class_addMethod(NSCPPObj_class, @selector(init), imp_implementationWithBlock(^(id self) {
            return self;        
        }), "@@:");
    
        class_addMethod(NSCPPObj_class, @selector(initWithInt:), imp_implementationWithBlock(^(id self, int value) {
            ((struct NSCPPObj *) self)->iVar = value;
    
            return self;
        }), "@@:i");
    
        class_addMethod(NSCPPObj_class, @selector(doSomething), imp_implementationWithBlock(^(id self) {
            ((struct NSCPPObj *) self)->doSomething();
        }), "v@:");
        class_addMethod(NSCPPObj_class, @selector(somethingThatReturnsAValue), imp_implementationWithBlock(^(id self) {
            return ((struct NSCPPObj *) self)->somethingThatReturnsAValue();
        }), "i@:");
    
        objc_registerClassPair(NSCPPObj_class);
    }
    
    Class NSCPPObj::cls()
    {
        return NSCPPObj_class;
    }
    
    NSCPPObj::NSCPPObj()
    {
        this->isa = NSCPPObj_class;
        [((id<NSCPPObj>) this) init];
    }
    
    NSCPPObj::NSCPPObj(int value)
    {
        this->isa = NSCPPObj_class;
        [((id<NSCPPObj>) this) initWithInt:value];
    }
    
    void NSCPPObj::doSomething()
    {
        std::cout << "Value Is: " << [((id<NSCPPObj>) this) somethingThatReturnsAValue] << std::endl;
    }
    
    int NSCPPObj::somethingThatReturnsAValue()
    {
        return iVar;
    }
    

    I’ll summarize what this does:

    1. Allocates a Class Pair
    2. Adds all class and instance methods to the object
    3. Registers the class Pair

    Now, as you can see, this isn’t very flexible, but it does work, and it’s a two-way street:

    id<NSCPPObj> obj = [[NSCPPObj::cls() alloc] initWithInt:15];
    [obj doSomething];
    
    NSLog(@"%i", [obj somethingThatReturnsAValue]);
    NSLog(@"%@", obj);
    
    NSCPPObj *objAsCPP = (__bridge NSCPPObj *) obj;
    
    objAsCPP->doSomething();
    std::cout << objAsCPP->somethingThatReturnsAValue() << std::endl;
    

    You can also create the object by using new NSCPPObj(15), but remember to delete it!
    Obviously, this can work in a ARC or non-ARC environment, but ARC requires a few extra bridged casts.

    So, I come to the real question:
    What are the pros/cons of this design structure? I can list a few off of the top of my head:

    Pros:

    1. Operator Overloading with C++
    2. Dynamic method binding with ObjC
    3. Can be constructed in either a C++ or ObjC fashion

    Cons:

    1. Hard-to-read implementation
    2. Selectors & bindings must be added for every C++ implementation added to the interface
    3. Class object cannot be referenced directly

    So, after all that, would you recommend this design structure in an application? and why.

    Solutions Collect From Internet About “Objective-C – Disadvantages to Bridging With C++?”

    So, after all that, would you recommend this design structure in an
    application? and why.

    No.

    It is a really nice bit of code; I particularly like the use of imp_implementationWithBlock() (but I admit I might be partial to that particular feature of the runtime ;). And, of course, explorations like this are always an incredibly valuable learning tool.

    The issue, in the context of “real world paying project” use, is that you are effectively creating a relatively generic bridge that will then have to have specific bridges at either end to interface with either typical C++ libraries or typical Objective-C APIs/libraries. To put it another way, you have effectively created a new runtime derived from an amalgamation of two existing runtimes.

    And, as you point out in the Cons, you pretty much have to touch, wrap, modify and/or debug a shim on top of every C++ class you want to bring into this pattern.

    In working with quite a bit of Objective-C++ code over the last 20+ years, a bridge like this is generally more trouble than it is worth. You would likely be better off — spend less time writing and debugging code — creating simple Objective-C wrappers around the C++ (or C, frankly) APIs that can then be integrated with and consumed by the targeted system’s Objective-C frameworks.