【问题标题】:how to define function pointer compatible with using lambda with capture as call back如何定义与使用 lambda 和捕获作为回调兼容的函数指针
【发布时间】:2020-01-13 18:50:39
【问题描述】:

需要一个 MFC 项目的简单模式对话框的几种变体,我编写了一个简单的类 CDialogDlg,它扩展了标准 MFC CDialog 类并包含用于实现特定回调的钩子,这些回调通常在类扩展CDialog。如果提供了这些回调,则在处理一些事件时使用,例如对话框初始化、OK 按钮处理以及提供接口对话框类变量和实际对话框控件的方法的DoDataExchange() 函数。

我的这个类的第一个版本使用标准函数进行回调。所以扩展类的回调设置方法用标准函数的地址设置了一个函数指针。

void SetDataExchangeCallBack(void(*f)(CDataExchange* pDX)) { funcDX = f; };
void SetInitCallBack(void(*f)(CWnd *dlgWnd)) { funcInit = f; }
void SetOnOkCallBack(void(*f)(CWnd *dlgWnd, CDocument *pDoc)) { funcOK = f; }

然后我意识到我应该能够使用 lambda 而不是标准函数。 lambdas 的第一个版本,它使用与标准函数相同的参数,没有捕获任何编译好的变量,并且可以与扩展类中的现有方法和函数指针一起正常工作。

但是,当我尝试在回调 setter 方法中指定的 lambda 中捕获一个变量时,我遇到了编译错误。

查看Passing capturing lambda as function pointerC++ lambda with captures as a function pointer 我发现尝试使用捕获执行lambda 将无法使用我正在使用的函数指针和回调设置函数的定义进行编译。

所以我决定在类中添加一个额外的 lambda 特定函数指针,同时覆盖现有方法,并使用一个额外的方法在类中使用 lambda 特定函数指针设置回调。

问题: 函数中的函数指针定义和参数定义应该是什么样的?我想在方法CPCSampleView::OnDisplayReportList () 中捕获局部变量CPCSampleDoc *pDoc。此变量包含一个指向 CDocument 派生对象的指针,该对象用于我想要捕获的视图,而不是使用存储它的方法并执行 static_cast 以将其返回到当前版本的 lambdas 不捕获.

源代码

扩展CDialogCDialogDlg 类如下所示:

class CDialogDlg : public CDialog
{
    // Construction
    void(*funcDX)(CDataExchange* pDX);
    void(*funcDXpDoc)(CDataExchange* pDX, CDocument *pDoc);
    void(*funcInit)(CWnd *dlgWnd);
    void(*funcOK)(CWnd *dlgWnd, CDocument *pDoc);
    CDocument *m_pDoc;

public:
    CDialogDlg(UINT nIDTemplate, CWnd* pParentWnd = NULL, void(*f)(CDataExchange* pDX) = NULL) : CDialog(nIDTemplate, pParentWnd) { funcDX = f; funcInit = NULL; }
    CDialogDlg(UINT nIDTemplate, CDocument *pDoc, CWnd* pParentWnd = NULL, void(*f)(CDataExchange* pDX, CDocument *pDoc) = NULL) : CDialog(nIDTemplate, pParentWnd) {
        funcDX = NULL; funcDXpDoc = f; funcInit = NULL; m_pDoc = pDoc;
    }

    void SetDataExchangeCallBack(void(*f)(CDataExchange* pDX)) { funcDX = f; };
    void SetInitCallBack(void(*f)(CWnd *dlgWnd)) { funcInit = f; }
    void SetOnOkCallBack(void(*f)(CWnd *dlgWnd, CDocument *pDoc)) { funcOK = f; }

    // Dialog Data
    //{{AFX_DATA(CCashierNoDlg)
    //}}AFX_DATA

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CCashierNoDlg)
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

protected:
    // Generated message map functions
    //{{AFX_MSG(CCashierNoDlg)
    virtual BOOL OnInitDialog();
    virtual void OnOK();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CDialogDlg, CDialog)
    //{{AFX_MSG_MAP(CCashierNoDlg)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CDialogDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    if (funcDX) funcDX(pDX);
    if (funcDXpDoc) funcDXpDoc(pDX, m_pDoc);
    //{{AFX_DATA_MAP(CCashierNoDlg)
    //}}AFX_DATA_MAP
}

