如何定义与使用 lambda 兼容的函数指针并将捕获作为回调

how to define function pointer compatible with using lambda with capture as call back

需要一个带有 MFC 项目的简单模式对话框的几个变体,我写了一个简单的 class、CDialogDlg,它扩展了标准 MFC CDialog class 和包含用于实现特定回调的挂钩,这些回调通常作为 class 扩展 CDialog 中的方法实现。这些回调(如果提供)用于处理某些事件,例如对话框初始化、OK 按钮处理和 DoDataExchange() 函数,该函数提供了连接对话框 class 变量和实际对话框控件的方法。

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

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,它使用与标准函数相同的参数,没有捕获任何编译好的变量,并且可以很好地与扩展 class.

中的现有方法和函数指针一起工作

然而,当我试图在回调 setter 方法中指定的 lambda 中捕获变量时,我遇到了编译错误。

回顾 and C++ lambda with captures as a function pointer 我知道尝试使用捕获来执行 lambda 将无法使用我正在使用的函数指针和回调设置函数的定义进行编译。

所以我决定我应该在 class 中添加一个额外的 lambda 特定函数指针,同时覆盖现有方法,并在 [=] 中使用 lambda 特定函数指针设置回调50=].

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

源代码

扩展 CDialogCDialogDlg class 如下所示:

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();
}

您可以使用 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;