Let's say we have the following scenario.
We have a ImageManager class which is used to internally store and manage image data.
The ImageManager class has a public member populateImage, which will read an image into memory, then return a populated MyImage which is a std::shared_ptr around an Image object. This object will contain a uuid which maps to the image data which is managed by ImageManager.
Finally, we define a callback function which gets called in the Image destructor and properly cleans up the image data stored by the ImageManager.
Here is the example:
#include <functional>
#include <vector>
#include <unordered_map>
#include <memory>
class Image {
public:
Image() = default;
~Image(){
if (m_deleter) {
m_deleter();
}
}
private:
friend class ImageManager;
std::function<void()> m_deleter = nullptr;
unsigned int m_uuid;
};
using MyImage = std::shared_ptr<Image>;
class ImageManager {
public:
void removeImage(unsigned int uuid) {
auto iter = m_imageMap.find(uuid);
if (iter == m_imageMap.end()) {
throw std::runtime_error("Unable to find image for UUID: " + std::to_string(uuid));
}
m_imageMap.erase(iter);
}
void populateImage(MyImage& image) {
image = std::make_shared<Image>();
static unsigned int uuid = 0;
++uuid;
image->m_uuid = uuid;
auto img = std::vector<uint8_t>(1000, 0);
m_imageMap[uuid] = img;
unsigned int currentUUID = uuid;
auto callbackFunc = [this, currentUUID]() {
this->removeImage(currentUUID);
};
image->m_deleter = callbackFunc;
}
private:
std::unordered_map<unsigned int, std::vector<uint8_t>> m_imageMap;
};
The issues arise when our instance of the ImageManager falls out of scope before our instances of Image, for example in the following driver code:
int main() {
MyImage img1, img2;
{
ImageManager manager;
manager.populateImage(img1);
manager.populateImage(img2);
}
}
Running this program prints:
terminate called after throwing an instance of 'std::runtime_error'
what(): Unable to find image for UUID: 2
But ultimately I understand that this is undefined behavior as the this pointer in m_deleter no longer points to a valid object.
What is the proper design pattern in order to avoid this problem?