c++ - 析构函数中使用可能指向或不指向有效对象的指针的回调的正确设计模式是什么?

标签 c++

假设我们有以下场景。

我们有一个ImageManager类,用于内部存储和管理图像数据。 ImageManager 类有一个公共(public)成员 populateImage,它将把图像读入内存,然后返回一个填充的 MyImage,它是一个 std::shared_ptr 围绕 Image 对象。该对象将包含一个uuid,它映射到由ImageManager管理的图像数据。

最后,我们定义一个回调函数,该函数在 Image 析构函数中调用,并正确清理 ImageManager 存储的图像数据。

示例如下:

#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;
};

当我们的 ImageManager 实例在 Image 实例之前超出范围时,就会出现问题,例如在以下驱动程序代码中:

int main() {
    MyImage img1, img2;
    {
        ImageManager manager;
        manager.populateImage(img1);
        manager.populateImage(img2);
    }
}

运行该程序打印:

terminate called after throwing an instance of 'std::runtime_error'
  what():  Unable to find image for UUID: 2

但最终我明白这是未定义的行为,因为 m_deleter 中的 this 指针不再指向有效的对象。

为了避免这个问题,正确的设计模式是什么?

最佳答案

总体来说,设计有味道。如果ImageManager是本地对象,而MyImage在范围之外,为什么需要删除 map 中的项目?

无论如何,我答应你展示共享/弱的习语。将 map 包装到 shared_ptr 中。这意味着 map 将与 ImageManager 一起被销毁(如果您不复制此共享指针):

std::shared_ptr<std::unordered_map<unsigned int, cv::Mat>> m_imageMap;

在 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;

弱指针会知道对应的共享指针是否被销毁。

请记住,在这种情况下不应使用 std::make_shared:否则只有当最后一个图像被销毁时,与 map 关联的内存才会被释放。

关于c++ - 析构函数中使用可能指向或不指向有效对象的指针的回调的正确设计模式是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71042377/

相关文章:

c++ - _M_ 尝试实现多线程队列时出现 construct null not valid 错误

c++ - C++ 中的多态映射键

c++ - 如何在线程循环内等待

c++ - 将基于 Wt 的小部件作为 iframe 嵌入网页中?

c++ - std::set 引用外部值的比较器

c++编译错误涉及方法重载的2个模板函数

c++ - 什么时候在构造函数中调用虚函数是安全的

c++ - QtCreator 中 printf() 与 qDebug() 的执行顺序

c++ - 意外的位移结果

c++ - 如何在不拖延主循环的情况下实现对话系统?