Objective-C + C++ Exception/Ownership Boundaries

Understand the deeper friction points of mixing Objective-C and C++ in Objective-C++ (.mm) files: how C++ RAII and ARC's differing memory models coexist (or conflict), why C++ and Objective-C exceptions are separate mechanisms that generally cannot safely cross each other's stack frames, how to use C++ templates and the STL internally while keeping a public header pure Objective-C via the wrapper-class/pimpl pattern, and the header-compatibility rules that keep C++ types out of headers plain Objective-C and Swift code need to import.

The earlier 'C++ / Objective-C++' lesson introduced the basics: .mm files let a single translation unit mix Objective-C and C++ syntax, and you can hold a C++ object as a member of an Objective-C class or vice versa. This lesson goes deeper into the parts of that mixture that are genuinely hazardous if you don't understand them precisely: two different, non-interoperating memory-management philosophies sharing one file (C++ RAII and ARC), two different, non-interoperating exception mechanisms that must never be allowed to cross each other's stack frames, and the discipline of keeping C++ complexity fully contained inside your .mm implementation so that your public .h header remains importable by plain Objective-C and Swift code that knows nothing about C++ at all.

Two Memory Models in One File: RAII Versus ARC

C++'s memory-management philosophy is RAII — Resource Acquisition Is Initialization — where a resource (memory, a file handle, a lock) is acquired in a constructor and deterministically released in the matching destructor, which the compiler guarantees runs when the owning object's lifetime ends, whether that's at the end of a scope, when a containing object is destroyed, or when an exception unwinds the stack past it. This is deterministic in a very specific sense: you can point to the exact line of code — the closing brace of the scope, or the delete of the owning pointer — where a C++ object's destructor runs, and therefore precisely when its resources are released.

ARC's philosophy is superficially similar (automatic, no manual retain/release calls needed for ordinary code) but mechanically different: an Objective-C object is deallocated when its retain count reaches zero, which the compiler arranges by inserting retain/release calls at compile time based on ownership qualifiers (strong, weak, etc.) and scope, but which is not always as visually obvious from a single line of code as a C++ destructor call, especially across property assignments, block captures, and collection membership. Both systems are 'automatic' from the programmer's point of view, in that you don't write explicit free() calls, but they are implemented by entirely different mechanisms and, crucially, neither one understands the other's bookkeeping at all.

This becomes a concrete problem the moment a C++ class needs to hold an Objective-C object as a member. A raw C++ member declared simply as MyClass *objcMember; inside a C++ class does not participate in ARC at all — ARC's ownership annotations (strong, weak, __unsafe_unretained) apply to Objective-C-visible storage (instance variables of Objective-C classes, local variables, properties), and a plain C++ class's member variable, by default, is exactly the kind of storage ARC does not automatically manage. Left as a bare pointer, that member behaves like __unsafe_unretained: it does not retain the object, and if nothing else holds a strong reference, the object can be deallocated while the C++ member still points at it — a dangling pointer, structurally identical to the use-after-free hazard from the pointer-casting lessons, just arising from a different cause (C++ member declaration syntax bypassing ARC, rather than a __bridge cast).

The fix is to declare the member with an explicit ownership qualifier even inside the C++ class: MyClass * __strong objcMember; (in Objective-C++, __strong can be applied to a C++ member exactly this way). With __strong present, ARC does generate the necessary retain in the constructor's initialization and release in the destructor, so the C++ object's own RAII-driven destructor now correctly participates in ARC's reference counting for that one member — the two systems end up cooperating, but only because you told the compiler explicitly to bridge them; it does not happen automatically from bare C++ member syntax.

• C++ RAII releases resources deterministically via destructors, tied to an exact point in the code (scope exit, containing-object destruction, or explicit delete). • ARC deallocates Objective-C objects when their retain count reaches zero, driven by compiler-inserted retain/release calls based on ownership qualifiers — automatic, but a different mechanism from destructors. • A bare C++ member holding an Objective-C object pointer (MyClass *member;, no qualifier) does not participate in ARC by default and behaves like __unsafe_unretained, risking a dangling pointer. • Declaring the member MyClass * __strong member; makes ARC generate the correct retain/release for it, letting RAII's constructor/destructor timing correctly drive ARC's reference counting for that member. • Neither memory model understands the other's bookkeeping automatically — cooperation between them requires explicit ownership annotations, not silent interoperability.

