Reputation: 1301
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?
Upvotes: 2
Views: 472
Reputation: 238351
Since the callback depends on the manager, it should have ownership.
It cannot have proper ownership because the manager already owns the (object that contains) the callback, but it can have weak shared ownership. A working adjustment of your example:
class ImageManager : std::enable_shared_from_this<ImageManager> {
// ...
auto callbackFunc = [this_w = weak_from_this(), currentUUID]() {
if (auto this_ = this_w.lock())
this_->removeImage(currentUUID);
};
// ...
auto manager = std::make_shared<ImageManager>();
manager->populateImage(img1);
manager->populateImage(img2);
Upvotes: 1
Reputation: 31579
One solution for this would be to remove the callbacks when the ImageManager
is destroyed.
In the ImageManager
destructor you can loop through m_imageMap
and set all deleters to null.
Upvotes: 0
Reputation: 6584
Overall the design smells. If the ImageManager
is a local object, and MyImage
are outside of the scope, why do you need to delete the items in the map?
Anyway, I promised you to show the shared/weak idiom. Wrap the map into a shared_ptr
. That would mean that the map will be destroyed together with the ImageManager (if you wouldn't copy this shared pointer):
std::shared_ptr<std::unordered_map<unsigned int, cv::Mat>> m_imageMap;
Store a weak pointer in the lambda:
std::weak_ptr<std::unordered_map<unsigned int, cv::Mat>> weakPtr = m_imageMap;
auto callbackFunc = [weakPtr, uuid]() {
auto imageMap = weakPtr.lock();
if (imageMap)
auto iter = imageMap.find(uuid);
if (iter == imageMap.end()) {
throw std::runtime_error("Unable to find image for UUID: " + std::to_string(uuid));
}
imageMap.erase(iter);
}
};
image->m_deleter = callbackFunc;
Weak pointer will know whether the shared pointer counterpart is destroyed or not.
Keep in mind that you shouldn't use std::make_shared
in this case: otherwise the memory associated with the map will be freed only when the last image is destroyed.
Upvotes: 1