为什么作为 lambda 函数发送参数不起作用,而作为普通函数指针发送却起作用

Jer*_*rry 6 c++ debugging curl libcurl

我一直在研究 libcurl 库,我需要为 API 提供一个回调函数,说明它应该如何处理接收到的数据。我尝试将这个回调函数作为 lambda 提供给它,它给了我访问冲突错误,而当我在其他地方定义函数后给出相同的函数作为函数指针时,它工作得很好!我想知道两者之间有什么区别,因为我认为它们是同一件事。

以下部分使用的代码来自https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html,这是libcurl的文档。

这是我向其发送 lambda 函数的代码(这会产生访问冲突错误):

int main() {
    // Irrelevant initial code...

    struct memory {
        char* response;
        size_t size;
    } chunk = { 0 };
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*) &chunk);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, // I define a lambda function for the callback...
        [](void* data, size_t size, size_t nmemb, void* chunk) -> size_t {
            cout << "This function will never get called.. :(" << endl;
            size_t realSize = size * nmemb;
            memory* mem = (memory*) chunk;
    
            char* ptr = (char*) realloc(mem->response, mem->size + realSize + 1);
            if (ptr == NULL) return 0;
    
            mem->response = ptr;
            memcpy(&(mem->response[mem->size]), data, realSize);
            mem->size += realSize;
            mem->response[mem->size] = 0;
    
            return realSize;
        });
    
    CURLcode success = curl_easy_perform(handle);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这里 lambda 函数从未被调用,因此该行This function will never get called.. :(永远不会显示在控制台中。它在名为 sendf.c 的文件的第 563 行中给出了 libcurl 库内部的访问冲突错误。

现在这是同样的事情,但我在外面定义了函数:

struct memory {
    char* response;
    int size;
};

// defined the callback function outside...
size_t cb(void* data, size_t size, size_t nmemb, void* userp)
{
    cout << "Working!" << endl;
    size_t realsize = size * nmemb;
    memory* mem = (memory*)userp;

    char* ptr = (char*) realloc(mem->response, mem->size + realsize + 1);
    if (ptr == NULL)
        return 0;

    mem->response = ptr;
    memcpy(&(mem->response[mem->size]), data, realsize);
    mem->size += realsize;
    mem->response[mem->size] = 0;

    return realsize;
}

int main() {
    // Irrelevant initial code...

    memory chunk = { 0 };
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*) &chunk);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cb);

    CURLcode success = curl_easy_perform(handle);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个有效并显示Working!在控制台中。

我不明白为什么这两个不同,为什么其中一个有效而另一个无效。是否可以使用 lambda 函数方法来完成这项工作,因为我认为它看起来更简洁。

Who*_*aig 3

编辑

@user17732522 的主要道具提醒我,只需使用+lambda 的前缀即可获得相同的效果,而无需所有戏剧性的操作。对于我来说编写合理的答案已经太晚了。Sry我完全把它分开了。

原来的

curl_easy_setopt采用一个可变参数堆栈,并根据正在建立的选项将它们分解为子组件。如果你深入研究curl headers,你会发现:

CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);
Run Code Online (Sandbox Code Playgroud)

变量参数很重要。这将需要几乎任何东西,并期望它作为其原生类型。那么...该 lambda 的本机类型是什么。一种“作弊”的判断方法是使用带有漂亮打印选项的通用模板来宣布扩展中收到的类型(这听起来很令人困惑,我保证不会在一分钟内出现):

#include <iostream>

template<class T>
void foo(T&&)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

int main() 
{
    foo([](void *, size_t , size_t , void *) -> size_t
    {
        return size_t();
    });

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出

void foo(T &&) [T = (lambda at main.cpp:11:9)]
Run Code Online (Sandbox Code Playgroud)

所以,是的,它很高兴地将“lambda”推入变量 arg 列表中。合法地转换为等效的函数指针类型(我们可以这样做,因为 lambda 是非捕获的),给我们:

#include <iostream>

template<class T>
void foo(T&&)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

int main() 
{
    foo(static_cast<size_t (*)(void *, size_t, size_t, void *)>(
        [](void *, size_t , size_t , void *) -> size_t
        {
            return size_t();
        }));

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出

void foo(T &&) [T = unsigned long (*)(void *, unsigned long, unsigned long, void *)]
Run Code Online (Sandbox Code Playgroud)

现在,一个实际的函数指针被推入 arg 列表中,这是所期望的CURLOPT_WRITEFUNCTION

注意:如果上下文调用函数指针期望,则可以在函数指针上下文中隐式使用非捕获 lambda。例如,以下内容不需要强制转换;转换是隐式的,因为 lambda 是非捕获的,因此是合格的,并且上下文专门调用相应匹配的函数指针:

#include <iostream>

void bar(size_t (*)(void *, size_t, size_t, void *))
{
}

int main() 
{
    bar([](void *, size_t , size_t , void *) -> size_t
        {
            return size_t();
        });

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

简短的答案是:curl_easy_setopt变量参数正在将您的 lambda 作为 lambda 进行消耗;不作为函数指针。如果在这种情况下你想要一个函数指针(你确实想要),你需要“哄”它屈服。