C++ Exceptions Versus Objective-C Exceptions: Separate Mechanisms

Objective-C has its own exception-handling syntax — @try, @catch, @finally, @throw — which predates, and is implemented separately from, C++'s try/catch/throw. Historically, on Apple's platforms, these were genuinely two distinct exception-handling implementations under the hood, using different unwinding mechanisms, and mixing them carelessly across a boundary — throwing a C++ exception and expecting an Objective-C @catch to catch it, or vice versa — was undefined behavior or an outright crash, not merely 'unsupported but harmless.'

Modern Apple compilers, under Objective-C++ with the right compiler flags (-fobjc-exceptions alongside standard C++ exception support), have unified the underlying unwinding mechanism to a significant degree, which improves the situation, but the practical guidance remains conservative and unchanged in spirit: do not rely on a C++ throw being safely catchable by an Objective-C @catch, or on an Objective-C @throw being safely catchable by a C++ catch, without deliberately verifying your exact toolchain, flags, and target platform support that interop, and even then, treat it as a narrow, carefully-tested capability rather than a default assumption. The safe default, applicable everywhere without needing to verify anything, is: catch C++ exceptions with C++ catch blocks, catch Objective-C exceptions with @catch blocks, and never let one kind of exception propagate up through stack frames that only know how to handle the other kind.

This matters most exactly at the .mm boundary this lesson is about: if your Objective-C++ wrapper class calls into a C++ library function that might throw, and that call happens inside an Objective-C method (an -(void)someMethod implementation), you should wrap the C++ call in an actual C++ try/catch right there, inside the method body, and translate any caught C++ exception into something Objective-C-idiomatic — an NSError ** out-parameter, an @throw of an NSException if your codebase uses those, or simply a BOOL success return — before the C++ exception has any chance of propagating further up through Objective-C-only calling frames above your method, which is exactly the scenario that's unsafe to leave unguarded.

• Objective-C's @try/@catch/@throw and C++'s try/catch/throw are historically separate exception-handling mechanisms with separate unwinding implementations. • Letting a C++ exception propagate up through Objective-C stack frames (or vice versa) without deliberate, verified interop support is unsafe and can crash or produce undefined behavior. • The safe default: catch each language's exceptions with that same language's catch construct, and never assume the other side can catch what you threw. • At the .mm boundary, wrap any C++ call that might throw in a real C++ try/catch inside the Objective-C method body, and translate the caught exception into an idiomatic Objective-C signal (NSError, BOOL, or an NSException @throw) before returning to purely-Objective-C calling code. • This translation-at-the-boundary discipline is the single most important practical rule for exception safety in any Objective-C++ wrapper class.

Templates and the STL, Used Internally Only

C++ templates and the Standard Template Library (std::vector, std::map, std::string, and so on) are frequently exactly the right tool for implementation-level work inside a .mm file — a std::vector<float> might be a far more natural and efficient way to accumulate a large buffer of raw numeric samples internally than an NSMutableArray of boxed NSNumber objects, and template-based generic algorithms from <algorithm> can express certain computations more concisely than hand-rolled Objective-C loops. Objective-C++ gives you complete freedom to use all of this inside your implementation.

The constraint this lesson emphasizes is where that freedom should stop: templates, STL types, and other C++-only constructs should stay confined to the .mm implementation file (and to any private, C++-aware headers that only your own .mm files import), and should not appear in the public .h header that Objective-C or Swift consumers of your class will #import. A header containing std::vector<float> _samples; as a visible instance variable, or a method declared to take a std::string parameter, cannot be imported by a plain .m file at all — the C-family preprocessor will choke on C++-only syntax the moment a non-Objective-C++ file tries to #import it — and it certainly cannot be imported by Swift, whose Objective-C bridging understands plain Objective-C and C types, but not arbitrary C++ template instantiations.

