c++swiftobjective-cmemory-leaksautomatic-ref-counting

How can I store automatic reference counted Swift/Objective-C objects in a C++ map without causing memory leaks when they are deleted from the map?


I have a C++ map which manages loaded regions. Each region has an associated MTLBuffer for the geometry generated after the region data is converted into a mesh. The idea is to store a reference to each buffer inside the C++ map so I have all of the regions and their associated data in a single data structure. I could have this Objective-C++ pseudocode:

struct Region {
    void *buffer;
    // Other fields
};
std::unordered_map<uint64_t, Region> loaded_regions;
void render_region(Region *region) {
    float vertices[MAX_SIZE];
    unsigned length;

    // ...

    // There are multiple ways I could do this. One could be:
    map[position].buffer = [device newBufferWithBytes: vertices length: length options: MTLStorageModeShared];

    // I could also pass `vertices` to some external function defined in Swift that creates a new `MTLBuffer` and returns it.
    // This is then converted to a bare pointer and stored in the map.
}

However, when regions are deleted from the map, for example in a function like this:

void delete_region(uint16_t position) {
    Region *region = loaded_regions[position];

    // ...

    map.erase(position);
    delete position;
}

How can I ensure any stored Swift object references or Objective-C object references are properly released and no memory leak is caused, assuming I have ARC enabled?

Is this possible, or will I just have to make a Swift Dictionary or an Objective-C NSDictionary to store these buffers independently from the C++ map that stores the actual regions?

When ARC is enabled, Xcode will not allow release messages to be used directly, so I do not know how else to manually free the memory.


Solution

  • It's quite easy with smart pointers. See the sketch below.

    struct Deleter {
        void operator()(void *buffer) const nothrow { CFRelease(buffer); }
    };
    
    struct Region {
        std::unique_ptr<void, Deleter> buffer;
        // Other fields
    };
    
    map[positon].buffer.reset([device newBufferWithBytes:vertices
                                                  length:length
                                                 options:MTLStorageModeShared]);