BOOL CDialogDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    if (funcInit) funcInit(this);
    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}

void CDialogDlg::OnOK()
{
    if (funcOK) funcOK(this, m_pDoc);
    CDialog::OnOK();
}

由菜单选择触发的CView 方法会显示一个对话框,其中包含可供选择的项目列表。显示的对话框是具有特定对话框模板 ID 的 CDialogDlg 对象。对于特殊处理,我使用了两个不同的回调,它们当前正在使用不捕获的 lambda。结果如下:

void CPCSampleView::OnDisplayReportList ()
{
    CPCSampleDoc *pDoc = GetDocument();

    CDialogDlg myDialog(IDD_DIALOG_REPORTLIST, pDoc, this, [](CDataExchange* pDX, CDocument *pDoc) {
        if (pDX->m_bSaveAndValidate) {
        }
        else {
            CPCSampleDoc *pDocDoc = static_cast<CPCSampleDoc *>(pDoc);
            POSITION pos = NULL;

            do {
                CPCSampleDoc::ListReportList sectionHeader;
                CListBox *x = static_cast<CListBox *>(pDX->m_pDlgWnd->GetDlgItem(IDC_LIST1));

                pos = pDocDoc->GetReportSectionHeader(pos, sectionHeader);
                x->AddString(sectionHeader.m_SectionTitle);
            } while (pos);
        }
    });

    myDialog.SetOnOkCallBack([](CWnd *dlgWnd, CDocument *pDoc) {
        CPCSampleDoc *pDocDoc = static_cast<CPCSampleDoc *>(pDoc);
        CListBox *x = static_cast<CListBox *>(dlgWnd->GetDlgItem(IDC_LIST1));
        int iPtr = x->GetCurSel();
        POSITION pos = NULL;

        do {
            CPCSampleDoc::ListReportList sectionHeader;

            pos = pDocDoc->GetReportSectionHeader(pos, sectionHeader);
            if (iPtr < 1) {
                pDocDoc->MoveToReportSectionHeader(sectionHeader.m_ListOffset);
                break;
            }
            iPtr--;
        } while (pos);
    });

    myDialog.DoModal();
}

【问题讨论】:

    标签: c++ c++11 lambda visual-studio-2015 mfc


    【解决方案1】:

    您可以使用std::function 来允许捕获 lambda 和其他仿函数以及常规函数指针:

    void(*funcDX)(CDataExchange* pDX);
    void(*funcDXpDoc)(CDataExchange* pDX, CDocument *pDoc);
    void(*funcInit)(CWnd *dlgWnd);
    void(*funcOK)(CWnd *dlgWnd, CDocument *pDoc);
    

    变成

    std::function<void(CDataExchange*)> funcDX;
    std::function<void(CDataExchange*, CDocument*)> funcDXpDoc;
    std::function<void(CWnd*)> funcInit;
    std::function<void(CWnd*, CDocument*)> funcOK;
    

    你的 setter/constructor 也应该将指针函数更改为 std::function

    void SetInitCallBack(std::function<void(CWnd*)> f) { funcInit = f; }
    

    否则你的用法是相同的:

    if (funcDX) funcDX(pDX);
    

    funcInit = nullptr;
    

    【讨论】:

    • 谢谢。我将在今天早上晚些时候回到实验室并跟进结果或是否有任何问题时尝试一下。美国东海岸凌晨 1 点,我需要睡几个小时。
    • 我目前使用的方法是保留那里的内容,并提供三个回调设置函数的克隆,这些函数接受 std::function&lt;&gt; 参数以及必要的附加 std::function&lt;&gt; 变量和添加检查使用回调的类方法是否设置为nullptr 以外的其他值。我的理解是,使用std::function&lt;&gt; 以这样一种方式包装 lambda,即只要 lambda 参数相同,捕获不同变量的不同 lambda 都可以使用相同的回调变量。对吗?
    • 可以用std::function完全替换指针函数。 std::function 具有采用指针函数的构造函数。如果参数匹配,std::function 几乎可以存储任何可调用对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    相关资源
    最近更新 更多