This constraint is not a limitation to work around reluctantly; it is exactly the right design boundary, because it keeps your class's public contract expressed entirely in terms both a Swift engineer and a plain-Objective-C engineer can consume without ever needing to know your implementation happens to use C++ internally at all. The wrapper-class pattern in the next section is precisely the mechanism that makes this separation achievable in practice.

• C++ templates and STL containers (std::vector, std::map, std::string, etc.) are often the right internal tool inside a .mm implementation for performance or expressiveness. • C++-only types must not appear in the public .h header — a plain .m file cannot import a header containing C++ syntax at all, and Swift's Objective-C bridging cannot see arbitrary C++ template types either. • Confining C++ usage to the .mm file (and private, C++-aware headers only your own .mm files import) is a deliberate design boundary, not a workaround. • This boundary keeps the class's public contract expressible purely in Objective-C/C terms, consumable by Swift and plain Objective-C callers with zero awareness that C++ is involved underneath.

The Wrapper-Class (Pimpl-Style) Pattern

The concrete technique for keeping C++ out of a public header while still using it freely inside the implementation is exactly the classic C++ 'pimpl' (pointer to implementation) idiom, adapted to Objective-C++: the public .h header declares an ordinary, pure-Objective-C class with no C++ types visible in any method signature or ivar declaration; the private C++ state lives either in a private extension inside the .mm file (an @interface MyWrapper () { std::vector<float> _samples; } @end block, which is only ever compiled as part of the .mm file and therefore never seen by a .m or Swift importer), or, for cases needing even stricter separation, behind a forward-declared opaque C++ struct pointer, à la a traditional pimpl.

