RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 827413
Accepted
Alex Nem
Alex Nem
Asked:2020-05-14 02:54:50 +0000 UTC2020-05-14 02:54:50 +0000 UTC 2020-05-14 02:54:50 +0000 UTC

库中用于回调的自己的模板类(析构函数的问题)

  • 772

再一次问好。我继续一点一点地为 Windows 编写我的 GUI 库,同时学习 C++。我在各种资料中读到,从您的库中导出标准 STL 类不是很好,因为将来在具有不同版本 STL 的环境中使用此类库时可能会导致各种困难(如果事实上这不是案例-纠正我)。因此,为了不导出 std:: 函数,我决定编写类似我自己的回调的东西。经过几个小时的谷歌搜索,我们成功地诞生了以下课程:

/**
 * \brief Частичное шаблонное объявление
 * \detail Обеспечивает возможность указания параметров шаблона в виде "ReturnType(Arguments...)" 
 * \tparam T Возвращаемый тип функции обратного вызова
 */
template<typename T>
class WQUERY_API Callback;

/**
 * \brief Класс-обретка над функцией (либо любым callable объектом) обратного вызова
 * \tparam ReturnType Возвращаемый тип функции обратного вызова
 * \tparam Arguments Аргументы функции обратного вызова
 */
template <typename ReturnType, typename ... Arguments>
class Callback<ReturnType(Arguments...)>
{
private:
    ReturnType(*wrapperFunction_)(Arguments... args, void*);  // Указатель на функцию-обретку (вызывает настоящий callable объект)
    void *pCallable_;                                         // Указатель на callabl'e объект объект

public:
    /**
     * \brief Конкструктор по умолчанию
     */
    Callback() :pCallable_(nullptr) {}

    /**
     * \brief Установка функции обратного вызова
     * \tparam T Тип callabl'e объекта
     * \param pCallable Указатель на функцию (callable объект)
     */
    template<class T>
    void Set(T pCallable)
    {
        // Выделить память под функцию (callable объект) и скопировать содежимое в эту память
        // если это лямбда, потребуется столько памяти, сколько занимают ее "захваченные" переменные
        this->pCallable_ = malloc(sizeof(T));
        memcpy(this->pCallable_, &pCallable, sizeof(T));

        // Создать функцию "обертку", которая инициирует вызов callable объекта с аргументами args
        this->wrapperFunction_ = [](Arguments... args, void *callable) ->ReturnType {
            return (*reinterpret_cast<T*>(callable))(args...);
        };
    }

    /**
     * \brief Вызов функции обертки
     * \param args Аргументы
     * \return Значение, возвращаемое оберткой (идентично тому что вернет pCallable_)
     */
    ReturnType Invoke(Arguments... args)
    {
        if (this->IsSet())
        {
            return this->wrapperFunction_(args..., this->pCallable_);
        }

        return ReturnType();
    }

    /**
     * \brief Сброс функции (очистить память и сделать не вызываемой)
     */
    void Unset()
    {
        if (this->pCallable_) {
            free(this->pCallable_);
            this->pCallable_ = nullptr;
        }
    }

    /**
     * \brief Установлено ли
     * \return Состояние
     */
    bool IsSet() const
    {
        return this->pCallable_ && this->wrapperFunction_;
    }

    /**
     * \brief Деструктор (срабсывает функцию)
     */
    ~Callback()
    {
        this->Unset();
    }
}

计划以某种方式使用这个东西:

1)在Window类(或控件类)中,用Callback类的一组对象声明一个结构

    struct
    {
        Callback<bool()> onClose;
        Callback<void()> onPaint;
        Callback<void(unsigned int type, Vector2D<int> newSizes)> onResized;
        Callback<void(unsigned int code)> onKeyDown;
        Callback<void(unsigned int code)> onKeyUp;
        Callback<void(char symbol)> onTyping;
        Callback<void(Vector2D<int> cursor)> onMouseMove;
        Callback<void(Vector2D<int> cursor, MouseKeys type)> onMouseKeyDown;
        Callback<void(Vector2D<int> cursor, MouseKeys type)> onMouseKeyUp;
    } events;

2)使用库时,回调将像这样指定

wquery::Window window1;
window1.events.onClose.Set([]() {std::cout << "Test" << std::endl; return true; });

3)在窗口过程中,在正确的地方,函数将被调用(如果它们已安装)

    case WM_CLOSE:
        if (window)
        {
            if(window->events.onClose.IsSet())
            {
                if (!window->events.onClose.Invoke()) return 0;
            }

            if(window->closesProgram_) PostQuitMessage(0);
        }
        ShowWindow(hWnd, SW_HIDE);
        break;

起初,一切都很顺利,你可以同时给 Set 函数的参数一个 lambda 和一个常规函数指针,它们被完美地调用了。但是对Window析构函数的调用,不幸的是,在执行过程中开始产生如下错误: 在此处输入图像描述

原来,这个错误是由Callback类本身的内存释放产生的。而这个特殊的时刻

    void Unset()
    {
        if (this->pCallable_) {
            free(this->pCallable_);
            this->pCallable_ = nullptr;
        }
    }

目前,我非常不清楚问题出在哪里,因为我没有删除我无权访问的内存,而是删除了 COPYED 内存(在 Set 函数中,它正在复制)。有趣的是,这个类在库之外没有问题。也许我在这个复制过程中遗漏了一些重要的细节?或者也许你应该以不同的方式来实现你的回调?

c++
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    user7860670
    2020-05-14T04:15:50Z2020-05-14T04:15:50Z

    这里有几个问题:

    • 在 C++ 中你不应该使用malloc,如果你这样做了,你应该检查结果是否不是nullptr

    • 通过简单的字节复制进行的初始化只能合法地用于简单复制的对象(例如 int、void *)。如果您T有某种捕获 lambda 作为std::string副本,则该副本将包含指向原始字符串所拥有的缓冲区的潜在无效指针。在 C++ 中,您应该调用复制构造函数。那些。代替

     this->pCallable_ = malloc(sizeof(T));
     memcpy(this->pCallable_, &pCallable, sizeof(T));
    

    称呼

     this->pCallable = new T{pCallable};
    
    • 该类Callback将使用编译器生成的复制构造函数(和赋值运算符),它将简单地复制另一个对象拥有的指针的值。这将导致双重释放错误。您应该编写自己的复制(移动)构造函数(和运算符)的变体。

    • 在 C++ 中,您不应该处理手动删除内存 - 这就是智能指针的用途。

    最后两点被称为3/5/0 规则。

    至于在不同版本的 stl 的环境中使用这样的库 - 不幸的是,二进制兼容性确实存在困难。甚至在由同一个编译器收集的具有不同参数的代码之间。这样的包装器在这方面无济于事。传统上唯一的方法是使用最小的 C api。

    • 2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5