The forward-declared-pointer variant looks like this in the header: @interface MyWrapper : NSObject\n- (void)addSample:(float)value;\n@end — nothing about std::vector anywhere. Inside the .mm file, a small internal C++ struct holds the real state (struct MyWrapperImpl { std::vector<float> samples; };), and the Objective-C class holds a plain MyWrapperImpl *_impl; ivar, allocated with new in -init and freed with delete in -dealloc (since it's a raw C++ struct, not an ARC-managed Objective-C object, its lifetime has to be managed manually — RAII's automatic destructor call doesn't apply to a heap-allocated struct reached through a raw pointer; only delete runs its destructor). Every public method (-addSample:) is implemented in the .mm file by translating its Objective-C-typed parameters into whatever the C++ implementation needs and calling straight into _impl.

Either variant achieves the same goal: any file that only ever sees the .h header — a plain .m file elsewhere in the project, or Swift code via the bridging header — can use MyWrapper exactly like any other Objective-C class, calling [wrapper addSample:3.14], with absolutely no awareness that the implementation behind that call is using std::vector<float> and C++ template machinery under the hood. The private-extension variant is usually preferable when it's available (simpler, no manual new/delete bookkeeping, ARC-compatible members are straightforward to add alongside the C++ ones) — the forward-declared-pointer pimpl variant is reserved for cases needing to fully hide even the *size* of the C++ state from anything outside the .mm file, such as a header that must remain stable across binary-compatible library versions even as the C++ implementation's internal layout changes.

• The wrapper-class pattern: a pure-Objective-C public .h header with no C++ types visible anywhere, backing its real state with C++ hidden entirely inside the .mm file. • Simplest variant: a private class extension inside the .mm file (@interface MyWrapper () { std::vector<float> _samples; } @end) — only ever compiled as part of the .mm translation unit. • Stricter variant: a forward-declared opaque C++ struct pointer (pimpl) as the only ivar in the header, with the real C++ struct and its layout defined privately inside the .mm file. • A raw C++ struct reached through a pointer and allocated with new must be explicitly deleted (commonly in -dealloc) — RAII's automatic destructor-on-scope-exit doesn't apply to heap allocations reached through a raw pointer. • Both variants let plain Objective-C and Swift consumers use the class normally through the header, with zero visibility into the C++ implementation underneath.

.mm Compilation and Header Compatibility Rules

A file's extension determines how the compiler treats it: .m files are compiled as Objective-C, .mm files are compiled as Objective-C++ (both Objective-C and C++ syntax allowed in the same translation unit), and plain .cpp/.cc files are compiled as pure C++ with no Objective-C syntax recognized at all. This is a per-file setting, which is exactly what makes the wrapper-class pattern practical at the project level: you can have MyWrapper.h (pure Objective-C declarations, importable anywhere), MyWrapper.mm (the Objective-C++ implementation, freely using C++ internally), and the rest of your project's ordinary .m files, all coexisting in one Xcode target, with each file compiled according to its own extension.

The header-compatibility rule that falls out of this is simple to state and important to hold to consistently: never let a #import chain force a .m file (or Swift, via a bridging header) to transitively pull in a header containing C++-only syntax. This means being disciplined not just about your own class's public header, but about anything it #imports in turn — if MyWrapper.h itself #imports some other header that declares a C++ type in a way visible to importers, you've broken the boundary just as surely as if MyWrapper.h had declared the C++ type directly. Keeping C++-aware headers named distinctly (a common convention: a private MyWrapperInternal.h or similar, imported only from .mm files) makes this discipline easy to audit for at a glance.

One practical consequence worth calling out explicitly: because .mm compilation is strictly more permissive than .m (Objective-C++ accepts every valid Objective-C construct plus C++ on top), it is sometimes tempting to simply rename every .m file in a project to .mm 'just in case' rather than reasoning carefully about which files actually need C++. This is broadly discouraged — beyond the compile-time cost of enabling C++ parsing project-wide, it erodes precisely the boundary this lesson is about: once every file is .mm, there is no longer a structural, file-extension-level guarantee that C++ types can't leak into a header some plain-Objective-C or Swift-facing file imports, because nothing is stopping any of those now-.mm files from doing so. Reserving .mm deliberately for the specific files that need it keeps the boundary real rather than nominal.

• .m compiles as Objective-C, .mm as Objective-C++ (both Objective-C and C++ syntax), and .cpp/.cc as pure C++ with no Objective-C syntax recognized — this is a per-file setting. • Never let a #import chain force a .m file or a Swift bridging header to transitively pull in any header containing C++-only syntax, not just your own class's immediate header. • A private, C++-aware header (imported only by .mm files, often named distinctly, e.g. with an 'Internal' suffix) keeps the public/private header boundary easy to audit. • Avoid renaming every .m file to .mm indiscriminately — doing so removes the file-extension-level structural guarantee that C++ can't leak into headers plain Objective-C/Swift files import. • Reserve .mm deliberately for exactly the files that need C++ interop, keeping the public-header boundary a real, enforced constraint rather than a matter of discipline alone.

Putting It Together: A Complete Wrapper Around a C++ Library

Consider wrapping a hypothetical C++ signal-processing library that exposes a SignalProcessor C++ class with methods that can throw std::runtime_error on invalid input, and that internally uses std::vector<float> extensively. The goal is an Objective-C class, OCSignalProcessor, usable from Swift and plain Objective-C, whose public header exposes only Objective-C/Foundation types — NSArray<NSNumber *> * or a plain C array, BOOL, NSError ** — with std::vector and the C++ library's exception type both fully hidden.

The .mm implementation does three things this lesson has now covered individually: it holds the C++ SignalProcessor as a __strong-irrelevant-but-explicitly-managed C++ member (here, actually a plain C++ object by value or a std::unique_ptr<SignalProcessor>, since it's a pure C++ type with no ARC involvement needed for it specifically — the __strong concern from earlier applies specifically when the C++ class holds an *Objective-C* object, not the reverse); it wraps every call into the C++ library in a try/catch (const std::runtime_error &e) block, translating any caught exception into an NSError populated via the public method's NSError ** out-parameter, exactly per the exception-boundary rule; and it converts between std::vector<float> and NSArray<NSNumber *> * (or a raw C array) at every public method boundary, so no C++ container type ever crosses into Objective-C-facing code.

• A complete wrapper combines all of this lesson's pieces: hidden C++ state (pimpl or private extension), try/catch translating C++ exceptions to NSError at every public method boundary, and container conversion (std::vector to NSArray) at the same boundary. • When the C++ side holds an Objective-C object as a member, the __strong-qualification concern applies; when the Objective-C side holds a C++ object (the more common direction for a wrapper), ordinary C++ RAII (by-value member or std::unique_ptr) handles its lifetime with no ARC involvement needed for that member specifically. • The finished public header for such a wrapper should be indistinguishable, from a Swift or plain-Objective-C caller's point of view, from a header for a class implemented in pure Objective-C.

The Wrapper-Class Pattern: Header Versus Implementation

MyWrapper.h contains zero C++ syntax, so any .m file or Swift bridging header can #import it safely. The private extension inside MyWrapper.mm is the only place std::vector<float> is declared -- it never appears in the header. The C++ try/catch inside -averageOrError: translates a std::runtime_error into an NSError written through the pointer-to-pointer error parameter, exactly at the Objective-C-facing method boundary, before any C++ exception could otherwise propagate further.

// MyWrapper.h -- pure Objective-C, no C++ visible, importable anywhere
@interface MyWrapper : NSObject
- (void)addSample:(float)value;
- (float)averageOrError:(NSError **)error;
@end

// MyWrapper.mm -- Objective-C++, C++ fully hidden here
#import "MyWrapper.h"
#include <vector>
#include <stdexcept>

@interface MyWrapper () {
    std::vector<float> _samples;
}
@end

@implementation MyWrapper

- (void)addSample:(float)value {
    _samples.push_back(value);
}

- (float)averageOrError:(NSError **)error {
    try {
        if (_samples.empty()) {
            throw std::runtime_error("no samples");
        }
        float sum = 0;
        for (float v : _samples) { sum += v; }
        return sum / _samples.size();
    } catch (const std::runtime_error &e) {
        if (error) {
            *error = [NSError errorWithDomain:@"MyWrapperErrorDomain"
                                          code:1
                                      userInfo:@{NSLocalizedDescriptionKey: @(e.what())}];
        }
        return 0.0f;
    }
}

@end

Code Examples

Without __strong, delegate would behave like an unretained pointer and could dangle if nothing else keeps the MyClass instance alive. With __strong, ARC generates the retain when delegate is assigned and the release when the EventForwarder is destroyed, correctly tying the two memory models together for this one member.

MyClass * __strong delegate; // ARC-managed even though this is a C++ class member

Because -processData never wraps the C++ call in try/catch, a thrown std::runtime_error propagates up through -processData's Objective-C stack frame and potentially further, into a plain .m file's calling code that has no C++ catch handler anywhere in its call chain -- undefined behavior or a crash.

Common mistake: Calling a C++ function that may throw directly from an Objective-C method with no surrounding try/catch, letting the exception propagate into Objective-C-only frames above it.

The try/catch is placed exactly at the Objective-C method boundary, so the C++ exception never escapes past this point -- it's translated into the idiomatic Objective-C BOOL-plus-NSError-out-parameter convention before returning to any calling code, Objective-C++ or otherwise.

} catch (const std::runtime_error &e) { MyWrapperImpl's full definition (and therefore the fact that std::vector is involved at all) lives entirely inside MyWrapper.mm. The header only ever sees an opaque pointer type name via the private extension inside the.mm file -- nothing about it is visible to a.m file or Swift importer. delete _impl in -dealloc is required because a heap-allocated C++ struct reached through a raw pointer does not get its destructor run automatically the way a stack-allocated or by-value RAII member would.

MyWrapperImpl *_impl;

Declaring std::vector<float> as a visible ivar directly in the .h header means any file that #imports MyWrapper.h must also be compiled as Objective-C++ (or fail to compile). A plain .m file elsewhere in the project, or a Swift bridging header, cannot import this header at all.

Common mistake: Declaring a C++-only type (like std::vector) directly in the public .h header instead of hiding it behind a private extension or a pimpl pointer inside the .mm file.

class EventForwarder {
public:
    MyClass * __strong delegate; // ARC-managed even though this is a C++ class member

    void fireEvent(int code) {
        if (delegate) {
            [delegate handleEventCode:code];
        }
    }
};
// DANGEROUS: no try/catch anywhere in this call chain
- (void)processData {
    self.processor->run(); // may throw std::runtime_error internally
}

// Called from a plain .m file with no knowledge of C++ at all:
// [wrapper processData]; -- if run() throws here, it propagates
// up through Objective-C frames that have no way to catch it.
- (BOOL)processDataWithError:(NSError **)error {
    try {
        self.processor->run();
        return YES;
    } catch (const std::runtime_error &e) {
        if (error) {
            *error = [NSError errorWithDomain:@"ProcessorErrorDomain"
                                          code:2
                                      userInfo:@{NSLocalizedDescriptionKey: @(e.what())}];
        }
        return NO;
    }
}
// MyWrapper.h
@interface MyWrapper : NSObject
- (void)addSample:(float)value;
@end

// MyWrapper.mm
#include <vector>
struct MyWrapperImpl {
    std::vector<float> samples;
};

@interface MyWrapper () {
    MyWrapperImpl *_impl;
}
@end

@implementation MyWrapper
- (instancetype)init {
    if (self = [super init]) {
        _impl = new MyWrapperImpl();
    }
    return self;
}
- (void)addSample:(float)value {
    _impl->samples.push_back(value);
}
- (void)dealloc {
    delete _impl;
}
@end
// MyWrapper.h -- WRONG: a plain .m file or Swift cannot import this
#include <vector>

@interface MyWrapper : NSObject {
    std::vector<float> _samples; // C++ type visible in the public header
}
- (void)addSample:(float)value;
@end

Important Rules

Qualify any Objective-C object held as a raw C++ class member with __strong explicitly — bare C++ member syntax does not participate in ARC automatically.

Never let a C++ exception propagate up through Objective-C-only stack frames, or an Objective-C exception propagate into C++-only frames, without deliberately verified toolchain support — wrap and translate at the boundary instead.

Confine templates, STL containers, and any other C++-only types to the .mm implementation (and private, C++-aware headers imported only by .mm files) — never let them appear in a public .h header.

Use the wrapper-class pattern — a private class extension or a forward-declared pimpl pointer — to hide C++ state behind a pure-Objective-C public interface.

Reserve the .mm extension deliberately for files that actually need C++ interop, rather than renaming every .m file to .mm, to keep the public-header boundary a real, enforced constraint.

Where This Shows Up in Practice

Games and audio/video engines built on cross-platform C++ cores (many game engines, audio DSP libraries, and video codecs) are almost always integrated into iOS/macOS apps via exactly this wrapper-class pattern: a thin Objective-C or Swift-facing shell around a substantial C++ core, with careful exception translation at every call into the C++ layer and a public header that hides every trace of the C++ implementation underneath. Getting the exception-boundary and header-compatibility rules right the first time avoids two of the most common categories of hard-to-diagnose crash reports in these kinds of hybrid codebases.

• (void)processData { self.processor->run(); // may throw std::runtime_error, uncaught } - (BOOL)processDataWithError:(NSError **)error { try { self.processor->run(); return YES; } catch (const std::runtime_error &e) { if (error) { *error = [NSError errorWithDomain:@"ProcessorErrorDomain" code:2 userInfo:@{NSLocalizedDescriptionKey: @(e.what())}]; } return NO; } }

Letting a C++ exception propagate out of an Objective-C method with no try/catch means it can reach Objective-C-only calling frames that have no way to catch it, producing undefined behavior or a crash. Wrapping the throwing call in try/catch and translating the exception into an NSError at the method boundary keeps the C++ exception fully contained within the .mm implementation.

Why doesn't a plain C++ class member like MyClass *objcMember; participate in ARC automatically? A. C++ classes can never hold Objective-C objects at all B. ARC's ownership qualifiers apply to Objective-C-visible storage by default, and a bare C++ member declaration bypasses that unless explicitly qualified with __strong C. Because .mm files disable ARC entirely D. Because Objective-C objects cannot be represented as pointers in C++

B. ARC's ownership qualifiers apply to Objective-C-visible storage by default, and a bare C++ member declaration bypasses that unless explicitly qualified with __strong — Without an explicit __strong qualifier, a raw C++ member behaves like an unretained pointer -- ARC does not automatically manage it the way it manages an Objective-C class's own ivars and properties.

What is the safe default for handling exceptions at an Objective-C++ boundary? A. Let C++ exceptions propagate freely into Objective-C @catch blocks, since they're identical mechanisms B. Catch each language's exceptions with that language's own catch construct, translating at the boundary before returning to the other language's calling code C. Disable exceptions entirely in all .mm files D. Only use C++ exceptions, never Objective-C exceptions, in any mixed codebase

B. Catch each language's exceptions with that language's own catch construct, translating at the boundary before returning to the other language's calling code — Objective-C and C++ exceptions are historically separate mechanisms; the safe, portable default is to catch each with its own language's construct and translate into the other idiom at the boundary rather than relying on cross-language catching.

Why should std::vector or other C++ template types not appear in a public Objective-C .h header? A. std::vector is deprecated in modern C++ B. A plain .m file cannot import a header containing C++-only syntax, and Swift's bridging cannot see arbitrary C++ template types either C. Public headers cannot contain any pointer types D. It would make the class run slower

B. A plain .m file cannot import a header containing C++-only syntax, and Swift's bridging cannot see arbitrary C++ template types either — A header with C++-only syntax fails to import into a plain .m file and is invisible to Swift's Objective-C bridging, breaking the header for exactly the consumers it needs to serve.

In the pimpl-style wrapper pattern, why must a heap-allocated C++ struct reached through a raw pointer be explicitly deleted (e.g. in -dealloc)? A. ARC automatically deletes it when the wrapper is deallocated B. RAII's automatic destructor-on-scope-exit does not apply to a heap allocation reached through a raw pointer -- only an explicit delete runs its destructor C. C++ structs never have destructors D. The struct is automatically freed by the C++ garbage collector

B. RAII's automatic destructor-on-scope-exit does not apply to a heap allocation reached through a raw pointer -- only an explicit delete runs its destructor — RAII ties destructor calls to a value's own scope or an owning object's lifetime; a bare pointer to a heap allocation has neither, so only an explicit delete call triggers the destructor and frees the memory.

Why is indiscriminately renaming every .m file in a project to .mm generally discouraged? A. It is not technically possible to compile a renamed file B. It removes the file-extension-level structural guarantee that C++ types can't leak into headers plain Objective-C or Swift files import C. The .mm extension is reserved by Apple and cannot be used freely D. It automatically disables ARC across the whole project

B. It removes the file-extension-level structural guarantee that C++ types can't leak into headers plain Objective-C or Swift files import — Once every file is .mm, nothing at the file-extension level prevents any of them from introducing C++ types into a shared header, eroding the deliberate public/private boundary the wrapper-class pattern depends on.

Design a public header MyWrapper.h for a class wrapping a hypothetical C++ JSON-parsing library whose core throws std::invalid_argument on malformed input and uses std::string and std::map internally. The header should expose a method - (nullable NSDictionary *)parseJSON:(NSString *)json error:(NSError **)error; with no C++ types visible anywhere. Sketch the .mm implementation's structure (which pattern -- private extension or pimpl -- you'd choose and why) and show where the try/catch belongs.

A private class-extension ivar holding the C++ parser type is sufficient unless the header must remain binary-stable across versions where the C++ parser's internal layout might change, in which case the pimpl pointer variant is preferable. Either way, the try/catch belongs inside -parseJSON:error:, catching std::invalid_argument and translating it into an NSError before returning, converting the parsed std::map<std::string, ...> into an NSDictionary at that same boundary.

Decide whether hiding the C++ parser instance behind a private class-extension ivar is sufficient, or whether you specifically need the forward-declared pimpl pointer -- what would make the stricter pimpl variant necessary here versus the simpler private-extension approach?